Qt基础教程之常用控件

Qt基础教程之常用控件

  本文作为Qt培训的基础篇,主要是给Qt新手介绍一下Qt的常用控件。在讲这些之前,我觉得对于新手来说首先应该学会使用Qt的IDE,也就是QtCreator。

一、QtCreator

1.1、代码格式化

  多人开发项目需要注意就是代码风格问题,每个人代码风格不一样,在改别人的代码时不小心就会将别人的代码格式化为自己的代码风格,导致git提交代码改动很大,但是都是无效修改,所以需要在项目组内规范代码格式化风格。

   QtCreator默认使用ClangFormat风格格式化代码,但是有个缺点就是无法满足定制化需求,比如去掉多余空行。所以可以下载astyle这个插件,导入到IDE中,然后都使用同一种风格格式化就行。下面我给了一个比较实用的风格供参考。

1.2、Qt语言家

  开发桌面项目考虑到后续国际化,尽量不要在代码中引用中文,全部使用英文源码,这样可以减少很多编码问题,同时方便后续维护。前面没有其他语言的需求时,只用生成一份中文的翻译即可,这样后续有了其他语言的翻译需求,就不用改源码,而只需要加对应的翻译。qmake中添加翻译如下:

1
2
TRANSLATIONS  +=  resources/tr_zh.ts  \
resources/tr_en.ts

  源码中使用tr(“”)包裹需要翻译的英文。

  菜单中《更新翻译》是更新翻译文档的源码.ts类型文件,生成ts文件后可以双击ts文件打开语言家进行编辑,翻译对应的中文。  《发布翻译》是指将ts源码生成最终程序使用的二进制.qm格式,然后程序去加载对应的qm翻译文件。对于习惯使用ide的人来说,这样操作比较方便。但是如果代码不是人工编译,而是流水线自动编译的话,就需要将这个过程配置在qmake或者cmake的构建阶段,作为一个单独的任务执行。

1.3、文档注释

  很多时候阅读别人的代码非常困难,一方面是代码设计问题,一方面就是缺乏注释。QtCreator可以添加注释模板,比如常用的函数注释,头文件注释,下面示例就是头文件注释模板,定义之后,在代码中敲Head就会出现注释提示,可以节省很多时间。

1.4、Qt设计师

  Qt界面开发有两种模式,一种是纯代码手动布局控件,另外一种就是借助设计师托控件,让IDE帮你自动生成界面代码,你只需要实现逻辑即可。

1.4.1、布局

  界面如果缺乏布局,就会非常凌乱。常见布局分为三种,垂直、水平和网格。从字面意思就能理解这三种布局的区别。在设计师上放置布局,首先选中控件,然后右键选择菜单中的《布局》,二级菜单中就有对应的三种布局,设置布局之后就可以往布局上拖动控件,如果想要去掉父窗体的布局,选二级菜单中的《打破布局》。

1.4.2、提升控件

  自定义控件由于没有实现Qt设计师插件,所以无法直接在界面上拖,需要借助《提升控件》这个功能,因为控件的基类都是QWidget,所以一般都是在界面上拖一个QWidget作为基类,然后提升成自定义的派生类。

  假设你想将一个自定义的CustomWidget(继承于QWidget)放到设计师界面,先放置一个QWidget,然后右键选择《提升为》,出现下面界面之后先将基类选择为QWidget,然后填写对应的类名称,ide会自动填写头文件。然后点击添加,添加完最后点击提升。这样右侧对象树的类就会变成自定义类型。

1.4.3、样式表

  样式表(Qt Style Sheet)是为了美化界面,和前端界面的CSS一样,但是只是实现了CSS2以及部分CSS3的功能。为控件设置样式表,右键控件选择《改变样式表》,然后输入样式表点击Apply按钮就可以预览效果。

  但是对于维护来说这样就非常零散,每个控件里面都有自己的样式表,比较好的做法是将一套皮肤的样式表作为一个独立的文件,在程序启动时由QApplication全局加载,然后在切换皮肤(比如常见的深色和浅色模式)时,也能很方便整体切换。

