QT-第四节 布局管理、常用控件
布局管理器
- 所谓 GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在放置时,组件的位置尤其重要。我们必须要指定组件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件定位的机制。
- Qt 提供了两种组件定位机制:绝对定位和布局定位。
- 绝对定位:
- 绝对定位就是一种最原始的定位方法:给出这个组件的坐标和长宽值。
- 这样,Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是,如果用户改变了窗口大小,比如点击最大化按钮或者使用鼠标拖动窗口边缘,采用绝对定位的组件是不会有任何响应的。
- 这也很自然,因为你并没有告诉 Qt,在窗口变化时,组件是否要更新自己以及如何更新。或者,还有更简单的方法:禁止用户改变窗口大小。但这总不是长远之计。
- 布局定位
- 你只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。
- 绝对定位:
- 布局定位完美的解决了使用绝对定位的缺陷。
- Qt 提供的布局中以下三种是我们最常用的:
- QHBoxLayout:按照水平方向从左到右布局;
- QVBoxLayout:按照竖直方向从上到下布局;
- QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;
- 系统提供的布局控件
- 创建一个继承自QWidget基类带UI的测试项目
- 双击打开Widget.ui,会跳转到“设计”栏目,在顶部有个layouts工具栏,下面列出的就是系统提供的布局控件
- Vertical Layout: 垂直布局
- Horizontal Layout: 水平布局
- Grid Layout: 网格布局
- Form Layout:
- 使用
- 这4个为系统给我们提供的布局的控件,但是使用起来不是非常的灵活,这里就不详细介绍了。
- 利用widget做布局
- 第二种布局方式是利用控件里的widget来做布局,在Containers中
- widget控件就相当于iOS中的UIView,一切控件都继承自它,他是一个空白控件
- 在widget中的控件可以进行水平、垂直、栅格布局等操作,比较灵活。
- 常用布局操作:
- 如何让控件内部的子控件自动水平、垂直布局?
-
点击父控件,选择顶部工具栏的“ ”图标,则该控件内部的子控件全部水平布局 - 点击父控件,选择顶部工具栏的三个水平横线的图标,则该控件内部的子控件全部水平布局
-
- 如何居中显示控件?
- 左边的控件列表中spacers中后水平、垂直弹簧效果
- 左右拖入水平弹簧,则显示正常宽度,而且水平居中显示;
- 上下拖入垂直弹簧,则显示正常高度,且垂直居中显示
- 控件之间的间距布局
- 控件之间拖入水平弹框
- 点击这个弹簧,右下角spacer属性中有个sizeType,右边点击箭头下拉列表有各种方式
- 选择fixed的就是固定大小
- 然后设置sizeHint:宽度30,高度20.
- 此时运行项目再次伸缩窗口,登录、退出按钮间距就不会变化了
- 栅格布局Grid Layout
- 当n个控件的排列方式像一个表格一样时(n行,m列),可以采用栅格布局
- 如何让一个控件正好包含他的子控件大小呢?
- 点击这个控件,右下角属性中找到sizepolicy,展开
- 选择“垂直策略”,fixed即可
- 删除弹簧默认间距:
- 如果布局中使用了弹簧,那么弹簧上下左右有个默认的间距9
- 可以点击父控件,UI右下角属性,找到Layout,将这些间距全部清0
- 如何删掉布局呢?
- 不能直接删除布局
- 找到要删除布局的父控件
- 右击->布局->打破布局
- 然后才能删除这个布局
- 再重新布局这个控件内部子控件
- 如何让控件内部的子控件自动水平、垂直布局?
- 再布局的同时我们需要灵活运用弹簧的特性让我们的布局更加的美观,下面布局一个登陆窗口:
- 向UI拖入一个widget控件,将用户名、输入窗口拖入,水平自动布局
- 同理密码、输入框,水平自动布局
- 再拖一个widget控件,内部拖2个pushButton,左边“登录”右边“退出”按钮,水平自动布局
- 点击UI主窗口,垂直自动布局
- 此时会发现:用户名部分、密码部分、登录退出部分全部占满整个屏幕
- 登录、退出按钮的布局
- 登录左边、退出右边各拖入水平弹簧,使这2个按钮水平居中
- 登录、退出按钮间拖入水平弹簧设置2者之间的固定距离
- 用户名、密码模块的布局
- 点击主窗口,打破布局
- 打破用户名模块的布局同时删除父控件widget
- 密码部分也这样处理
- 拖入一个widget空间,将用户名、输入框、密码、输入框这4个控件都拖入这个控件中
- 点击这个widget控件,顶部工具栏选择“栅格布局”
- 这样就可以使“用户名输入框”与“密码输入框”垂直对齐了。
- 整个界面布局
- 点击主窗口UI,自动垂直布局
- 用户名、密码部分,水平居中正常大小显示布局
常用控件
- Qt为我们应用程序界面开发提供的一系列的控件,下面我们介绍两种最常用一些控件,所有控件的使用方法我们都可以通过帮助文档获取。
Buttons组
- Push Button
- 用于点击、显示图片+文字
- Tool Button
- 主要用于显示图片
- 也可以显示文字,需要修改ToolButtonStyle的属性类型
- Radio Button
- 带圆圈点的单选框
- 通常与Group Box 联合使用,这样能够是多组单选框互不影响
- 将n个单选框拖入Group Box 然后水平、垂直布局。
- Check Button
- 多选框
- 通常与Group Box 联合使用
ItemWidgets
- ListWidget
- 一个列表容器,容器内部可以放一行一行的内容
- QListWidgetItem是容器中的每一个项
- 可以设置对齐方式
-
代码举例:
//写诗 QListWidget *listWidget = ui->listWidget; QListWidgetItem *item = new QListWidgetItem("锄禾日当午"); QListWidgetItem *item2 = new QListWidgetItem("汗滴禾下土"); //设置对齐方式:右侧对齐 item->setTextAlignment(Qt::AlignRight); item2->setTextAlignment(Qt::AlignRight); listWidget->addItem(item); listWidget->addItem(item2); //无法设置对齐方式 QStringList list; list<<"谁知盘中餐"<<"粒粒皆辛苦"; listWidget->addItems(list);
- TreeWidget
- 表格性列表,表头,子列表
-
代码举例:
//Treewidget的使用 QTreeWidget *treewidget = ui->treeWidget; //1.添加头 treewidget->setHeaderLabels(QStringList()<<"英雄"<<"英雄介绍"); //2. 添加顶层项 QTreeWidgetItem *titem = new QTreeWidgetItem(QStringList()<<"力量"); QTreeWidgetItem *titem2 = new QTreeWidgetItem(QStringList()<<"敏捷"); QTreeWidgetItem *titem3 = new QTreeWidgetItem(QStringList()<<"智力"); treewidget->addTopLevelItem(titem); treewidget->addTopLevelItem(titem2); treewidget->addTopLevelItem(titem3); //3. 给力量、敏捷、智力追加子项 QStringList heroL1; QStringList heroL2; QStringList heroM1; QStringList heroM2; QStringList heroZ1; QStringList heroZ2; heroL1 << "刚被猪" << "前排坦克,能在吸收伤害的同时造成可观的范围输出"; heroL2 << "船长" << "前排坦克,能肉能输出能控场的全能英雄"; heroM1 << "月骑" << "中排物理输出,可以使用分裂利刃攻击多个目标"; heroM2 << "小鱼人" << "前排战士,擅长偷取敌人的属性来增强自身战力"; heroZ1 << "死灵法师" << "前排法师坦克,魔法抗性较高,拥有治疗技能"; heroZ2 << "巫医" << "后排辅助法师,可以使用奇特的巫术诅咒敌人与治疗队友"; QTreeWidgetItem *litem1 = new QTreeWidgetItem(heroL1); QTreeWidgetItem *litem2 = new QTreeWidgetItem(heroL2); titem->addChild(litem1); titem->addChild(litem2); QTreeWidgetItem *mitem1 = new QTreeWidgetItem(heroM1); QTreeWidgetItem *mitem2 = new QTreeWidgetItem(heroM2); titem2->addChild(mitem1); titem2->addChild(mitem2); QTreeWidgetItem *zitem1 = new QTreeWidgetItem(heroZ1); QTreeWidgetItem *zitem2 = new QTreeWidgetItem(heroZ2); titem3->addChild(zitem1); titem3->addChild(zitem2);
- TableWidget
- 就是一个真正意义上的表格
-
代码示例
//QTableWidget的使用 QTableWidget *tableWidget = ui->tableWidget; //1. 设置水平方向表头 //1.1. 设置有多少列 QStringList clist; clist<<"姓名"<<"性别"<<"年龄"; tableWidget->setColumnCount(clist.size()); //设置列宽 // tableWidget->setColumnWidth(1,20); //1.2. 设置水平方向的表头 tableWidget->setHorizontalHeaderLabels(QStringList()<<"姓名"<<"性别"<<"年龄"); //2. 设置行 tableWidget->setRowCount(5); //3. 设置表中的内容 // QTableWidgetItem *tableItem =new QTableWidgetItem("亚瑟"); // tableItem->setTextAlignment(Qt::AlignCenter); // tableWidget->setItem(0,0,tableItem); QStringList nameList; nameList<<"亚瑟"<<"妲己"<<"安其拉"<<"东皇太一"<<"李白"; //QStringList QList<QString> 二者等价 QList<QString> sexlist; sexlist <<"男"<<"女"<<"女"<<"男"<<"男"; for (int i = 0; i<5; i++) { int clo = 0; //添加性别 tableWidget->setItem(i,clo++,new QTableWidgetItem(nameList[i])); //性别 tableWidget->setItem(i,clo++,new QTableWidgetItem(sexlist[i])); //年龄 int 转Qstring :QString::number(int) tableWidget->setItem(i,clo++,new QTableWidgetItem(QString::number(30+i))); } //点击按钮添加赵云 connect(ui->addzyButton,&QPushButton::clicked,[=](){ //1. 先判断有没有赵云,没有则添加,反之不添加 bool isempty = tableWidget->findItems("赵云",Qt::MatchExactly).empty(); if(isempty){ tableWidget->insertRow(0); tableWidget->setItem(0,0,new QTableWidgetItem("赵云")); //性别 tableWidget->setItem(0,1,new QTableWidgetItem("男")); //年龄 int 转Qstring :QString::number(int) tableWidget->setItem(0,2,new QTableWidgetItem(QString::number(30))); }else { //提示 QMessageBox::warning(this,"警告!","已经添加!"); } }); //点击删除,删除赵云 connect(ui->delzyButton,&QPushButton::clicked,[=](){ //1. 先判断有没有赵云,有则删除,反之不删 bool isempty = tableWidget->findItems("赵云",Qt::MatchExactly).empty(); if(isempty){ //提示 QMessageBox::warning(this,"警告!","没有!"); }else { //删除 //1. 找到item QTableWidgetItem *temitem = tableWidget->findItems("赵云",Qt::MatchExactly).first(); //2. 找到行 int row = temitem->row(); //3. 删除 tableWidget->removeRow(row); } });
Containers
- GroupBox: 前面已经介绍,用于分组显示
- Scroll Area:
- 当有n个控件,按一定顺序排列,而又不想占用太多的空间时
- 可以将这n个控件拖入到ScrollArea中,然后垂直或者水平布局,n个控件可以在ScrollArea中滚动显示
- ToolBox:相当于qq的组头,家人、朋友、黑名单等
- 注意修改的话需要在属性修改
- Tab Widget:标签页面
- 就相当于打开n个网页,上面的标签
- 点击相应的标签,显示相应的页面
- 点击标签,右下角的属性,可以修改标签的内容
- Stack Widget
- 这个控件,右上角有个“左右小箭头”
- 点击小箭头一次,翻一次页,每页可以有不同的页面布局
- 每个页面可以布局一些内容,然后点击箭头,布局下一个页面
- 如果页面不够用,右击该控件插入一个即可
- 但是运行起来发现那个“左右小箭头”不见了,那么怎么使用呢?
- 它相当于一个栈,通过设置显示不同的栈
- 比如现在已经设置好了3个页面,3个页面分别拖入了:ScrollArea、ToolBox、TabWidget
- 然后在该控件外部拖3个按钮,点击不同的按钮显示相应的栈
- 点击StackWidget控件,查看右下角属性的QStackWidget下的currentIndex,可以看到当前显示的内容是第几个页面,点击左右箭头,可以查看每个页面对应的index是多少
- 点击3个不同的按钮,StackWidget显示相应的页面
-
代码举例:
//stackWidget页面 connect(ui->btnscroll,&QPushButton::clicked,[=](){ //0、1、2这些index,是从stackedWidget的currentIndex属性中查看到的 //指定stackedWidget显示那个页面 ui->stackedWidget->setCurrentIndex(0); }); //toolBox页面 connect(ui->btntool,&QPushButton::clicked,[=](){ ui->stackedWidget->setCurrentIndex(1); }); //tab页面 connect(ui->btntabWid,&QPushButton::clicked,[=](){ ui->stackedWidget->setCurrentIndex(2); });
Input Widgets
- combo Box
- 下拉菜单
-
代码示例
//comboBox ui->comboBox->addItem("奔驰"); ui->comboBox->addItem("宝马"); ui->comboBox->addItem("拖拉机"); //指定选择哪个item ui->comboBox->setCurrentIndex(1);
- Line Edit
- 相当于iOS的UITextField
- 有个属性echoMode,用于设置输入的内容是明文还是暗文
- 使用QLineEdit类的setEchoMode () 函数设置文本的显示模式,函数声明:
-
EchoMode是一个枚举类型,一共定义了四种显示模式:
QLineEdit::Normal 模式显示方式,按照输入的内容显示。 QLineEdit::NoEcho 不显示任何内容,此模式下无法看到用户的输入。 QLineEdit::Password 密码模式,输入的字符会根据平台转换为特殊字符。 QLineEdit::PasswordEchoOnEdit 编辑时显示字符否则显示字符作为密码。
- 另外,我们再使用QLineEdit显示文本的时候,希望在左侧留出一段空白的区域,那么,就可以使用QLineEdit给我们提供的setTextMargins函数:
void setTextMargins(int left, int top, int right, int bottom)
- 用此函数可以指定显示的文本与输入框上下左右边界的间隔的像素数。
- Text Edit
- 相当于iOS的UITextView
- pinBox:点击上下箭头,加减数字
Display Widgets
- QLabel
- QLabel是我们最常用的控件之一,其功能很强大,我们可以用来显示文本,图片和动画等。
- 显示文字 (普通文本、html)
- 通过QLabel类的setText函数设置显示的内容:
void setText(const QString &)
-
可以显示普通文本字符串
QLable *label = new QLable; label->setText(“Hello, World!”);
-
可以显示HTML格式的字符串,比如显示一个链接:
QLabel * label = new QLabel(this); label ->setText("Hello, World"); label ->setText("<h1><a href=\"https://www.baidu.com\">百度一下</a></h1>"); label ->setOpenExternalLinks(true);
- 其中setOpenExternalLinks()函数是用来设置用户点击链接之后是否自动打开链接,如果参数指定为true则会自动打开。
- 通过QLabel类的setText函数设置显示的内容:
- 显示图片
- 可以使用QLabel的成员函数setPixmap设置图片
void setPixmap(const QPixmap &)
-
代码:
QPixmap pixmap; pixmap.load(":/Image/boat.jpg"); QLabel *label = new QLabel; label.setPixmap(pixmap);
- 可以使用QLabel的成员函数setPixmap设置图片
- 显示动画
- 可以使用QLabel 的成员函数setMovie加载动画,可以播放gif格式的文件
void setMovie(QMovie * movie)
-
代码:
//用Qlabel显示图片 // ui->labelImage->setPixmap(QPixmap(":/Image/Luffy.png")); //显示gif图片 QMovie *move = new QMovie(":/Image/mario.gif"); ui->labelImage->setMovie(move); //播放 move->start();
- 可以使用QLabel 的成员函数setMovie加载动画,可以播放gif格式的文件
自定义控件
- 在搭建Qt窗口界面的时候,在一个项目中很多窗口,或者是窗口中的某个模块会被经常性的重复使用。一般遇到这种情况我们都会将这个窗口或者模块拿出来做成一个独立的窗口类,以备以后重复使用。
- 在使用Qt的ui文件搭建界面的时候,工具栏栏中只为我们提供了标准的窗口控件,如果我们想使用自定义控件怎么办?
- 自定义一个带UI的控件(相当于iOS的代码+xib)
- 右击项目->Add New…->如果选择C++那就是不带UI的,因此选择Qt->Qt设计界面类->choose
- 选择空白控件Widget->next->设置类名比如:ZHDefineWidget->点击下一步/完成,则会创建3个文件:ZHDefineWidget.h/ZHDefineWidget.cpp/ZHDefineWidget.ui
- 布局ZHDefineWidget.ui,相当于布局Xib
- 拖入一个SpinBox和一个HorizontalSlider
- 整体水平布局
- QMainWindow.ui中使用ZHDefineWidget控件
- 在UI中拖入一个Widget控件(因为ZHDefineWidget继承自Widget)
- 右击该控件->提升为->提升的类名称:设置为ZHDefineWidget
- 勾选全局包含->点击添加->提升
- 此时在点击Widget控件,可以在右边属性中看到该类已经为ZHDefineWidget类了
- 运行起来就会发现,主界面显示的控件就是自定义布局的ZHDefineWidget控件。
- 注意:一旦自定义了这个控件,如果再拖入一个空白Widget控件,右击,有2个提升为,点击那个有箭头的,找到ZHDefineWidget,点击,就直接可以提升为ZHDefineWidget类了,不用其他步骤了
- 实现自定义控件的功能(点击加减,滑块移动;滑块移动,自动加减)
-
进入ZHDefineWidget.cpp中,实现代码如下:
ZHDefineWidget::ZHDefineWidget(QWidget *parent) : QWidget(parent),ui(new Ui::ZHDefineWidget) { ui->setupUi(this); //spinBox加减,slider移动 void (QSpinBox:: * signal)(int) = &QSpinBox:: valueChanged; QSpinBox *spinBox = ui->spinBox; connect(spinBox,signal,ui->horizontalSlider,&QSlider:: setValue); //滑动slider,spinBox自动加减 connect(ui->horizontalSlider,&QSlider::valueChanged,ui->spinBox,&QSpinBox:: setValue); }
-
- 主UI功能丰富
- 在主UI界面拖一个按钮“获取值”,再拖一个“设置一半”
- 分别实现获取当前自定义控件的值,以及设置自定义控件滑动到一半
- 代码功能实现:
- 在自定义ZHDefineWidget.h文件提供2个接口(成员函数),然后在.cpp中实现
- 在MainWindow.cpp中实现调用代码
-
代码示例:
//zhdefinewidget.cpp #include "zhdefinewidget.h" #include "ui_zhdefinewidget.h" ZHDefineWidget::ZHDefineWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ZHDefineWidget) { ui->setupUi(this); //spinBox加减,slider移动 void (QSpinBox:: * signal)(int) = &QSpinBox:: valueChanged; QSpinBox *spinBox = ui->spinBox; connect(spinBox,signal,ui->horizontalSlider,&QSlider:: setValue); //滑动slider,spinBox自动加减 connect(ui->horizontalSlider,&QSlider::valueChanged,ui->spinBox,&QSpinBox:: setValue); } ZHDefineWidget::~ZHDefineWidget() { delete ui; } //成员函数的实现 //设置值 void ZHDefineWidget:: setValue(int v){ ui->spinBox->setValue(v); } //获取值 int ZHDefineWidget:: getValue(){ return ui->spinBox->value(); } //mainwindow.cpp文件 #include "mainwindow.h" #include "ui_mainwindow.h" #include<QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); //获取值 connect(ui->pushButton,&QPushButton::clicked,[=](){ int value = ui->widget->getValue(); qDebug()<<"当前值为:"<< value; }); //点击设置一半 connect(ui->pushButton_2,&QPushButton:: clicked,[=](){ ui->widget->setValue(50); }); } MainWindow::~MainWindow() { delete ui; }
- 上面相当于iOS 的(代码+xib),那么下面用纯代码实现
- 右击项目->Add New…->选择C++->C++ class 输入类名: SmallWidget ;基类名为:QWidget,点击下一步、完成
-
实现代码如下:
// smallwidget.h class SmallWidget : public QWidget { Q_OBJECT public: explicit SmallWidget(QWidget *parent = 0); signals: public slots: private: QSpinBox* spin; QSlider* slider; }; // smallwidget.cpp SmallWidget::SmallWidget(QWidget *parent) : QWidget(parent) { spin = new QSpinBox(this); slider = new QSlider(Qt::Horizontal, this); // 创建布局对象 QHBoxLayout* layout = new QHBoxLayout; // 将控件添加到布局中 layout->addWidget(spin); layout->addWidget(slider); // 将布局设置到窗口中 setLayout(layout); // 添加消息响应 connect(spin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), slider, &QSlider::setValue); connect(slider, &QSlider::valueChanged, spin, &QSpinBox::setValue); }
- 使用同上面一样