QWidget是所有用户界面的基类,其他几乎所有控件都直接或间接继承自QWidget。
QWidget是用户界面的基本单元:它从窗口系统中接收键盘、鼠标等事件,并在屏幕上绘制自己,每个widget在屏幕上都是一个矩形区域。
QWidget的构造函数:
QWidget::QWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags())
参数2:Qt::WindowFlags f = {}设置窗口标识,默认适用于大多数widget,也可通过函数void setWindowFlags(Qt::WindowFlags type)进行设置。如:
//设置窗口无边框
setWindowFlags(Qt::FramelessWindowHint);
//设置控件大小和位置
void QWidget::setGeometry(int x, int y, int w, int h);
//获取位置
QPoint QWidget::pos() const;
//...
最简单的文本显示控件
#include
//...
QLabel* pLabel = new QLabel(this); //创建一个QLabel对象
pLabel->setText("Hello,Qt");
pLabel->setAlignment(Qt::AlignRight); //设置文字居右对齐
pLabel->setGeometry(10, 10, 200, 20);
注:创建QLabel对象时,需指定父对象。
#include
//...
QPushButton* pbtnOk = new QPushButton(this); //创建按钮
pbtnOk->setText("Ok");
单行文本输入框,用于文本的输入或编辑。
#include
//...
QLineEdit* pLineEdit = new QLineEdit(this); //创建对象
//设置占位文字,即输入框为空时显示的提示文字
pLineEdit->setPlaceholderText("请输入姓名");
//设置最大输入长度
pLineEdit->maxLength(5);
弹出式的消息提示框。一般调用QMessageBox类的静态函数。
int res = QMessageBox::warning(this, "删除", "确定删除?");
if(res == QMessageBox::Ok)
{
//...
}
else
{
//...
}
该机制类似于设计模式中的“观察者模式”,发布者发布信息,订阅者接收信息。
信号槽连接(Qt4风格)语法如下:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
//点击按钮,关闭窗口[cilcked()是类QPushBUtton内置的信号,close()是类QWidget内置的槽函数]
connect(pBtnClose, SIGNAL(clicked()), pWgt, SLOT(close()));
自定义信号使用宏signals或Q_SIGNALS,自定义槽函数使用宏slots或Q_SLOTS。如:
class Counter:public QObject
{
Q_OBJECT
public:
Counter(){m_value = 0;}
int value() const {return m_value;}
signals:
void valueChanged(int newValue);
public slots:
void setValue(int newValue);
};
void Counter::setValue(int newValue)
{
if(value != m_value)
{
m_value = newValue;
//发出信号
emit valueChanged(newValue);
}
}
//...
//创建Counter对象
Counter a,b;
QOject::connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
a.setValue(12);
b.setValue(38);
可根据返回值判断信号是否连接成功
bool res = connect(sender, SLGNAL(signal), recever, SLOT(slot));
连接成功返回true,失败返回false。
connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));
connect(slider, SIGNAL(valueChanged(int)), this, SLOT(updateStatusBarIndicator(int)));
注:信号发出时,会以不确定的顺序调用这些槽函数。
connect(lcd, SIGNAL(overflow()), this, SLOT(handleMathError()));
connect(calculator, SIGNAL(divisionByZero()), this, SLOT(handleMathError()));
connect(sender, SIGNAL(signal), receiver, SLOT(slot), Qt::UniqueConnection);
信号槽重复连接,加上 Qt::UniqueConnection,当发出信号时,槽函数只会调⽤⼀次;否则会多次调⽤槽函数。
disconnect(lcd, SIGNAL(overflow()), this, SLOT(handleMathError()));
通过blockSignals()可以阻塞信号的发出。
btn->blockSignals(true); //阻塞信号
btn->blockSignals(flase); //解除阻塞
帮助与提示
布局是为了让子控件能够自动调整大小和位置,从而保证能够充分利用空间。作用:
1.调整子控件位置
2.感知窗口大小的改变;
3.当字体、文字、控件显示、隐藏时,能够自动更新窗口内控件的大小和位置。
布局类都是继承自QLayout
常见的布局有:1.水平布局QHBoxLayout 2.垂直布局QVBoxLayout 3.层叠布局QStackedLayout 4.栅格布局QGridLayout
//1.设置外边距
QLayout::setMargin(int margin);
//分别设置左、右、上、下的外边距
QLayout::setContentMargins(int left, int top, int right, int bottom);
QLayout::setContentMargins(const QMargins& margins);
//2.设置间距
setSpacing(int);
//3.添加弹簧
addStretch(); //添加伸缩弹簧,理解为占位
//4.设置拉伸比例
QBoxLayout::setStretch(int index, int stretch);
QBoxLayout::setStretchFactor(QWidget* widget, int stretch);
QWidget: 所有其他控件的基类
QLabel: 文本显示框
QPushButton: 按钮
QMenu: 菜单
QLineEdit: 单行文本输入框
QTextEdit: 多行文本输入框
QTextBrowser: 文本显示控件
QCheckBox: 多选
QRadioButton: 单选
QComboBox: 下拉选择控件
QSpinBox:
QGroupBox:
QTimeEdit: 时间显示
QDateTimeEdit:
QSlider:
QProgressBar: 进度条
QTableWidget:
QStackedWidget:
1. 熟悉每个控件常用接口
2. 熟悉每个控件提供的信号
3。查找接口及其用法
编译一个程序,主要经过以下几个阶段:
1. 预编译:替换宏定义等;
2. 编译:将源码转为汇编码;
3. 汇编:将汇编码转为机器码(字节码),生成目标文件;
4. 链接:将目标文件转为可执行文件
静态库、动态库的区别主要在链接阶段如何处理,链接成可执行程序。在链接阶段,静态库会将汇编生成的目标文件.o与引用的库一起打包到可执行文件中;而动态库是在程序运行时才被载入。
Windows系统中,动态库(Dynamic-link library)文件大多以dll为后缀。
优点:
1. 节省空间。不同程序调用相同的库,内存中只需要有一份该共享库即可;
2. 便于部署更新。用户只需要更新动态库,进行增量更新,而不必重新编译整个应用程序。
创建动态库通常需要先定义导出宏,然后使用导出宏修饰需要导出的类/函数。
使用动态库通常需要三个方面:头文件、导入库lib文件、动态库dll文件
需求
帮助与提示
//创建QListWidget
QListWidget *pListWidget = new QListWidget(this);
//添加项
QString strBook0 = "The Call of the Wild";
QString strBook1 = "Sophie's Word";
pListWidget->addItem(strBook0);
pListWidget->addItem(strBook1);
//删除项
int row = pListWidget->currentRow(); //当前行行号
if(-1 != row)
{
QListWidgetItem* pItem = pListWidget->takeItem(row);
delete pItem;
pItem = nullptr;
}
//重命名
int iRow = pListWidget->currentRow();
if(-1 != iRow)
{
QListWidgetItem* pItem = pListWidget->takeItem(iRow);
pItem->setText("The Moon and Sixpence");
}
//清空
pListWidget->clear();
此外,QListWidget还提供很多有用的信号和接口来处理用户操作,如currentItemChanged()信号会在当前选中的Item发生改变时发出。
//创建树
QTreeWidget* pTreeWgt = new QTreeWidget(this);
//添加根节点
QTreeWidgetItem* pRootItem = new QTreeWidgetItem(pTreeWgt);
//添加树节点的子节点
QTreeWidgetItem* pChildItem = new QTreeWidgetItem();
pRootItem->addChild(pChildItem);
//创建表格
QTableWidget pTableWgt = new QTableWidget(this);
//设置行数、列数
pTableWgt->setRowCount(10);
pTableWgt->setColumnCount(3);
//设置水平头标签
QListItem tableHeader;
tableHeader<<"#"<<"Name"<<"Text";
pTableWgt->setHorizontalHeaderLabels(tableHeader);
//隐藏垂直表头
pTableWgt->verticalHeader()->setVisible(false);
//禁止编辑
pTableWgt->setEditTriggers(QAbstractItemView::NoEditTriggers);
//设置选中模式为选中整行
pTableWgt->setSelectionBehavior(QAbstractItemView::SelectRows);
//设置选中模式为只能选中一行
pTableWgt->setSelectionMode(QAbstractItemView::SingleSelection);
//不显示网格线
pTableWgt->setShowGrid(false);
注:所有这些控件,可直接通过代码编写,也可在界面及逆行编辑数据、查看属性。
即通过封装的方式,将已有的简单控件组装成复杂的控件,实现更丰富的界面效果。
封装
提升
复用
ui界面文件,使用Qt Designer打开,编辑控件和布局。本质是xml文件。其生成的代码也是在窗口中创建控件、调整位置、设置布局。
对于动态变化较大的控件,如通过配置文件创建不同数量的按钮,只用Designer是无法实现的,仍需要通过编码来做。
qrc资源文件,用于添加图片、样式表、配置文本文件等资源。图片添加完成后,可获得资源路径,可直接在程序中使用。qrc本质也是一个xml文件。
qrc ⽂件中添加的资源是写⼊到程序中的(可执⾏⽂件或者库⽂件中),所以在程序打包时不⽤考虑资源的路径问题。如果使⽤相对路径(如 images/bg-blue.png)或者绝对路径(如E:/images/bg-blue.png),那 么打包时需要将资源放到合适的路径下,很容易出现路径错误找不到图⽚等问题。建议在开发中使⽤ qrc ⽂件来管理资源。
帮助与提示
//以 QList 为例,Qt 容器的遍历有很多种写法。
QList list;
list << "A" << "B" << "C" << "D";
//Java ⻛格的迭代器
QListIterator i(list);
while (i.hasNext())
{
qDebug() << i.next();
}
//STL ⻛格的迭代器
QList::iterator i;
for (i = list.begin(); i != list.end(); ++i)
{
*i = (*i).toLower();
}
//foreach 关键字
foreach (const QString &str, list)
{
if (str.isEmpty())
{
break;
}
qDebug() << str;
}
qSort: 排序
qCopy: 复制
qFill: 覆盖
qFind: 查找
Qt中的样式表(Qt Style Sheet,qss)用来设计界面样式和元素布局。
QFile file("style.qss");
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
this->setStyleSheet(file.readAll());
}
最常用的选择器是“类型选择器”和“ID选择器”。
ui文件中的控件在编译时经过uic处理,会将其对象名(object name)设置为Qt Designer中指定的名称;若是在代码中创建的控件,需调用setObjectName()手动指定其对象名(object name)
QLabel* pNameLbl = new QLabel;
pNameLbl->setObjectName("nameLabel");
#nameLabel {border: 1px soild red;}
QComboBox::drop-down {image: url(dropdown.png)}
QPushButton { color: red; }
QPushButton:hover { color: green; }
QPushButton:pressed { color: #ffffff; }
这⾥:https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-pseudo-states 列出了 Qt 控件的所有伪状态。
当多条样式规则,用不同的值指定同一属性时,会发生冲突。如:
QPushButton#okButton { color: gray }
QPushButton { color: red }
这⾥ QPushButton#okButton 优先于 QPushButton,因为它指定了特定的对象。
同样,指定了伪状态的规则,优先于未指定伪状态的规则:
QPushButton:hover { color: white }
QPushButton { color: red }
当⿏标悬浮于按钮时,按钮上的⽂字呈红⾊。然⽽:
QPushButton:hover { color: white }
QPushButton:enabled { color: red }
这两个选择器有同样的优先级,此时最后⼀条规则⽣效。再看下⾯的情况:
QPushButton { color: red }
QAbstractButton { color: gray }
使用样式表时,每个widget都被当做有4个同心矩形的盒子:margin(外边距)、border(边框)、padding(内边距)、content(内容)。
QLabel {
border: 5px solid green;
margin: 10px 30px 20px 40px;
padding: 20px 30px 40px 10px;
}
#pushButton {
border-image: url(bg.png) 4px 6px 4px 6px;
border-width: 4px 6px 4px 6px;
}
void CustomWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
qDebug() << this->geometry();
qDebug() << this->frameGeometry();
qDebug() << this->pos();
QRect(686,404 547x361)
QRect(678,373 563x400)
QPoint(678,373)
// pushButton 的全局坐标
qDebug() << ui.pushButton->mapToGlobal(QPoint(0, 0));
// pushButton 相对于主窗⼝的坐标
qDebug() << ui.pushButton->mapTo(this, QPoint(0, 0));
// 输出窗⼝结果:
QPoint(796,554)
QPoint(110,150)
void TestWidget::mousePressEvent(QMouseEvent *event)
{
qDebug() << event->pos();
qDebug() << event->globalPos();
}
void TestWidget::mousePressEvent(QMouseEvent *event)
{
// 调⽤⽗类的函数
QWidget::mousePressEvent(event);
// 获取⿏标按下的位置
QPoint pos = event->pos();
}
文件读写使用QFile类。
QFile file("a.txt");
if(file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QString strText = file.readAll();
}
-
...(不往下写了,请⾃⼰设计配置⽂件格式)
帮助与提示
通过QPainter实现Qt二维绘图:
1. 几何图形,点、线、多边形等;
2. 特殊效果,如渐变;
3. 变换、平移、旋转、缩放等。
绘图时,需实现paintEvent函数,并创建QPainter对象。
void CustomWidget::painterEvent(QPainterEvent* event)
{
QPainter painter(this);
//...
}
通过QPainter::setPen()设置画笔,使用画笔设置线的属性。如:线宽、线色、线型
通过QPainter::setBrush()设置画刷,用于控制绘图时的填充属性:填充色、填充样式。
使用QPainter可以画各种各样的图形或文字等元素,如:
1. 点:QPainter::drawPoint()
2. 线:QPainter::drawLine()
3. 矩形:QPainter::drawRect()
4. 圆角矩形:QPainter::drawRoundedRect()
5. 多边形:QPainter::drawPolygon()
6. 扇形:QPainter::drawPie()
7. 文字:QPainter::drawText()
8. 图片:QPainter::drawPixmap()
...
QPainterPath path;
path.moveTo(20, 80);
path.lineTo(20, 30);
path.cubicTo(80, 0, 50, 50, 80, 80);
QPainter painter(this);
painter.drawPath(path);
void CustomWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setPen(Qt::red);
// 坐标变换
painter.translate(100, 200);
painter.rotate(90);
painter.scale(0.5, 0.5);
painter.drawLine(0, 0, 200, 200);
}
通过 QPainter::resetTransform() 将所有经过平移、旋转、伸缩等变换进⾏重设。
保存、恢复变换状态
坐标变换的状态可以保存和恢复,类似⼀个栈结构,保存时将状态⼊栈,恢复时将状态出栈。
保存:QPainter::save()
恢复:QPainter::restore()
void CustomWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setPen(Qt::red);
// 保存状态
painter.save();
painter.translate(100, 200);
painter.rotate(90);
painter.scale(0.5, 0.5);
// 恢复状态
painter.restore();
// 恢复之前的状态后,在该状态下继续进⾏坐标变换
painter.translate(200, 30);
painter.drawLine(0, 0, 200, 200);
}
GraphicsView提供了用于管理大量图形元素及其交互的方式,并提供了一个用于显示这些元素并且支持放缩、旋转的widget。在图形元素多、坐标变换多的情况下,更偏向使用GraphicsView,而非QWidget
QGraphicsScence提供了图形显示场景,主要负责:提供用于管理大量item的场景;将事件传给每个item;管理item的状态,如选中和聚焦处理。
场景(Scene)是QGraphicsItem对象的容器,通过调用QGraphicsScene::addItem()添加Item,再获取item。
QGraphicsScene* pscene = new QGraphicsScene();
QGraphicsView继承自QWidget,用于显示场景中的内容。
QGraphicsView* pView = new QGraphicsView();
pView->setScene(pScene);
QGraphicsItem是场景中图元的基类,如矩形(QGraphicsRectItem)、椭圆(QGraphicsEllipseItem)、文本(QGraphicsTextItem)。不过更强大的是,你可实现自定义item。QGraphicsItem支持以下特性:
若是自定义图元,首先需继承QGraphicsItem,然后实现以下的纯虚函数:
class SimpleItem : public QGraphicsItem
{
public:
QRectF boundingRect() const override
{
qreal penWidth = 1;
return QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
20 + penWidth, 20 + penWidth);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget) override
{
painter->drawRoundedRect(-10, -10, 20, 20, 5, 5);
}
};
SimpleItem *pItem = new SimpleItem();
pScene->addItem(pItem);
Qt提供了2种使用定时器的方法,一是使用QTimer类,二是使用QObject提供的定时器接口和事件。
QTimer类提供了定时器接口,通过QTimer::start()启动定时器。定时器到达间隔时间后会发出信号timeout(),然后重复计时。
// 创建定时器
QTimer* pTimer = new QTimer(this);
// 信号timeout()连接到槽函数
connect(pTimer, SIGNAL(timeout()), this, SLOT(timeoutSlot()));
// 启动定时器
pTimer->start(1000);
在这个示例中,定时器的间隔是 1000 ms。即每隔 1s 钟,定时器都会发出⼀次 timeout() 信号。
如果想让定时器只运⾏⼀次,可以调⽤它的 setSingleShot(bool) ⽅法。
pTimer->setSingleShot(true);
另外⼀种⽅式是使⽤静态函数 QTimer::singleShot():
// 开启定时器,200毫秒后,进⼊槽函数updateCaption()
QTimer::singleShot(200, this, SLOT(updateCaption()));
// 启动⼀个定时器,返回定时器的id
int QObject::startTimer(int interval);
// 移除定时器
void QObject::killTimer(int id);
每隔 interval 的时间,都会进⼊⼀次 QObject::timerEvent(QTimerEvent *event) 事件,通过
QTimerEvent::timerId() 可以获取当前哪个定时器被激活。
//启动定时器
m_timerId = startTimer(1000);
//...
//定时器事件
void TestWidget::timerEvent(QTimerEvent* event)
{
if(event->timerId() == m_timerId)
{
//...
}
}
QTime::currentTime()
将时间转换为字符串:
QString strCurTime = QTime::currentTime().toString("hh:mm:ss");
m_pUdpSocket = new QUDPSocket(this);
//...
QByteArray datagram = "Broadcast message " + QByteArray::number(messageNo);
m_pUdpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 45454);
m_pUdpSocket = new QUdpSocket(this);
m_pUdpSocket->bind(45454, QUdpSocket::SharedAddress);
connect(m_pUdpSocket, &QUdpSocket::readyRead, this, &Receiver::processPendingDatagrams);
void Receiver::processPendingDatagrams()
{
QByteArray datagram;
while(m_pUdpSocket->hasPendingDatagrams())
{
datagram.resize(int(m_pUdpSocket->pendingDatagramSize()));
m_pUdpSocket->readDatagram(datagram.data(), datagram.size());
m_pStatusLabel->setText(tr("Received datagram: \"%1\"").arg(datagram.constData()));
}
}
QThread是Qt线程类,最简单的用法就是子类化QThread,然后重新实现run()方法。
class WorkerThread : public QThread
{
Q_OBJECT
protected:
void run() override
{
QString res;
emit resultReady(result);
}
signals:
void resultReady(const QString& s);
};
void MyObject::startWorkInThread()
{
WorkerThread* workThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::resultReady, this, &MyObject::handleResults);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
workerThread->start();
}
当一个Qt程序启动时,就有一个进程被操作系统创建,同时一个线程也立即运行,这个线程称主线程,也叫GUI线程。
Qt中,所有与界面有关的操作只能在主线程中进行,不能在其他线程中处理界面,否则很可能造成程序崩溃。
Qt4未提供图标库,但是你可以通过使用QGraphicsView自己封装,也可以使用第三方库。Qt5中提供了图表库QCharts。
折线图、饼图、柱状图