二、常用控件介绍

  Qt的widget控件可以大体分为三种类型,1、窗体控件;2、单体式功能控件;3、容器型控件。容器型控件一般分为两种模式,一种是开箱即用的,内部已经封装好了默认Model,比如QListWidget。第二种就是稍微原始一点的,需要自定义Model的,缺点是用户需要继承Model类比如常用的QStandardItemModel,在此基础上实现model基类提供的虚函数接口,自己管理数据增、删和改等行为。但是优势也是非常明显,控件的用法就会非常灵活,可以快捷实现各种显示需求,比如常见的消息置顶、排序等功能。

2.1、窗体控件

2.1.1、QMainWindow

 从命名看就知道这是主窗体控件,其实QWidget没有父窗体也可以作为窗体控件,QMainWindow相比QWidget的优势在于已经集成了常用的主窗体所需要的元素,比如菜单栏(QMenuBar)和状态栏(QStatusBar)。QMainWindow使用需要注意的是控件整体已经存在布局了,不能直接给QMainWindow加布局。中央区域的centralWidget才是给用户显示自己控件的地方。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ui->setupUi(this);
QPushButton *btn1 = new QPushButton();

QPushButton *btn2 = new QPushButton();

QPushButton *btn3 = new QPushButton();

QHBoxLayout *layout = new QHBoxLayout();

layout->addWidget(btn1);

layout->addWidget(btn2);

layout->addWidget(btn3);

//this->setLayout(layout); //错误写法

ui->centralwidget->setLayout(layout); //正确写法

错误写法Qt会给出警告“QWidget::setLayout: Attempting to set QLayout “” on MainWindow “MainWindow”, which already has a layout” 。编程的一个细节就是需要从警告学习正确做法,很多时候编译器和IDE等都会给出各种警告,很多时候一些程序异常崩溃问题就藏在这些被忽视的警告中,比如常见的无符号和有符号反转、整型溢出,很多程序都有这种类型的编译警告。一个健壮的程序首先应该从去除程序的编译警告开始。

2.1.2、QDialog

 QDialog作为弹出式对话框使用,没有特殊需求使用默认的即可,上面已经集成了文本显示,确定和取消等按钮。这个控件需要注意的是内存释放问题,我看到有一个奇怪的用法:

1
2
QDailog *dialog=new QDialog();
dialog->exec();

这是很明显的错误用法,导致内存泄漏,exec()是一个阻塞的函数,会进入事件循环,这里并不会导致立即释放窗体对象。正确用法如下:

1
2
QDailog dialog;
dialog.exec();

这样对话框结束后出了作用域自动释放内存即可。

2.1.3、QSplashScreen

  这是一种常用的软件封面弹窗,在软件启动阶段执行,基本用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPixmap pixmap(":/splash.png");
QSplashScreen splash(pixmap);
splash.show();
app.processEvents();
...
QMainWindow window;
window.show();
splash.finish(&window);
return app.exec();
}

  如果需要做一些动画效果,简单的方式可以加载gif,或者重写该类的paintEvent接口。

2.2、单体式功能控件

2.2.1、QPushButton

  这个控件本身基础功能很简单,就是提供一个按钮,然后给用户提供自定义的点击相应信号槽。比较适合带文字显示的按钮。需要注意的是点击(click)本身并不是一个基础事件,click包含press和release两个动作。这个控件有两个接口需要注意:1、setAutoRepeat,这个设置为true时,只要把按钮按下,就会一直发送pressed(), released(), 和 clicked() 信号。2、setMenu(),这个接口可以给按钮设置菜单。

2.2.2、QToolButton

  这个按钮比较适合作为工具栏上的图标按钮,比如word文档上的菜单栏,这个按钮可拓展性比较高,可以结合qss定制成各种类型的按钮。

2.2.3、QCheckBox

    这是复选框控件,一般作用就是给用户提供勾选项,如果是多项需要互质只能选择一项,那么可以结合QButtonGroup实现。

1
2
3
QButtonGroup *group = new QButtonGroup(this);
group->addButton(ui->checkBox,1);
group->addButton(ui->checkBox_2,2);

2.2.4、QComboBox

  这是下拉框控件,常规用法很简单,这里就不细讲。这里讲两个比较实用的用法:

  • 自动补全

    如果下拉框中选项过多,用户选择起来就会非常困难,所以可以打开可编辑属性,然后提供自动补全功能,这样使用起来就会很方便。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    QComboBox *m_ComboBox=new QComboBox(this);
    m_ComboBox->setEditable(true);
    QStandardItemModel *model=new QStandardItemModel(this); //源数据
    QList<QStandardItem *> row;
    QStandardItem *blankitem=new QStandardItem("");
    model->appendRow(blankitem); //增加一行空白
    QStringList list=getList(); //获取数据函数
    foreach (QStringList name, list) {
    if(!name.isEmpty()){
    row.clear();
    QStandardItem *item=new QStandardItem(name);
    model->appendRow(item); //添加一行,2列数据
    }
    }
    m_ComboBox->setModel(model); //QComboBox设置数据源

    QCompleter *completer=new QCompleter(m_ComboBox);
    completer->setCaseSensitivity(Qt::CaseInsensitive);
    completer->setModel(model);
    completer->setCompletionColumn(m_ComboBox->modelColumn());
    completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
    completer->setMaxVisibleItems(10); //下拉最大高度
    completer->popup()->setStyleSheet("QListView{font:75 12pt \"微软雅黑\";subcontrol-origin: padding;subcontrol-position: top right;width: 20px;}"); //设置弹出的补全列表样式

    m_ComboBox->setCompleter(completer); //设置自动补全
  • 自定义下拉List界面

    默认下拉框的List界面样式不够丰富可以调用setView()接口传入自定义的View。

2.2.5、QLineEdit

&emsp;&emsp;文本输入框,重点接口:

  • setEchoMode()设置显示模式,可以设置显示模式,比如输入时显示点或者星号作为密码框具体四种模式可以查看文档详细了解。

  • setPlaceholderText设置默认显示文本,一般作为提示信息,用户编辑时就会覆盖。

2.2.5、QLabel

&emsp;&emsp;这个控件一般有两种用法,1、显示文本;2、显示图片,支持gif图片。

2.2.6、QTextEdit

&emsp;&emsp;这个是富文本编辑框,支持HTML用法,有时候为了简单方便做出复杂的效果,可以直接使用现成的HTML的语法,比如实现语法高亮等。利用这个控件可以做一个简单的word文档编辑器。这里介绍一下插入图片的两种实现方法:

  • Qt API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    QImage image(":/Images/qq");

    if (image.isNull())
    return;

    int width = text_edit->viewport()->width();
    int height = text_edit->viewport()->height();
    if (image.width() > width || image.height() > height) {
    image = image.scaled(30, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation);
    }

    QTextCursor cursor = text_edit->textCursor();
    QTextDocument *document = text_edit->document();
    cursor.movePosition(QTextCursor::End);


    document->addResource(QTextDocument::ImageResource, QUrl(":/Images/qq"), QVariant(image));

    //插入图像,使用QTextCursor API文档:
    QTextImageFormat image_format;
    image_format.setName(":/Images/qq");
    cursor.insertImage(image_format);
  • HTML

    1
    text_edit->append("<img src=\"mydata://image.png\" />");

2.3、容器型控件

&emsp;&emsp;容器型控件参考stl标准库中各种容器,比如std::list<=>QListWidget(QListView)

2.3.1、QListWidget与QListView

&emsp;&emsp;这两个都是作为列表控件,QListWidget已经封装了Model组件,用户能够操作的就是QListWidgetItem,而QListView仅仅只是预留了接口,并没有集成Model组件,需要用户自己去实现Model类的接口。

  • QListWidget基础操作

    1
    2
    3
    4
    QListWidgetItem *newItem = new QListWidgetItem;
    newItem->setText(itemText);

    listWidget->insertItem(row, newItem);

对于这个控件来说只需要掌握它的几种信号发射时机区别即可,比如currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)信号是告诉你选择的项目发生变化,并且给出了前后两个Item的指针方便进一步操作。这里QListWidgetItem有个接口void setData(int role, const QVariant &value)。这个可以给每个Item按照角色(role)存储自定义的数据,通常用户setText时其实就是把数据存储到了Qt::DisplayRole对应的数据中,Qt自定义的这些role一般最好不要手动通过setData去修改。为此Qt提供了Qt::UserRole,用户自己的数据存放在用户角色就行。

  • QListView

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ListView               = new QListView(this);

    StandardItemModel = new QStandardItemModel(this);


    QStandardItem* item1 = new QStandardItem(QIcon("1.bmp"), "item1");

    QStandardItem* item2 = new QStandardItem(QIcon("2.bmp"), "item2");

    QStandardItem* item3 = new QStandardItem("item3");
    StandardItemModel->appendRow(item1);

    StandardItemModel->appendRow(item2);

    StandardItemModel->appendRow(item3);


    ListView->setIconSize(QSize(40, 30));

    ListView->setModel(iStandardItemModel);

&emsp;&emsp;这种用法是最基础的,没有重新派生QListView也没有派生QStandardItemModel,如果有需求需要自己管理Model里面的数据,那么可以如下继承QAbstractItemModel去实现虚函数接口。QStandardItemModel可以和QListView配合也可以和其他view配合,具体取决于原始数据模型是什么类型的数据结构。

2.3.2、QTreeWidget和QTreeView

&emsp;&emsp;树形结构控件一般是用来显示工程结构之类的树状数据,存储数据一般是xml或者json格式。QDomDocument可以解析xml文档,虽然性能比tinyxml低,但是操作简单。

1
2
3
4
5
6
7
8
QTreeWidget *treeWidget = new QTreeWidget();

treeWidget->setColumnCount(1);
QList<QTreeWidgetItem *> items;
for (int i = 0; i < 10; ++i)
items.append(new QTreeWidgetItem((QTreeWidget*)0, QStringList(QString("item: %1").arg(i))));

treeWidget->insertTopLevelItems(0, items);

这是给树形控件设置成一列,然后给插入了10个顶层Item,注意一点就是每一列可以有多个顶层Item,然后树形控件也可以设置多列。

2.3.3、QTableWidget和QTableView

&emsp;&emsp;表格控件一般少量数据就可以直接用QTableWidget,如果结合数据库显示大量表格内容,对于这种情况可以用QTableView配合两个特殊Model:1、QSqlTableModel;2、QSqlQueryModel。第一个Model封装了数据库的常用操作,可以方便查询以及更新数据并实时刷新view界面,第二个则是自由度更高,可以使用自定义sql的语句去查询数据,并于复杂的多表查询比较适合。

1
2
3
4
5
6
7
8
tableWidget = new QTableWidget(this);

tableWidget->setRowCount(10);
tableWidget->setColumnCount(5);

QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
(row+1)*(column+1)));
tableWidget->setItem(row, column, newItem);

需要注意到插入空白行列之后,里面的QTableWidgetItem是nullptr,只要setItem填充过之后才是有内容的,所以通过Item去获取指针之后,最好需要判空。


Qt基础教程之常用控件
http://yoursite.com/2021/11/10/Qt基础教程之常用控件/
作者
还在输入
发布于
2021年11月10日
许可协议