跨平台;一定程度上简化了内存回收(对象树 new出来的对象不用写delete)。
对象树:当创建的对象 父窗口是 QObject 或者是QObject派生下来的类,此对象会放到对象树上,当程序执行完毕,树上的内容会从下往上依次释放。
通过QObject::dumpObjectTree()可以在调试输出中打印对象树的内容。
CMake组织Qt工程
在实际的项目开发中,我们会遇到各种各样特殊情况:不同开发者的电脑环境不一样、项目
需要跨平台、项目中使用了大量其它库。如果采用传统的方法,我们需要修改繁杂的工程配置,费
时费力,并且很容易出现问题。
# 指定CMake最小版本
cmake_minimun_required(VERSION 3.10.0)
# 设置工程名称
project(Demo)
# 查找Qt库
if(WIN32)
if(MSVC)
# VS2015及以后默认查找Qt5
if(MSVC_VERSION GREATER_EQUAL 1900)
set(QT_VERSION "5" CACHE STRING "Qt Version")
else()
set(QT_SERSION "4" CACHE STRING "Qt Version")
endif()
else()
set(QT_VERSION "5" CACHE STRING "Qt Version")
endif()
else()
set(QT_VERSION "5" CACHE STRING "Qt Version")
endif()
set_property(CACHE QT_VERSION PROPERTY STRINGS 4 5)
if(QT_VERSION VERSION_GREATER "4")
find_package(Qt5 REQUIRED Core Widgets Xml)
else()
find_package(Qt4 REQUIRED QtCore QtGui QtXml)
#设置自动处理Qt的moc、uic、rcc
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIX ON)
set(CMAKE_AUTORCC ON)
#头文件包含当前目录
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#将自动生成的文件放到单独文件夹
set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP "Generated Files")
#生成可执行程序
add_executable(
${PROJECT_NAME}
TestWidget.h
TestWidget.cpp
TestWidget.ui
main.cpp
)
#链接Qt库
if(Qt5Widgets_FOUND)
target_link_libraries(
${PROJECT_NAME}
Qt5::Core
Qt5::Gui
Qt5::Widgets
)
elseif(Qt4_FOUND)
target_link_library(
${PROJECT_NAME}
Qt4::QtGui
Qt4::QtXml
)
endif()
mian入口函数中 exec()
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
...
return a.exec();
}
a.exec()
程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
动态转换
C++中提供了dynamic_cast()函数,用于动态转换。需要RTTI支持。Qt中也有类似的函数:qobject_cast()。它不需要RTTI支持,执行速度比dynamic_cast()快,并且可以跨动态库边界工作。应尽量使用qobject_cast()。
使用qobject_cast()有两个限制条件:
1.被转换的对象必须直接或间接继承自QObject
2.被转换的对象必须声明QObject宏
中文乱码问题
法一:#pragma execution_character_set("utf-8") //设置执行字符集
法二:QStringLiteral("str")
字符串
C++语言提供了两种字符串的实现:C风格字符串,以\0结尾;std::string。Qt使用QString,可包含\0符号,length()会返回整个字符串的长度,而非到'\0'的长度。
//常用接口
toUpper() //将字符串所有字母转为大写形式
toLower() //将字符串所有字母转为小写形式
//trimmed可去掉字符串首位的空格, simplified 不仅去掉首位的空格,中间连续的空格也用一个空格替换掉
QString str1 = " Are you OK? ";
QString str2 = str1.trimmed(); //str2 == "Are you OK?"
QString str3 = str1.simplified(); //str3 == "Are you OK?"
QString str1 = "Everything is Visual, hello everyone";
QString substr = "eve";
int pos = str.indexOf(str1); //pos == 29
pos = str.indexOf(str, 0, Qt::CaseInsensitive); // pos == 0
pos = str.indexof(str,pos+1, Qt::CaseInsensitive); // pos == 29
pos = str.lastIndexOf(str); //pos == 29
//isNull 判断字符串是否未赋值, isEmpty 判断字符串是否为空
QString str1;
QString str2 = "";
str1.isNull(); // true
str1.isEmpty(); // true
str2.isNull(); // false
str2.isEmpty(); // true
str1.clear();
str1.isNull(); // false
str1.isEmpty(); // true
//判断是否包含子串 可设置大小写敏感 默认敏感
QString str = "Everything is Visual";
QString substr = "Ev";
str.contains(substr); // true
str.startsWith(substr); // true
str.endsWith(substr); // false
QString str = "Everything is Visual";
QString strLeft = str.left(5); // strLeft == "Every"
QString strRight = str.right(6);//strRight == "Visual"
QString strMid = str.mid(5, 2); //strMid == "th"
QString str0("%1和%2").arg("李雷").arg("韩梅梅"); //str0 == “李雷和韩梅梅”
QString str1("%1+%2").arg(1).arg(QString::number(2)); //str1 == "1+2"
QString number0 = QString::number(12.2754, 'g', 4); //12,28
QString number1 = QString("%1 %L2").arg(1234).arg(1234); //1234 1,234
//总宽度7位,小数点后3位,总位数不够以下划线'_'填充
QString number2 = QString("%1").arg(12.34, 7, 'f', 3, '_'); //“_12.340”
//QString 和 std::string 的转换
std::string str1 = "CSDN";
QString str2 = QString::fromStdString(str1);
str1 = str2.toStdString();
//对应的转换宽字符 std::wstring 的函数为 toStdWString 、 fromStdWString 。
//QString 和 char* 的转换
//不含中文时转 char* ,需用到QByteArray类,因为 char* 需要以 \0 结尾,而通过 QString::toLatin1() 转换为QByteArray时会在字符串后面自动加上\0
QString str = "Everything is Visual.";
char* ch = nullptr;
QByteArray ba = str.toLatin1();
ch = ba.data();
//含中文时转 char* ,因为 QString::toLatin1() 只支持拉丁字母,故改用 QString::toLocal8Bit()
//char* 转 QString ,直接用 QString(const char*) 构造函数即可
char* ch = "科技改变生活";
QString str = QString(ch);
通用容器
QVariant可以存储各种数据类型。QVariant内置支持所有QMetaType::Type里声明的类型如:int、QString、QFont等,甚至QList、QMap<QString,QVariant>,用户自定义的数据结构。简而言之,QVariant可以存储任意数据类型。
内置类型
QVariant var0(27); //生成一个包含整形数字27的值
int iNum = var0.toInt(); //iNum == 27
QVariant var1("hello"); //生成一个包含字符串的值
QString str = var1.toString(); //str == "hello"
QVariant var2(Qt::red); //生成一个包含颜色QColor的值
QColor color = var2.value<QColor>(); //color == Qt::red
QVariant var3;
var3.setValue(true);
bool b = var3.toBool(); //b == true;
QList<int> list = {1,2,3};
QVairant var4 = QVairant::fromValue(list);
QList<int> listAnother = var4.value< QList<int> >();
自定义类型
用户自定义的数据结构或类(含默认构造、默认析构、拷贝构造),需要使用Q_DECLARE_METATYPE注册,注册后,可被QVariant处理。通过canConvert可以确认是否转换。包含命名空间的,Q_DECLARE_METATYPE应在命名空间外面。
Q_DECLARE_METATYPE结尾不需要分号。
namespace MyNamespace
{
struct MyStruct
{
int num;
QString id;
};
}
Q_DECLARE_METATYPE(MyNamespace::MyStruct)
using namespace MyNamespace;
MyStruct data, data2;
QVariant var = QVariant::fromValue(data);
QVariant var2;
var2.setValue(data2);
//检查是否可以转换
if(var.canConvert<MyStruct>() && var2.canConverrt<MyStruct>())
{
MyStruct data3 = var.value<MyStruct>(); //data3 == data
MyStruct data4 = var2.value<MyStruct>();//data4 == data2
}
QListWidgetItem中存入数据
class DataForInternal
{
public:
QString strData;
DataForInternal(const QString& str="")
: strData(str)
{}
};
Q_DECLARE_METATYPE(DataForInternal)
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
init();
}
~Widget()
{
delete ui;
}
private Q_SLOTS:
//添加项
void slotAddItem()
{
QString strText = ui->leText->text().trimmed();
QString strData = ui->leInternalData->text().trimmed();
QListWidgetItem *pItem = new QListWidgetItem();
pItem->setText(QString("%1(%2)").arg(strText, strData));
pItem->setData(Qt::UserRole, QVariant::fromValue(DataForInternal(strData)));
ui->listWidget->addItem(pItem);
}
//搜索内部数据
void slotSearchItem()
{
QString strData = ui->leInternalDataForSearch->text().trimmed();
//遍历列表
for (int i = 0; i < ui->listWidget->count(); i++)
{
QListWidgetItem *pItem = ui->listWidget->item(i);
if(!pItem)
{
continue;
}
QVariant var = pItem->data(Qt::UserRole);
DataForInternal data = var.value<DataForInternal>();
bool bMatch = (var.canConvert<DataForInternal>() && data.strData == strData);
pItem->setBackground(bMatch ? Qt::red : Qt::white);
}
}
private:
void init()//初始化
{
connect(ui->btnAdd, &QPushButton::clicked, this, &Widget::slotAddItem);
connect(ui->btnSearch, &QPushButton::clicked, this, &Widget::slotSearchItem);
}
private:
Ui::Widget *ui;
};
几种简单的控件
QWidget
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;
//设置窗口尺寸
resize(600, 400);
//设置固定尺寸
setFixedSize(600, 400);
//...
QMainWndow
MainWindow::MainWindow(QWidget* parent)
:QMainWindow(parent)
{
// 重置窗口尺寸
resize(600, 400);
// 1.菜单栏 只有一个
QMenuBar* bar = menuBar();
// 菜单栏设置到窗口中
this->setMenuBar(bar);
// 添加菜单
QMenu* fileMenu = bar->addMenu("文件");
QMenu* editMenu = bar->addMenu("编辑");
// 添加菜单项
QAction* newAction = fileMenu->addAction("新建");
// 添加分割线
fileMenu->addSeparator();
QAction* openAction = fileMenu->addAction("打开");
// 菜单项中添加子菜单
QMenu* subMenu = new QMenu;
subMenu->addAction("子菜单1");
subMenu->addAction("子菜单2");
newAction->setMenu(subMenu);
// 2.工具栏 可以多个
QToolBar* toolBar = new QToolBar(this);
// 将工具栏设置到窗口中
addToolBar(Qt::LeftToolBarArea, toolBar);
// 设置只允许左右停靠
toolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
// 设置浮动
toolBar->setFloatable(false);
// 设置移动
toolBar->setMovable(false);
// 添加菜单项
toolBar->addAction(newAction);
// 添加分割线
toolBar->addSepartor();
toolBar->addAction(openAction);
// 3.状态栏 只能一个
QStatusBar* sBar = statusBar();
setStatusBar(sBar);
QLabel* label0 = new QLabel("左侧信息", this);
sBar->addWidget(label0);
QLabel* label1 = new QLabel("右侧信息", this);
sBar->addPermanentWidget(label1);
QLabel* label2 = new QLabel("左侧信息2", this);
sBar->insertWidget(0, label2);
// 4.铆接部件/浮动窗口 可以多个
QDockWidget* dock = new QDockWidget("标题", this);
addDockWidget(Qt::BottomDockWidgetArea, dock);
// 设置停靠
dock->setAlowedArea(Qt::BottomDockWidgetArea);
// 5.中心部件/核心部件 只有一个
QTextEdit* edit = new QTextEdit(this);
setCenterWidget(edit);
}
QDialag
// 点击按钮,弹出对话框
connect(ui->actionNew, &QAction::triggered,[=](){
// 对话框分为
// 模态对话框 不可以对其他窗口进行操作 阻塞
// 非模态对话框 可以对其他窗口进行操作 不会阻塞
// 创建模态对话框
QDialog dlg(this);
dlg.resize(120, 30);
dlg.exec();
// 创建非模态对话框
QDialog* dlg = new QDialog(this);
dlg->resize(120, 30);
dlg->show();
//设置属性 55号
dlg->setAttribute(Qt::WA_DeleteOnClose);
});
QLabel
最简单的文本显示控件
#include <QLabel>
//...
QLabel* pLabel = new QLabel(this); //创建一个QLabel对象
pLabel->setText("Hello,Qt");
pLabel->setAlignment(Qt::AlignRight); //设置文字居右对齐
pLabel->setGeometry(10, 10, 200, 20);
pLabel->setAutoFillBackground(true);
QPalette palette = this->palette(); //取色器
palette.setColor(QPalette::Background, color); //QPalette::Text
pLabel->setPalette(palette);
注:创建QLabel对象时,需指定父对象。
QPushButton
#include <QPushButton>
//...
QPushButton* pbtnOk = new QPushButton(this); //创建按钮
pbtnOk->setText("Ok");
QLineEdit
单行文本输入框,用于文本的输入或编辑。
#include <QLineEdit>
//...
QLineEdit* pLineEdit = new QLineEdit(this); //创建对象
//设置占位文字,即输入框为空时显示的提示文字
pLineEdit->setPlaceholderText("请输入姓名");
//设置最大输入长度
pLineEdit->maxLength(5);
QMessageBox
弹出式的消息提示框。一般调用QMessageBox类的静态函数。
// 错误提示
QMessage::critical(this, "critical", "错误!");
// 信息提示
QMessageBox::information(this, "info", "信息提示!");
// 询问提示
if(QMessageBox::Save == QMessageBox::question(this, "ques", "询问!",
QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Cancel))
{
qDebug() << "选择的是保存";
}
else
{
qDebug() << "选择的是取消";
}
// 警告提示
int res = QMessageBox::warning(this, "删除", "确定删除?");
if(res == QMessageBox::Ok)
{
//...
}
else
{
//...
}
其他对话框
// 颜色对话框
QColor color = QColorDialog::getColor(QColor(255, 0, 0));
qDebug() << color.red() << color.green() << color.blue();
// 字体对话框
bool ok;
QFont font = QFontDialog::getFont("华文彩云", 36);
qDebug() << "字体:" << font.family() << "字号:" << font.pointSize()
<< "加粗:" << font.bold() << "倾斜:" << font.italic();
// 文件对话框
QString str = QFileDialog::getOpenFileName(this, "打开文件", "filePath", "(*.txt *.doc)");
qDebug() << str;
常用控件
QRadioButton 单选按钮(配合Group Box)
QCheckBox 复选按钮
半选状态属性设置
监听某选项是否被选中信号:QCheckBox::stateChanged(int state);
快捷键 QShortcut
信号槽
很多其他语言或框架是通过回调实现的,而Qt提供了一种特殊的方法——信号槽。
该机制类似于设计模式中的“观察者模式”,发布者发布信息,订阅者接收信息。优点:松散耦合。
信号槽连接(Qt4风格)语法如下:
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
//点击按钮,关闭窗口[cilcked()是类QPushBUtton内置的信号,close()是类QWidget内置的槽函数]
connect(pBtnClose, SIGNAL(clicked()), pWgt, SLOT(close()));
Qt4版本的信号和槽:
优点:参数直观,写法简单
缺点:编译器不检查参数类型
自定义信号槽
自定义信号使用宏signals或Q_SIGNALS,自定义槽函数使用宏slots或Q_SLOTS。(Qt定义Q_SIGNALS、Q_SLOTS、Q_EMIT关键字是为了避免冲突,因为signals、slots、emit可能会在第三方库,如boost中使用)如:
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。
对于带有参数的信号槽连接,跨线程的信号槽,对于自定义的参数类型,需要使用qRegisterMetaType对该类型进行注册。
参数说明
信号和槽可重载。重载后,需要函数指针指向函数地址
connect(m_leftWgtBtnBox, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), [=](int id) {Slot_leftWidgetButtonClicked(id); });
class Teacher : public QObject
{
Q_OBJECT
...
signals:
void signalHungry();
//重载
void signalHungry(QString foodName);
};
class Student : public QObject
{
...
public slots:
void slotTreat()
{qDebug() << "请老师吃饭";}
void slotTreat(QString foodName)
{
//QString 转char*
//先调用toUtf8()转为QByteArray,再调用data()转char*
qDebug() << "请老师吃饭,老师要吃 :" <<foodName.toUtf8().data();
}
};
class Widget : QWidget
{
Widget(QWidget* parent)
:QWidget(parent)
{
this->m_teacher = new Teacher(this);
this->m_student = new Student(this);
//信号和槽发生重载后,需要函数指针指向函数地址
void(Teacher::* teacherSignal)(QString) = &Teacher::signalHungry;
void(Student::* studentSlot)(QString) = &Student::slotTreat;
connect(this->m_teacher, teacherSignal, this->student, studentSlot);
}
...
void classISOver()
{
emit this->m_teacher->signalHungry("炸鸡");
}
Teacher* m_teacher;
Student* m_student;
};
一个信号可以连接多个槽
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);
connect(sender, SIGNAL(signal), receiver, SLOT(slot), Qt::UniqueConnection);
信号槽重复连接,加上 Qt::UniqueConnection,当发出信号时,槽函数只会调⽤⼀次;否则会多次调⽤槽函数。
Qt connect第5个参数(一般不填,为默认值)
- Qt::AutoConnection:为默认值。使用此值连接类型会在信号发送时决定。若接收者与发送者在同一个线程,则自动使用Qt::DirectConnection类型。若接收者与发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
- Qt::DirectConnection:槽函数会在信号发送时直接被调用。槽函数与信号发送者在同一线程。效果就像是直接在信号发送位置调用了槽函数,像函数调用,同步执行。emit语句后面的代码将在与信号关联的所有槽函数执行完毕后才被执行。
- Qt::QueuedConnection:信号发出后,信号会暂时被放在一个消息队列中。需等到接收对象所属线程的事件循环取得控制权时才获取该信号,再执行与信号关联的槽函数。这种方式既可在同一线程内传递消息,也可跨线程操作。emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕。
- Qt::BlockingQueuedConnection:槽函数调用时机与Qt::QueuedConnection一致,不过发完信号后发送者所在线程阻塞,直到槽函数运行完。且接收者与发送者绝对不能在一个线程,否则会造成死锁。用在多线程同步时的场合。
- Qt::UniqueConnection:这个标志可通过按位或|与以上四个结合在一起使用。当设置此标志时,当某个信号与槽已经连接时,再进行重复的连接会失败。即就是为避免重复连接。
一般Qt connect的第5个参数会在多线程中运用到。注意:
QThread是管理线程的,QThread对象所依附的线程和所管理的线程并非一个概念。QThread所依附的线程,就是创建QThread对象的线程;QThread所管理的线程,就是run启动的线程,即新建线程。如,QThread对象依附在主线程上,QTread对象的slot函数会在主线程中执行,而非次线程。除非QThread对象依附到次线程中(通过moveToThread)
移除连接
disconnect(lcd, SIGNAL(overflow()), this, SLOT(handleMathError()));
// 取消对象所有信号的连接
disconnect(myObject, 0, 0, 0);
myObject->disconnect();
//取消指定信号的所有连接
disconnect(myObject, SIGNAL(mySignal()), 0, 0);
myObject->disconnect(SIGNAL(mySignal()));
//取消指定接收者的连接
disconnect(myObject, 0, myReceiver, 0);
myObject->disconnect(myReceiver);
阻塞信号发出
通过blockSignals()可以阻塞信号的发出。
btn->blockSignals(true); //阻塞信号
btn->blockSignals(flase); //解除阻塞
信号来源
若想在槽函数中获取发出信号的对象,可使用函数QObject::sender()
//获取触发信号的菜单,并设置播放速度
QAction* pAct = qobject_cast<QAction*>(sender());
double speed = pAct->data().toDouble();
总结
作业1
现在,我们将投⼊实战项⽬——设计⼀个思维导图软件,每天完成其中⼀部分,最终效果如下:

⾸先需要实现软件的登录功能,设计⼀个如下图所示的登录窗⼝:

⽤户输⼊⽤户名、密码之后,点击登录按钮。
登录成功(即⽤户名、密码完全匹配)则显示关闭登录窗⼝,并显示主窗⼝,在窗⼝右上⻆显示⽤户昵称;
登录失败则弹出错误提示。

- 密码输⼊框不显示明⽂密码
- 主窗⼝⽆边框,并在右上⻆添加按钮,实现窗⼝的“最⼩化、最⼤化、恢复、关闭”等功能
帮助与提示
布局管理
布局是为了让子控件能够自动调整大小和位置,从而保证能够充分利用空间。作用:
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);
pHBoxLayout->addWidget(pButtonTwo, 1, Qt::AlignLeft | Qt::AlignBottom);
pHBoxLayout->addWidget(pButtonThree,2);
pHBoxLayout->addWidget(pButtonFour, 1, Qt::AlignHCenter | Qt::AlignBottom);
pHBoxLayout->addWidget(pButtonFive, 0, Qt::AlignLeft | Qt::AlignTop);
分割器 QSplitter
与QBoxLayout相似,可以完成布局管理器的功能。但是包含在其里面的部件,在运行时可根据鼠标拖拽动态调整其分割大小。
//主分割器
QSplitter *pMainSplitter = new QSplitter(Qt::Horizontal);
QTextEdit *pLeftEdit = new QTextEdit("左部件");
pLeftEdit->setAlignment(Qt::AlignCenter);
//创建右侧分割器
QSplitter *pRightSplitter = new QSplitter(Qt::Vertical, this);
// 设置拖拽分割条时是否实时更新。若为true,则实时更新,否则拖拽时显示一条虚线
pRightSplitter->setOpaqueResize(false);
QTextEdit *pUpEdit = new QTextEdit("上部件");
pUpEdit->setAlignment(Qt::AlignCenter);
QTextEdit *pMiddleEdit = new QTextEdit("中间部件");
pMiddleEdit->setAlignment(Qt::AlignCenter);
QTextEdit *pBottomEdit = new QTextEdit("底部部件");
pBottomEdit->setAlignment(Qt::AlignCenter);
//向右侧分割器添加控件
pRightSplitter->addWidget(pUpEdit);
pRightSplitter->addWidget(pMiddleEdit);
pRightSplitter->addWidget(pBottomEdit);
//向主分割器中添加控件
pMainSplitter->addWidget(pLeftEdit);
pMainSplitter->addWidget(pRightSplitter);
// 设置控件是否可伸缩。
// 参数1 指定控件序号 参数2 大于0时,表示控件可伸缩。小于0则不可伸缩
pMainSplitter->setStretchFactor(0, 1);
//主布局
QHBoxLayout *pHBoxLayout = new QHBoxLayout;
pHBoxLayout->addWidget(pMainSplitter);
this->setLayout(pHBoxLayout)
基本控件
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文件
作业2
需求
-
引⼊思维导图库 MindMap ,在 VS 的⼯程属性中配置好相关路径和⽂件名
-
查看思维导图库提供的相关接⼝,将思维导图加到主界⾯下⽅,并使⽤布局调整标题栏和主界⾯的结构
-
调⽤思维导图库的接⼝,使⽤模拟数据初始化思维导图:

帮助与提示
从思维导图的接⼝SimDataManager::getSimData获取模拟数据,它的返回值是⼀个结构体
StMindData
列表、树、表格控件
QListWidget
QListWidget类的继承关系如下:
QListWidget -> QListView ->QAbstractItemView ->QAbstractScrollArea -> QFrame ->QWidget.
每个列表项中可以包含文字、图标等内容。实际开发中,我们还可以将指定的窗口或者控件放置到列表项中显示。如QWidget窗口、QLabel文本框、QPushButton按钮、QLineEdit输入框等。
//创建QListWidget
QListWidget *pListWidget = new QListWidget(this);
//添加项
//QListWidgetItem* item = new QListWidgetItem("#########");
//item->setTextAligment(Qt::AlignHCenter | Qt::AlignVCenter); //设置文本对齐方式
//pListWidget->addItem(item);
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();
//将指定Widget窗口添加到item列表项中
void setItemWidget(QListWidgetItem* item, QWidget* wgt);
此外,QListWidget还提供很多有用的信号和接口来处理用户操作,如currentItemChanged()信号会在当前选中的Item发生改变时发出。
QTreeWidget
类的继承关系如下:
QTreeWidget->QTreeView->QAbstractItemView->QAbstractScrollArea->QFrame->QWidget
//创建树
QTreeWidget* pTreeWgt = new QTreeWidget(this);
//设置头标签
pTreeWgt->setHeaderLabels(QStringList() << "英雄" << "英雄介绍");
//item创建
QTreeWidgetItem* liItem = new QTreeWidgetItem(QStringList() << "力量");
//添加顶层级别item
pTreeWgt->addTopLevelItem(liItem);
QStringList heroL1, heroL2;
heroL1 << "刚被猪" << "xxxxxxxx";
heroL2 << "船长" << "xxxxxxxxxxxxx";
//创建子Item,挂载到顶层item上
QTreeWidgetItem* l1 = new QTreeWidgetItem(heroL1);
liItem->addChild(l1);
QTreeWidgetItem* l2 = new QTreeWidgetItem(heroL2);
liItem->addChild(l2);
//表头是否隐藏,此处隐藏标题
pTreeWgt->setHeaderHidden(true);
//树形结构构造后默认是折叠的,可将设置项全部展开
pTreeWgt->expandAll();
//给每个项添加图标
pFirstRootItem->setIcon(0, QIcon(":/myIcon.png"));
//添加勾选框(树形控件经常需要展示勾选状态)
pFirstRootItem->setCheckState(0, Qt::Unchecked);
当树中的项的勾选状态发生变化时,树形控件会发射itemChanged信号,我们可以连接该信号到槽函数进行一些处理。如使其所有子节点保持和该节点一样的勾选状态。
connect(pTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotProcessItem(QTreeWidgetItem*,int);
void slotProcessItem(QTreeWidgetItem* item, int column)
{
if(!item) return;
//获取当前勾选状态
Qt::CheckState state = item->checkState(column);
//屏蔽树形信号
pTreeWidget->blockSignals(true);
//遍历子节点
QTreeWidgetItemIterator it(item);
while (*it)
{
//设置勾选状态
(*it)->setCheckState(0, state);
++it;
}
//解除信号屏蔽
pTreeWidget->blockSignals(true);
}
QTableWidget
//创建表格
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);
//设置正文
pTableWgt->setItem(0,0,new QTableWidgetItem("xx"));
//点击添加赵云,实现添加
connect(ui->btn_add, &QPushButton::cilcked, [=](){
bool isEmpty = pTableWgt->findItems("赵云", Qt::MatchExactly).isEmpty();
if(!isEmpty)
{
QMessageBox::warning(this, "警告", "已有赵云,添加失败!");
}
else
{
pTableWgt->insertRow(0);
pTableWgt->setItem(0,0,new QtableWidgetItem(QString("赵云")));
pTableWgt->setItem(0,1,new QTableWidgetItem(QString("男")));
pTableWgt->setItem(0,2,new QTableWidgetItem(QString::number(30)));
}
});
//点击删除赵云,实现删除
connect(ui->btn_del, &QPushButton::cilcked, [=](){
bool isEmpty = pTableWgt->findItems("赵云", Qt::MatchExactly).isEmpty();
if(isEmpty)
{
QMessageBox::warning(this, "警告", "没有赵云,删除失败!");
}
else
{
int rowNum = pTableWgt->findItems("赵云", Qt::MatchExactly).first()->row(0);
pTableWgt->removeRow(rowNum);
}
});
其他常用控件
// stacked widget
//设置默认选中第一个
ui->stackedWidget->setCurrentIndex(0);
connect(ui->btn_scroll, &QPushButton::clicked,[=](){
// 设置当前索引
ui->stackedWidget->setCurrentIndex(0);
});
//combo box下拉框
//点击保时捷,定位到相应选项
connect(ui->btn_select,&QPushButton::clicked,[=](){
//ui->comboBox->setCurrentIndex(2);
ui->comboBOx->setCurrentText("保时捷");
});
//利用QLabel显示图片
QPixmap pix;
pix.load(":/Image/butterFly.png");
ui->label_img->setPixmap(pix);
ui->label_img->setFixedSize(pix.width(), pix.height());
//利用Qlabel显示动图
QMovie* movie = new QMovie(":/Image/mario.gif");
ui->label_movie->setMovie(movie);
movie->start();
movie->setSpeed(300);
connect(movie, &QMovie::frameChanged,[=](int frameId){
if(frameId == movie->frameCount()-1)
movie->stop();
});
注:所有这些控件,可直接通过代码编写,也可在界面编辑数据、查看属性。
自定义控件
即通过封装的方式,将已有的简单控件组装成复杂的控件,实现更丰富的界面效果。
封装
提升
复用
Qt程序编译流程
在构建 Qt 程序的过程中,打开编译器的编译输出窗⼝,可以看到以下内容:
uic TestProject.ui
rcc TestProject.qrc
moc TestProject.h
这 3 条命令分别⽤于解析 ui ⽂件、qrc ⽂件,以及头⽂件。
在 Qt 的安装⽬录下,可以找到 uic、rcc、moc 这 3 个⼯具。
uic 。User Interface Compiler,将界⾯⽂件(.ui)⽣成 C++ 代码。
rcc 。Resource Compiler,将 qrc ⽂件⽣成 C++ 代码。
moc 。Meta-Object Compiler,moc ⼯具读取头⽂件,如果找到⼀个或多个包含 Q_OBJECT 宏的类,它 将为这些类⽣成⼀个包含元对象(meta-object)代码的 C++ 源⽂件。元对象代码⽤于信号槽机制、运⾏时类型识别、动态属性等⽅⾯。(这就是⾃定义信号槽必须添加 Q_OBJECT 宏的原因)
结果:
uic 根据 TestProject.ui ⽣成 ui_TestProject.h ⽂件;
rcc 根据 TestProject.qrc ⽣成 qrc_TestProject.cpp ⽂件;
moc 根据 TestProject.h ⽣成 moc_TestProject.cpp ⽂件。
这些⽣成后的⽂件,都可以在⼯程的⽣成⽬录下找到。
全部解析成 C++ 编译器可识别的⽂件之后,接下来便按照”预处理-编译-汇编-链接“的过程进⾏编译,最终⽣成可执⾏⽂件。
ui文件
ui界面文件,使用Qt Designer打开,编辑控件和布局。本质是xml文件。其生成的代码也是在窗口中创建控件、调整位置、设置布局。
对于动态变化较大的控件,如通过配置文件创建不同数量的按钮,只用Designer是无法实现的,仍需要通过编码来做。
qrc文件
qrc资源文件,用于添加图片、样式表、配置文本文件等资源。图片添加完成后,可获得资源路径,可直接在程序中使用。qrc本质也是一个xml文件。
qrc ⽂件中添加的资源是写⼊到程序中的(可执⾏⽂件或者库⽂件中),所以在程序打包时不⽤考虑资源的路径问题。如果使⽤相对路径(如 images/bg-blue.png)或者绝对路径(如E:/images/bg-blue.png),那 么打包时需要将资源放到合适的路径下,很容易出现路径错误找不到图⽚等问题。建议在开发中使⽤ qrc ⽂件来管理资源。
作业3
在上⼀节我们在窗⼝中添加了思维导图,并使⽤模拟数据进⾏初始化。
下⾯,我们将使⽤QTreeWidget展示思维导图的数据。
创建⼀个界⾯,上⽅是搜索框,下⽅是⼀个QTreeWidget。
获取思维导图的数据,并初始化QTreeWidget。在上⽅搜索框中输⼊⽂字,⽤于搜索匹配的树节点。

接下来,将该界⾯放到主界⾯左侧,叠加在思维导图上⾯,参考下图(暂不考虑样式)

帮助与提示
- 思维导图每⼀项与树的每个节点对应,树节点中需保存名称、类型(根节点或叶⼦节点)、对应思维导图那⼀项的指针
- 树的搜索:匹配到的节点显示,不匹配的节点隐藏;搜索框为空,则显示所有节点;
- 树的层级不⼀定只有3层
容器
容器的遍历
//以 QList 为例,Qt 容器的遍历有很多种写法。
QList<QString> list;
list << "A" << "B" << "C" << "D";
//Java ⻛格的迭代器
QListIterator<QString> i(list);
while (i.hasNext())
{
qDebug() << i.next();
}
//STL ⻛格的迭代器
QList<QString>::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)用来设计界面样式和元素布局。
引入样式
通常采⽤ 2 种⽅式引⼊样式:
通过 Qt Designer 在 ui ⽂件中设置样式
读取⽂件设置样式 ⽐如:先将样式写进⽂件 style.qss 中,然后读取⽂件内容
QFile file("style.qss");
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
this->setStyleSheet(file.readAll());
}
QApplication::setStyleSheet() 为整个应⽤程序设置样式表:
qApp->setStyleSheet("...");
使⽤ QWidget::setStyleSheet() 给指定的 widget 及其⼦ widget 设置样式:
pMainWgt->setStyleSheet("...");
样式表语法
语法规则
样式表由多组“规则”组成,每个规则由“选择器“(selector)、“属性”(property)和”值“(value)组成。如:QPushButton{color: red}
选择器:QPushButton,多个选择器用逗号隔开
属性:color,多个属性时用分号隔开
值:red,多个关键字时大多以空格隔开
选择器类型
最常用的选择器是“类型选择器”和“ID选择器”。
ui文件中的控件在编译时经过uic处理,会将其对象名(object name)设置为Qt Designer中指定的名称;若是在代码中创建的控件,需调用setObjectName()手动指定其对象名(object name)
QLabel* pNameLbl = new QLabel;
pNameLbl->setObjectName("nameLabel");
然后再通过“ID选择器”设置样式:
#nameLabel {border: 1px soild red;}
选择器类型:
通用选择器 * 匹配所有控件
类型选择器 QPushButton 匹配给定类型控件,包括子类
类选择器 .QPushButton 匹配给定类型控件,不包括子类
属性选择器 QPushButton[flat=“false”] 匹配给定类型控件中符合[属性]的控件
ID选择器 QPushButton#closeBtn 匹配给定类型,且对象名为closeBtn的控件
子对象选择器 QDialog>QPushButton 匹配给定类型的直接子控件
子孙对象选择器 QDialog QPushButton 匹配给定类型的子孙控件
子控件选择器 QComboBox::drop-down 复杂对象的子控件
伪状态选择器 QPushButton:hover 控件的特定状态下的样式
子控件
有的复杂控件须通过获取其⼦控件来设置样式,⽐如 QComboBox 的下拉按钮,QSpinBox 向上和向下的箭头。通过两个冒号(::)来获取⼦控件
QComboBox::drop-down {image: url(dropdown.png)}
常用辅助控制器:
::indicator 单选框、复选框、可选菜单项或可选群组项的指示器
::menu-indicator 按钮的菜单指示器
::item 菜单、菜单栏或状态栏项
::up-button 微调框或滚动条的向下按钮
::down-button 微调框或滚动条的向上按钮
::up-arrow 微调框、滚动条或标题视图的向上按钮
::down-arrow 微调框、滚动条或标题视图的向下按钮
::drop-down 组合框的下拉箭头
::title 群组框的标题
伪状态
QPushButton { color: red; }
QPushButton:hover { color: green; }
QPushButton:pressed { color: #ffffff; }
伪状态还可以多个连用,达到逻辑与效果。例如,当鼠标悬停在一个被选中的QCheckBox部件上时才应用规则,那么这个规则可以写为:
QCheckBox: hover:checked{color:white}
如果有需要,也可以使用逗号来表示逻辑或操作,例如:
QCheckBox : hover,QCheckBox:checked{color :white)
常用伪状态选择器:
状态 描述
:disabled 控件禁用
:enabled 控件启用
:focus 控件获取输入焦点
:hover 鼠标在控件上悬停
:pressed 鼠标按下
:checked 控件被选中
:unchecked 控件没有选中
:indeterminate 控件部分被选中
:open 控件打开
:closed 控件关闭
:on 控件可以切换,且处于on状态
:off 控件可以切换,且处于off状态
! 对以上状态的否定
这⾥: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 }
层叠
样式表可以设置在QApplication上、父部件或者子部件上。部件有效的样式表是通过部件祖先的样式表和QApplication上的样式表合并得到。发生冲突时,部件自己的样式表优先于任何继承的样式表,同理,父部件的样式表优先于祖先的样式表。
继承
使用Qt样式表时,部件并不会自动从父部件继承字体和颜色设置。如,一个QPushButton包含在一个QGroupBox设置样式表。
qApp ->setStyleSheet("QGroupBox {color: red;}");
但没有对QPushButton设置样式表。此时,QPushButton会使用系统颜色,而不会继承QGroupBox的颜色。若想要QGroupBox的颜色设置到子部件上,可以:
aApp ->setStyleSheet( "QGroupBox,QGroupBox * { color: red;}");
设置QObject属性
任何可设计的Q_PROPERTY都可以使用“qproperty-属性名称”语法来设置样式表。如,
MyLabel{ qproperty- pixmap:url(pixmap. png);}
MyGroupBox{ qproperty- titleColor: rgb(100,200,100);}
QPushButton{ qaproperty- iconSize: 20px 20px;}
Demo:
/*style.qss*/
/* 设置Q_PROPERTY定义的属性样式 */
#Widget[checked = true] {
background-color: rgb(0, 0, 0);
}
/* 设置动态属性样式 */
#Widget[property1 = true] {
background-color: rgb(255, 0, 0);
}
/* 通过Qss设置Q_PROPERTY定义的属性的值 */
#Widget {
qproperty-BgColor: rgb(0, 0, 255);
qproperty-age: age3; /* 通过Q_ENUM注册的枚举修改自定义属性值*/
}
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool checked READ isChecked WRITE setChecked)
Q_PROPERTY(QColor BgColor READ isBgColor WRITE setBgColor)
Q_PROPERTY(AgeEnum age READ age WRITE setAge) // 想要通过Q_ENUM注册的枚举修改属性值,属性的类型就需要时【枚举的类型】,而不能是其它类型,例如int
public:
Widget(QWidget *parent = nullptr);
~Widget();
enum AgeEnum {
age1 = 10,
age2 = 20,
age3 = 30
};
Q_ENUM(AgeEnum) // 向元对象系统注册枚举类型(可以使用Q_ENUM或者Q_ENUMS,不过后者已经过时)
bool isChecked() const;
void setChecked(bool value);
QColor isBgColor() const;
void setBgColor(QColor color);
AgeEnum age() const;
void setAge(AgeEnum value);
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
void on_pushButton_3_clicked();
private:
void initStyle();
private:
Ui::Widget *ui;
bool m_checked = false;
QColor m_bgColor = QColor(255, 255, 255);
AgeEnum m_age;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QStyle>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle(QString("Qss属性功能 - V%1").arg(APP_VERSION));
initStyle();
qDebug() << "在构造函数中获取属性值:" << m_bgColor.name(); // 无法获取到qss修改后的属性值
}
Widget::~Widget()
{
delete ui;
}
bool Widget::isChecked() const
{
return m_checked;
}
void Widget::setChecked(bool value)
{
m_checked = value;
}
QColor Widget::isBgColor() const
{
return m_bgColor;
}
void Widget::setBgColor(QColor color)
{
m_bgColor = color;
}
Widget::AgeEnum Widget::age() const
{
return m_age;
}
void Widget::setAge(AgeEnum value)
{
m_age = value;
}
/**
* @brief 加载qss文件
*/
void Widget::initStyle()
{
QString strFile = qApp->applicationDirPath() + "/style.css"; // 这里我没有使用资源文件,而是把样式表文件放在当前路径下,便于随时更换
QFile file(strFile);
if(file.open(QIODevice::ReadOnly))
{
QTextStream stream(&file);
QString strQss;
while (!stream.atEnd())
{
strQss.append(stream.readLine());
}
qApp->setStyleSheet(strQss); // 设置整个程序的样式表而不是当前窗口
}
else
{
qWarning() << "打开qss文件失败!";
}
}
/**
* @brief 通过Q_PROPERTY定义的属性更新Qss样式
* 设置属性的方式有两种
* 方式一:setChecked
* 方式二:setProperty("checked", value) : 设置成功返回true,否则返回false
*/
void Widget::on_pushButton_clicked()
{
//this->setProperty("checked", true);
this->setChecked(!this->isChecked()); // 更改控件的属性 【Q_PROPERTY】
this->style()->polish(this); // 属性值更改后重新初始化给定控件的样式。
}
/**
* @brief 通过动态属性的方式更新QSS样式
* 如果没有通过Q_PROPERTY定义属性,使用setProperty("property1", value)
* 设置后会将property1添加为动态属性,并且返回false,
* 效果和使用Q_PROPERTY定义的属性类似
*/
void Widget::on_pushButton_2_clicked()
{
static bool value = true;
qDebug() << this->setProperty("property1", value); // 设置动态属性 false
value = !value;
this->style()->polish(this); // 属性值更改后重新初始化给定控件的样式。
}
/**
* @brief 在Qss通过qproperty-属性 的方式修改属性的值,qproperty 语法只在程序启动显示控件是生效一次
* 在构造函数中由于控件还没有开始显示,所以qproperty没生效,是无法获取修改后的属性值的,在窗口显示后就可以获取到属性值
* 注意:虽然主要继承于QObject的类都可以通过Q_PROPERTY定义属性,但是只有继承于QWidget的类定义的属性可以通过Qss修改,
* 因为QObject不包含QStyle
*/
void Widget::on_pushButton_3_clicked()
{
qDebug() << "程序启动后获取属性值:" << m_bgColor.name();
qDebug() <<"Qss设置的属性值:" << m_age;
}
盒子模型
使用样式表时,每个widget都被当做有4个同心矩形的盒子:margin(外边距,外边距是透明的)、border(边框)、padding(内边距,外边距是透明的)、content(内容)。
QLabel {
border: 5px solid green;
margin: 10px 30px 20px 40px;
padding: 20px 30px 40px 10px;
}
即外边距上右下左的顺序,分别为 10px、30px、20px、40px。 边框为 5px。
内边距上右下左顺序,分别为 20px、30px、40px、10px。
示例 2:设置按钮的背景图⽚
#pushButton {
border-image: url(bg.png) 4px 6px 4px 6px;
border-width: 4px 6px 4px 6px;
}
后⾯ 4 个数值是 border 的值,顺序是上右下左,
注意:如果 border-image 后⾯有这⼏个值,那么必须要同时写上border-width!!!并且值要⼀⼀对应。
常⽤控件样式
控件示例
QLabel
设置字体样式
QLabel{
/*分开设置*/
/*font-family:”楷体”;
font-size: 20px;
font-style: italic;
font-weight:bold ; */
/*快捷设置*/
font:bold italic 18px "微软雅黑";
color:cornflowerblue;
}
px 像素 italic 倾斜 normal 不倾斜 bold 加粗 normal 不加粗
font 同时设置字体style weight size family的样式。但是style和weight必须出现在开头,size和family在后面,且size必须在family之前,否则样式不生效。Font中不能设置颜色。
color 可使用十六进制表示颜色,也可使用某些特殊的字体颜色:red green blue等,或者使用rgb(r,g,b)和rgba(r,g,b,a)来设置。其中 r、g、b、a 值为0~255,如果想不显示颜色可以设置值为透明 transparent。
文字位置
QLabel{
padding-left: 10px;
padding-top: 8px;
padding-right: 7px;
padding-bottom: 9px;
}
一般 padding-left 相当于 x 坐标,padding-top 相当于 y 坐标
边框样式
QLabel
{
/*分开设置*/
border-style: solid;
border-width: 2px;
border-color:darkgoldenrod;
/*快捷设置*/
border:2px solid red;
}
solid 为实线, dashed 为虚线, dotted 为点线, none 为不显示(如果不设置 border-style 的话,默认会设置为 none)
border 为同时设置 border 的 width style color 属性,但值的顺序必须是按照 width style color 来写,不然不会生效!
单独设置某条边框的样式
QLabel
{
border-left: 2px solid red;
border-top: 2px solid black;
border-right: 2px solid blue;
border-bottom-color: transparent; /*下边框透明,不显示*/
}
设置边框半径(圆角)
QLabel
{
border-left: 2px solid red;
border-top: 2px solid black;
border-right: 2px solid blue;
border-bottom: 2px solid yellow;
border-top-left-radius: 20px;
border-top-right-radius: 15px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 5px;
/*border-radius: 20px;*/
}
border-radius 为设置所有边框圆角半径,单位为 px 像素,通过圆角半径可以实现圆形的 Label
背景样式
QLabel
{
background-color: #2E3648;
background-image: url("./image.png");
background-repeat: no-repeat;
background-position: left center;
/*background: url("./image.png") no-repeat left center #2E3648;*/
}
background-repeat 为设置背景图是否重复填充背景,如果背景图片尺寸小于背景实际大小的话,默认会自动重复填充图片,可以设置为 no-repeat 不重复,repeat-x 在x轴重复,repeat-y 在y轴重复。
background-position 为设置背景图片显示位置,只支持 left right top bottom center;值 left right center为设置水平位置,值 top bottom center 为设置垂直位置。
background 为设置背景的所有属性,color image repeat position 这些属性值出现的顺序可以任意。
QPushButton
QPushButton
{
/*1*/
border:none; /*去掉边框*/
border-radius:10px;
/*2,添加图片*/
background-image: url(:/images/quit.png);
background-repeat:none;
background-position:center;
/*3,把图片作为边框,会自动铺满背景*/
border-image: url(:/images/quit.png);
}
QPushButton:hover
{
background-color:rgba(102,205,227,255);
}
QPushButton:pressed
{
/*按键按下瞬间显示*/
background-color:rgb(48,188,218);
}
QPushButton:checked
{
/*按键按下显示*/
background-color:rgb(48,188,218);
}
QCheckBox QRadioButton
QCheckBox
{
color:red;
}
QCheckBox::indicator
{
width:16px;
height:16px;
border-image: url(:/images/checkbox-unchecked.png);
border-radius:5px;
}
QCheckBox::indicator:checked
{
border-image: url(:/images/checkbox-checked.png);
}
QCheckBox::indicator:unchecked:hover
{
border-image: url(:/images/checkbox-unchecked-hover.png);
}
QCheckBox::indicator:checked:hover
{
border-image: url(:/images/checkbox-checked-hover.png);
}
QGroupBox
QGroupBox {
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #E0E0E0, stop: 1 #EEEEEE);
border: 2px solid gray;
border-radius: 5px;
margin-top: 10px; /* leave space at the top for the title */
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top center; /* position at the top center */
padding: 2px 3px;
color: white;
margin-top: 2px;
background-color: gray;
border-radius: 3px;
spacing: 5px;
}
QGroupBox::indicator {
width: 13px;
height: 13px;
border: 1px solid black;
background: white;
}
QGroupBox::indicator:checked {
background: yellow;
}
QComboBox
QComboBox {
color: black;
border:1px solid black;
border-radius:5px;
padding: 1px 1px 1px 1px; /*不加这个圆角会有缺失*/
}
QComboBox::drop-down
{
width:25px;
border-image: url(:/images/comboBox/drop-down.png);
}
QComboBox::drop-down:hover
{
border-image: url(:/images/comboBox/drop-down-hover.png);
}
/*把checked换成on也行*/
QComboBox::drop-down:checked
{
border-image: url(:/images/comboBox/drop-down-on.png);
}
QComboBox::drop-down:checked:hover
{
border-image: url(:/images/comboBox/drop-down-on-hover.png);
}
QSpinBox
QSpinBox的subcontrol有::up-button,::down-button,::up-arrow,::down-arrow。up-button 显示在QSpinBox里,它的subcontrol-origin是相对于QSpinBox的;down-button 显示在QSpinBox里,它的subcontrol-origin是相对于QSpinBox的;up-arrow 显示在up-button里,它的subcontrol-origin是相对于up-button的;down-arrow 显示在 down-button里,它的subcontrol-origin是相对于down-button的。
QSpinBox
{
border:1px solid black;
border-radius:5px;
}
/*按钮*/
QSpinBox:down-button,QSpinBox:up-button
{
width:16px;
height:15px;
subcontrol-origin:padding;
background:white;
border:2px solid rgb(217,217,217);
border-radius:5px;
}
QSpinBox:down-button
{
subcontrol-position:left center;
}
QSpinBox:up-button
{
subcontrol-position:right center;
}
QSpinBox:down-button:hover,QSpinBox:up-button:hover
{
border:2px solid rgb(138,138,138);
}
/*箭头*/
QSpinBox:down-arrow
{
border-image: url(:/images/spinBox/down-arrow.png);
}
QSpinBox:up-arrow
{
border-image: url(:/images/spinBox/up-arrow.png);
}
QSpinBox:down-arrow:hover
{
border-image: url(:/images/spinBox/down-arrow-hover.png);
}
QSpinBox:up-arrow:hover
{
border-image: url(:/images/spinBox/up-arrow-hover.png);
}
QSlider
QSlider的subcontrol有 ::groove(槽),::handle,::add-page 和 ::sub-page。groove 显示在 QSlider 里,它的 subcontrol-origin 是相对于 QSlider 的;handle 显示在 groove 里,它的 subcontrol-origin 是相对于 groove 的;sub-page 显示在 groove 里,它的 subcontrol-origin 是相对于 groove 的;add-page 显示在 groove 里,它的 subcontrol-origin 是相对于 groove 的;handle, sub-page, add-page虽然都显示在groove里,但是都可以把它们扩展到groove外。
QSlider::groove:horizontal
{
border: 1px solid skyblue;
background-color: skyblue;
height: 10px;
border-radius: 5px;
}
QSlider::handle:horizontal
{
background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.7 white,stop:0.8 rgb(143,212,255));
width: 20px;
border-radius: 10px;
margin-top: -5px;
margin-bottom: -5px;
}
QSlider::sub-page:horizontal
{
background: #999;
margin: 5px;
border-radius: 5px;
}
QSlider::add-page:horizontal
{
background: #666;
margin: 5px;
border-radius: 5px;
}
QProgressBar
对于QProgressBar的QSS,大多数都是想把chunk定义为圆角矩形的样子。但是当它的value较小时,chunk的圆角会变成直角。若想要更改QProgressBar外观,推荐继承QProgressBar自己绘制或者使用QStyle。
/*边框*/
QProgressBar
{
border:1px solid skyblue;
border-radius:5px;
height:5px;
text-align: center;
}
/*进度条*/
QProgressBar::chunk
{
background-color: steelblue;
border-radius: 5px;
}
QProgressBar
{
border-color: 1px solid blue;
border-radius: 5px;
text-align: center;
}
QProgressBar:chunk
{
background-color: aqua; /*设置块的颜色*/
width: 5px; /*块的宽度*/
margin: 0.5px; /*让每个块之间有点间隔*/
}
若最大值最小值都是0,则会显示一个繁忙提示,等待系统容错处理结束,再继续恢复加载。
progressBar->setRange(0,0);
QWidget#TitleWidget {
image: url(:/title/images/title/title.png);
}
#leftSideBtn {
border-image:url(:/title/images/title/left_show.png);
}
#leftSideBtn:hover {
border-image:url(:/title/images/title/left_show_h.png);
}
#leftSideBtn:checked {
border-image:url(:/title/images/title/left_hide.png);
}
#leftSideBtn:checked:hover {
border-image:url(:/title/images/title/left_hide_h.png);
}
#rightSideBtn {
border-image:url(:/title/images/title/right_show.png);
}
#rightSideBtn:hover {
border-image:url(:/title/images/title/right_show_h.png);
}
#rightSideBtn:checked {
border-image:url(:/title/images/title/right_hide.png);
}
#rightSideBtn:checked:hover {
border-image:url(:/title/images/title/right_hide_h.png);
}
#settingBtn, #exitBtn {
border-width: 0;
border-style: none;
border-image: none;
border-left-width: 10px;
padding-left: 20px;
padding-right: 10px;
padding-top: 6px;
padding-bottom: 6px;
background-repeat: no-repeat;
background-position: left center;
background-origin: padding;
color:#7e97ae;
}
#settingBtn {
background-image: url(:/title/images/title/setting.png);
}
#settingBtn:hover {
background-image: url(:/title/images/title/setting_h.png);
color:#34c0be;
}
#exitBtn {
background-image: url(:/title/images/title/exit.png);
}
#exitBtn:hover {
background-image: url(:/title/images/title/exit_h.png);
color:#34c0be;
}
#timeLbl {
color: #C4F2FF;
font-size: 26px;
font-family: "LCD AT&T Phone Time/Date";
}
* {
font-family: "Microsoft YaHei";
outline: 0px;
}
#MainWindow {
background: #000000;
}
/* HLine 水平线 */
QFrame[frameShape="4"] {
border: none;
background-color: #233245;
max-height: 1px;
}
/* VLine 垂直线 */
QFrame[frameShape="5"] {
border: none;
background-color: #233245;
max-width: 1px;
}
/* QLabel */
QLabel {
color: #7E97AE;
}
/* 按钮 begin */
QPushButton {
border: none;
}
QPushButton {
border-image: url(:/button/images/button/normal.png) 2px;
border-width: 2px;
color: #A9BEDF;
}
QPushButton:hover {
border-image: url(:/button/images/button/hover.png) 2px;
}
QPushButton:pressed {
border-image: url(:/button/images/button/pressed.png) 2px;
}
QPushButton:checked {
border-image: url(:/button/images/button/checked.png) 2px;
}
QPushButton:disabled {
border-image: url(:/button/images/button/disabled.png) 2px;
}
/* 按钮 end */
/* 文本输入框 begin */
QLineEdit, QTextEdit, QPlainTextEdit {
color: #7E97AE;
padding: 0px 8px;
border-image: url(:/edit/images/edit/normal.png) 1px;
border-width: 1px;
font-size: 13px;
}
QLineEdit:hover, QTextEdit:hover, QPlainTextEdit:hover {
border-image: url(:/edit/images/edit/hover.png) 1px;
}
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
border-image: url(:/edit/images/edit/focus.png) 1px;
color: #bed6fa;
}
QLineEdit[readOnly="true"], QTextEdit[readOnly="true"], QPlainTextEdit[readOnly="true"] {
border-image: url(:/edit/images/edit/Readonly_normal.png) 1;
}
QLineEdit:disabled, QTextEdit:disabled, QPlainTextEdit:disabled {
border-image: url(:/edit/images/edit/Readonly_normal.png) 1;
color: #566b8b;
}
/* 文本输入框 end */
/* 勾选框 begin */
QCheckBox {
color: #bed6fa;
}
QCheckBox:indicator {
margin-top: 2px;
}
QCheckBox:disabled {
color: #566b8b;
background: transparent;
}
QCheckBox:indicator:unchecked {
image: url(:/checkbox/images/checkbox/unchecked_normal.png);
}
QCheckBox:indicator:unchecked:hover {
image: url(:/checkbox/images/checkbox/unchecked_hover.png);
}
QCheckBox:indicator:unchecked:pressed {
image: url(:/checkbox/images/checkbox/unchecked_pressed.png);
}
QCheckBox:indicator:unchecked:disabled {
image: url(:/checkbox/images/checkbox/unchecked_disabled.png);
}
QCheckBox:indicator:indeterminate {
image: url(:/checkbox/images/checkbox/half_normal.png);
}
QCheckBox:indicator:indeterminate:hover {
image: url(:/checkbox/images/checkbox/half_hover.png);
}
QCheckBox:indicator:indeterminate:pressed {
image: url(:/checkbox/images/checkbox/half_pressed.png);
}
QCheckBox:indicator:indeterminate:disabled {
image: url(:/checkbox/images/checkbox/half_disabled.png);
}
QCheckBox:indicator:checked {
image: url(:/checkbox/images/checkbox/checked_normal.png);
}
QCheckBox:indicator:checked:hover {
image: url(:/checkbox/images/checkbox/checked_hover.png);
}
QCheckBox:indicator:checked:pressed {
image: url(:/checkbox/images/checkbox/checked_pressed.png);
}
QCheckBox:indicator:checked:disabled {
image: url(:/checkbox/images/checkbox/checked_disabled.png);
}
/* 勾选框 end */
/* 多选框 begin */
QComboBox {
background: transparent;
color: #7E97AE;
border-image: url(:/edit/images/edit/normal.png) 1;
border-width: 1px;
padding: 0px 8px;
font-size: 13px;
}
QComboBox:hover {
border-image: url(:/edit/images/edit/hover.png) 1;
}
QComboBox:disabled {
border-image: url(:/edit/images/edit/Readonly_normal.png) 1;
color: #566b8b;
}
QComboBox::drop-down:disabled {
image: url(:/combobox/images/combobox/dropdown_disabled.png);
}
QComboBox::drop-down {
image: url(:/combobox/images/combobox/dropdown_normal.png);
}
QComboBox::drop-down:hover {
image: url(:/combobox/images/combobox/dropdown_hover.png);
}
QComboBox::drop-down:on {
image: url(:/combobox/images/combobox/dropdown_presssed.png);
}
QComboBox QAbstractItemView {
/*background-color: rgba(18, 23, 31, 0.9);
border: 1px solid #7594b8;*/
background-color:transparent;
margin-top: 1px;
border-image: url(:/edit/images/edit/normal.png) 1px;
border-width: 1px;
selection-color: #5affca;
selection-background-color: transparent;
color: #7E97AE;
}
/* 多选框 end */
QSpinBox, QDoubleSpinBox, QTimeEdit, QDateTimeEdit {
padding: 0 5;
border-image: url(:/edit/images/edit/normal.png) 1;
border-width: 1px;
color: #7e97ae;
selection-background-color: #30d0de;
selection-color: #121820;
font-size: 13px;
}
QSpinBox:hover, QDoubleSpinBox:hover, QTimeEdit:hover, QDateTimeEdit:hover {
border-image: url(:/edit/images/edit/hover.png) 1;
border-width: 1px;
}
QSpinBox:focus, QDoubleSpinBox:focus, QTimeEdit:focus, QDateTimeEdit:focus {
border-image: url(:/edit/images/edit/focus.png) 1;
border-width: 1px;
color: #bed6fa;
}
QSpinBox:disabled, QDoubleSpinBox:disabled, QTimeEdit:disabled, QDateTimeEdit:disabled {
border-image: url(:/edit/images/edit/Readonly_normal.png) 1;
border-width: 1px;
color: #566b8b;
}
QSpinBox::up-arrow, QDoubleSpinBox::up-arrow, QTimeEdit::up-arrow, QDateTimeEdit::up-arrow {
image: url(:/spinbox/images/spinbox/spinup_normal.png);
width: 23px;
height: 12px;
}
QSpinBox::up-arrow:hover, QDoubleSpinBox::up-arrow:hover, QTimeEdit::up-arrow:hover, QDateTimeEdit::up-arrow:hover {
image: url(:/spinbox/images/spinbox/spinup_hover.png);
}
QSpinBox::up-arrow:pressed, QDoubleSpinBox::up-arrow:pressed, QTimeEdit::up-arrow:pressed, QDateTimeEdit::up-arrow:pressed {
image: url(:/spinbox/images/spinbox/spinup_pressed.png);
}
QSpinBox::down-arrow, QDoubleSpinBox::down-arrow, QTimeEdit::down-arrow, QDateTimeEdit::down-arrow {
image: url(:/spinbox/images/spinbox/spindown_normal.png);
width: 23px;
height: 12px;
}
QSpinBox::down-arrow:hover, QDoubleSpinBox::down-arrow:hover, QTimeEdit::down-arrow:hover, QDateTimeEdit::down-arrow:hover {
image: url(:/spinbox/images/spinbox/spindown_hover.png);
}
QSpinBox::down-arrow:pressed, QDoubleSpinBox::down-arrow:pressed, QTimeEdit::down-arrow:pressed, QDateTimeEdit::down-arrow:pressed {
image: url(:/spinbox/images/spinbox/spindown_pressed.png);
}
QSpinBox::up-button, QDoubleSpinBox::up-button, QTimeEdit::up-button, QDateTimeEdit::up-button {
background: transparent;
subcontrol-origin: border;
subcontrol-position: top right;
width: 23px;
height: 12px;
border-width: 1px;
}
QSpinBox::down-button, QDoubleSpinBox::down-button, QTimeEdit::down-button, QDateTimeEdit::down-button {
background: transparent;
subcontrol-origin: border;
subcontrol-position: bottom right;
width: 23px;
height: 12px;
}
QDateTimeEdit::drop-down {
min-width: 8;
max-width: 8;
min-height: 5;
max-height: 5;
image: url(:/spinbox/images/spinbox/spindown_normal.png);
}
/* ListView begin */
QListView {
border: none;
outline: none;
background-color: transparent;
color: #7E97AE;
font-size: 13px;
}
QListView::item {
padding-left: 10px;
height: 26px;
border: none;
}
QListView::item:hover {
color: #78FFFA;
}
QListView::item:selected {
padding-left: 5px;
border-image: url(:/treeview/images/treeview/item_selected.png) 1px;
border-width: 1px;
color: #78FFFA;
}
/* ListView end */
/* 树 begin */
QTreeView {
background-color: transparent;
outline: 0px;
border: none;
}
QTreeView::item {
font-size: 13px;
height: 26px;
outline: 0px;
color: #687f95;
margin-right: 1px;
margin-bottom: 1px;
padding-left:1px;
}
QTreeView::item:hover {
background-color: transparent;
color: #78fffa;
padding-left:1px;
}
QTreeView::item:selected {
border-image: url(:/treeview/images/treeview/item_selected.png) 1px;
border-width:1px;
color: #78fffa;
}
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(:/treeview/images/treeview/vline.png);
}
QTreeView::branch:has-siblings:!adjoins-item:disabled {
border-image: url(:/treeview/images/treeview/vline.png);
}
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(:/treeview/images/treeview/branch_more.png);
}
QTreeView::branch:has-siblings:adjoins-item:disabled {
border-image: url(:/treeview/images/treeview/branch_more.png);
}
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(:/treeview/images/treeview/branch_end.png);
}
QTreeView::branch:!has-children:!has-siblings:adjoins-item:disabled {
border-image: url(:/treeview/images/treeview/branch_end.png);
}
QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings {
image: url(:/treeview/images/treeview/branch_close.png);
border-image: none;
}
QTreeView::branch:has-children:!has-siblings:disabled, QTreeView::branch:closed:has-children:has-siblings:disabled {
image: url(:/treeview/images/treeview/branch_close.png);
border-image: none;
}
QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings {
image: url(:/treeview/images/treeview/branch_open.png);
border-image: none;
}
QTreeView::branch:open:has-children:!has-siblings:disabled, QTreeView::branch:open:has-children:has-siblings:disabled {
image: url(:/treeview/images/treeview/branch_open.png);
border-image: none;
}
/* 树 end */
/* 表格 begin */
QHeaderView {
background-color: transparent;
border-image: url(:/tableview/images/tableview/cornerButton_normal.png);
color: #ffffff;
qproperty-showSortIndicator: 0;
qproperty-highlightSections: 0;
}
QHeaderView::section {
background: transparent;
min-height: 30px;
border: 1px solid #263142;
}
QHeaderView::section:first:horizontal {
border-top: none;
border-bottom: none;
border-image: url(:/tableview/images/tableview/h_section_normal.png);
}
QHeaderView::section:vertical {
border-left: none;
border-right: none;
}
QHeaderView::section:first:vertical, QHeaderView::section:last {
border: none;
border-image: url(:/tableview/images/tableview/cornerButton_normal.png);
}
QHeaderView::section:vertical {
background: transparent;
min-width: 30px;
border-image: url(:/tableview/images/tableview/cornerButton_normal.png);
}
QHeaderView::section:vertical:hover {
border-image: url(:/tableview/images/tableview/v_section_hover.png);
}
QHeaderView::section:vertical:pressed {
border-image: url(:/tableview/images/tableview/v_section_pressed.png);
}
QTableView {
background: #101b26;
alternate-background-color: #0a131b;
outline: none;
border: none;
qproperty-showGrid: 1;
color: #7e97ae;
gridline-color: #263142;
}
QTableView::item {
border-image: url();
}
QTableView::item:hover {
border-image: url(:/tableview/images/tableview/item_hover.png);
border-width: 2px;
}
QTableView::item:selected {
border-image: url(:/tableview/images/tableview/item_selected.png);
border-width: 2px;
color: #2aa8f0;
}
QTableView QTableCornerButton::section {
background-color: transparent;
border-image: url(:/tableview/images/tableview/cornerButton_normal.png);
}
QTableView QTableCornerButton::section:pressed {
background-color: transparent;
border-image: url(:/tableview/images/tableview/cornerButton_pressed.png);
}
/* 表格 end */
/* ListView、TreeView、TableView通用 begin */
QTreeView:indicator, QTableView:indicator, QListView:indicator {
width: 18px;
height: 18px;
margin-top: 0px;
}
QTreeView:indicator:unchecked, QTableView:indicator:unchecked, QListView:indicator:unchecked {
image: url(:/checkbox/images/checkbox/unchecked_normal.png);
}
QTreeView:indicator:unchecked:hover, QTableView:indicator:unchecked:hover, QListView:indicator:unchecked:hover {
image: url(:/checkbox/images/checkbox/unchecked_hover.png);
}
QTreeView:indicator:unchecked:pressed, QTableView:indicator:unchecked:pressed, QListView:indicator:unchecked:pressed {
image: url(:/checkbox/images/checkbox/unchecked_pressed.png);
}
QTreeView:indicator:unchecked:disabled, QTableView:indicator:unchecked:disabled, QListView:indicator:unchecked:disabled {
image: url(:/checkbox/images/checkbox/unchecked_disabled.png);
}
QTreeView:indicator:indeterminate, QTableView:indicator:indeterminate, QListView:indicator:indeterminate {
image: url(:/checkbox/images/checkbox/half_normal.png);
}
QTreeView:indicator:indeterminate:hover, QTableView:indicator:indeterminate:hover, QListView:indicator:indeterminate:hover {
image: url(:/checkbox/images/checkbox/half_hover.png);
}
QTreeView:indicator:indeterminate:pressed, QTableView:indicator:indeterminate:pressed, QListView:indicator:indeterminate:pressed {
image: url(:/checkbox/images/checkbox/half_pressed.png);
}
QTreeView:indicator:indeterminate:disabled, QTableView:indicator:indeterminate:disabled, QListView:indicator:indeterminate:disabled {
image: url(:/checkbox/images/checkbox/half_disabled.png);
}
QTreeView:indicator:checked, QTableView:indicator:checked, QListView:indicator:checked {
image: url(:/checkbox/images/checkbox/checked_normal.png);
}
QTreeView:indicator:checked:hover, QTableView:indicator:checked:hover, QListView:indicator:checked:hover {
image: url(:/checkbox/images/checkbox/checked_hover.png);
}
QTreeView:indicator:checked:pressed, QTableView:indicator:checked:pressed, QListView:indicator:checked:pressed {
image: url(:/checkbox/images/checkbox/checked_pressed.png);
}
QTreeView:indicator:checked:disabled, QTableView:indicator:checked:disabled, QListView:indicator:checked:disabled {
image: url(:/checkbox/images/checkbox/checked_disabled.png);
}
/* ListView、TreeView、TableView通用 end */
/* 滚动条 begin*/
QScrollBar::add-page, QScrollBar::sub-page {
background: none;
}
/* 垂直 ------*/
QScrollBar::vertical {
width: 8px;
border-image: url(:/scrollbar/images/scrollbar/v_groove_normal.png);
padding: 10px 0px;
}
QScrollBar::handle:vertical {
border-image: url(:/scrollbar/images/scrollbar/V_handle_bg_normal.png);
image: url(:/scrollbar/images/scrollbar/V_handle_normal.png)
}
QScrollBar::handle:vertical:hover {
border-image: url(:/scrollbar/images/scrollbar/V_handle_bg_hover.png);
image: url(:/scrollbar/images/scrollbar/V_handle_hover.png);
}
QScrollBar::handle:vertical:pressed {
border-image: url(:/scrollbar/images/scrollbar/V_handle_bg_pressed.png);
image: url(:/scrollbar/images/scrollbar/V_handle_pressed.png);
}
QScrollBar::handle:vertical:disabled {
border-image: url(:/scrollbar/images/scrollbar/V_handle_bg_disabled.png);
image: url(:/scrollbar/images/scrollbar/V_handle_disabled.png);
}
QScrollBar::up-arrow {
image: url(:/scrollbar/images/scrollbar/arrowup_normal.png);
}
QScrollBar::up-arrow:hover {
image: url(:/scrollbar/images/scrollbar/arrowup_hover.png);
}
QScrollBar::up-arrow:pressed {
image: url(:/scrollbar/images/scrollbar/arrowup_pressed.png);
}
QScrollBar::down-arrow {
image: url(:/scrollbar/images/scrollbar/arrowdown_normal.png);
}
QScrollBar::down-arrow:hover {
image: url(:/scrollbar/images/scrollbar/arrowdown_hover.png);
}
QScrollBar::down-arrow:pressed {
image: url(:/scrollbar/images/scrollbar/arrowdown_pressed.png);
}
QScrollBar::add-line:vertical {
subcontrol-position: bottom;
subcontrol-origin: margin;
height: 8;
border: none;
}
QScrollBar::sub-line:vertical {
subcontrol-position: top;
subcontrol-origin: margin;
height: 8;
border: none;
}
/* 垂直 ------ */
/* 水平 ------ */
QScrollBar:horizontal {
height: 8px;
border-image: url(:/scrollbar/images/scrollbar/h_groove_normal.png);
padding: 0px 10px;
}
QScrollBar::handle:horizontal {
border-image: url(:/scrollbar/images/scrollbar/H_handle_bg_normal.png);
image: url(:/scrollbar/images/scrollbar/H_handle_normal.png);
}
QScrollBar::handle:horizontal:hover {
border-image: url(:/scrollbar/images/scrollbar/H_handle_bg_hover.png);
image: url(:/scrollbar/images/scrollbar/H_handle_hover.png);
}
QScrollBar::handle:horizontal:pressed {
border-image: url(:/scrollbar/images/scrollbar/H_handle_bg_pressed.png);
image: url(:/scrollbar/images/scrollbar/H_handle_pressed.png);
}
QScrollBar::handle:horizontal:disabled {
border-image: url(:/scrollbar/images/scrollbar/H_handle_bg_disabled.png);
image: url(:/scrollbar/images/scrollbar/H_handle_disabled.png);
}
QScrollBar::left-arrow {
image: url(:/scrollbar/images/scrollbar/arrowleft_normal.png);
}
QScrollBar::left-arrow:hover {
image: url(:/scrollbar/images/scrollbar/arrowleft_hover.png);
}
QScrollBar::left-arrow:pressed {
image: url(:/scrollbar/images/scrollbar/arrowleft_pressed.png);
}
QScrollBar::right-arrow {
image: url(:/scrollbar/images/scrollbar/arrowdright_normal.png);
}
QScrollBar::right-arrow:hover {
image: url(:/scrollbar/images/scrollbar/arrowdright_hover.png);
}
QScrollBar::right-arrow:pressed {
image: url(:/scrollbar/images/scrollbar/arrowdright_pressed.png);
}
QScrollBar::add-line:horizontal {
subcontrol-position: right;
subcontrol-origin: margin;
width: 8;
border: none;
}
QScrollBar::sub-line:horizontal {
subcontrol-position: left;
subcontrol-origin: margin;
width: 8;
border: none;
}
/* 水平 ------ */
/* 滚动条 end*/
/* TabWidget begin */
QTabWidget::pane
{
border-top:1px solid #b82525;
}
QTabWidget::tab-bar
{
/*height:27px;*/
}
QTabBar::tab
{
width:82px;
height:30px;
background-color:#294564;
margin-right:4px;
color: #687f95;
}
QTabBar::tab:hover
{
background-color:#132c3f;
color: #78fffa;
}
QTabBar::tab:selected
{
background-color:#132c3f;
color: #78fffa;
}
QTabBar::tab:first
{
margin-left:10px;
}
/* TabWidget end */
/* ScrollArea背景透明 begin */
QScrollArea {
background: transparent;
border: none;
}
QScrollArea>QWidget>QWidget {
background: transparent;
}
/* ScrollArea背景透明 end */
#taskListLbl, #taskDetailLbl, #deviceInfoLbl {
border-image: url(:/others/images/others/title.png) 2px 24px 2px 2px;
border-width: 2px 24px 2px 2px;
}
注意 :样式表不⽣效
void CustomWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
使用建议
1. 统⼀设置⼀个 widget 中所有控件的样式,不要在代码中单独给每个控件设置样式,这样做会造成混乱;
2. 图⽚资源先添加到 qrc ⽂件中,再在样式表中使⽤;
3. 同⼀个类,不要混⽤多种⽅式设置样式表,⽐如既在 ui ⽂件中设置样式,⼜在代码中读取样式表⽂件,这样做很可能造成样式覆盖,并且问题难以查找。
作业4
调整界⾯样式
修改界⾯结构,添加时间显示(暂时不需要动态更新),添加标题“思维导图”,通过样式表设置主界⾯样式,参考样式
主要包含以下内容:
-
顶部标题栏区域
-
标题栏右上⻆的昵称显示样式、最⼤化、最⼩化、关闭按钮样式
-
标题栏左下⽅⼏个菜单的样式
-
主界⾯背景
-
主界⾯左侧树的样式
坐标系统
下⾯是 QWidget 中与坐标位置有关的⼀些函数:
1. QPoint QWidget::pos() const;
获取⼀个 widget 相对于它的⽗ widget 的位置。
如果该 widget 是⼀个窗⼝,那么得到的将是它在桌⾯上的位置(包含边框)。
2. QRect QWidget::geometry() const;
获取 widget 相对于⽗ widget 的 geometry(即位置和⼤⼩),不包括窗⼝边框。
3. QSize QWidget::size() const;
获取⼀个 widget ⼤⼩(即宽和⾼),如果是窗⼝则不包括窗⼝边框。
4. QRect QWidget::frameGeometry() const;
widget 相对于它⽗ widget 的 geometry,如果是窗⼝则包括窗⼝边框。
5. QSize QWidget::frameSize() const;
获取 widget 的⼤⼩,包括窗⼝边框。
其它还有⼀些函数,⽐如 x()、y()、width()、height() 等,可查看 Qt ⽂档中的相关介绍。
窗口
如果⼀个 widget 没有指定⽗对象(即⽗对象为空指针),那么它将作为⼀个窗⼝显示,窗⼝位置的计算是相对于桌⾯的左上⻆的。在窗⼝显示之后,获取窗⼝的信息:
qDebug() << this->geometry();
qDebug() << this->frameGeometry();
qDebug() << this->pos();
QRect(686,404 547x361)
QRect(678,373 563x400)
QPoint(678,373)
非窗口控件
如果⼀个 widget 指定了其它 widget 为⽗对象,那么它的位置信息将会是相对于⽗ widget 的。
qDebug() << ui.widget->geometry();
qDebug() << ui.pushButton->geometry();
输出窗⼝:
QRect(30,40 400x250)
QRect(80,110 75x23)
绿⾊边框的 widget,相对于主窗⼝的位置是 (30, 40);
红⾊边框 pushButton,相对于绿⾊边框 widget 的位置是 (80, 110)。
坐标转换
上⾯算的都是相对于⽗ widget 的位置,那么如何得到间接的坐标,⽐如上⾯ pushButton 相对于桌⾯、相对于主窗⼝的坐标呢?
先了解⼀些与坐标转换有关的函数:
1. QPoint QWidget::mapToGlobal(const QPoint &pos) const;
将当前 widget 中的坐标 pos 转为全局坐标。
QPoint QWidget::mapFromGlobal(const QPoint &pos) const;
将全局坐标转为widget中的局部坐标
2. QPoint QWidget::mapToParent(const QPoint &pos) const;
将当前 widget 的坐标 pos 转为相对于⽗ widget 的坐标。
QPoint QWidget::mapFromParent(const QPoint &pos) const;
将父widget中的坐标转为在当前子widget中的坐标
3. QPoint QWidget::mapTo(const QWidget *parent, const QPoint &pos) const;
将当前 widget 中的坐标 pos 转为相对于 parent 的坐标,parent 不能为空,并且必须是当前widget 的直接或间接⽗ widget。
QPoint QWidget::mapFrom(const QWidget *parent, const QPoint &pos) const
将父widget中的坐标转为在当前子widget中的坐标(必须满足父子关系)
// 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 MainWindow::keyPressEvent(QKeyEvent *event)
{
// 键盘按下事件
// 修饰键是ctrl
if (event->modifiers() & Qt::ControlModifier)
{
if (event->key() == Qt::Key_C)
{
qDebug() << QStringLiteral("复制");
}
else if (event->key() == Qt::Key_V)
{
qDebug() << QStringLiteral("粘贴");
}
}
// 修饰键是shift
else if (event->modifiers() == Qt::ShiftModifier)
{
qDebug() << event->text();
}
}
事件
Qt提供了两种方法对应用程序中发生的事情做出响应。一是信号槽,二是使用事件。
当事件发⽣时,Qt 创建了⼀个事件对象,并将它传给特定的对象。事件⼤多是⽤户与界⾯交互时触发,但也会有系统⾃动触发,⽐如:当⿏标按下时会触发⿏标事件(QMouseEvent),键盘按下时会触发键盘事件(QKeyEvent),窗⼝显示时会触发绘图事件(QPaintEvent),定时器超时后触发定时器事件(QTimerEvent)。
事件处理
方法一:重写QApplication::notify()
继承QApplication,并重写notify()函数
bool CustomApplication::notify(QObject* receiver, QEvent* evnet)
{
if(event->type() == QEvent::MouseButtonPress)
{
qDebug() << "类名:" << receiver->metaObject()->className();
return false;
}
return QApplication::notify(receiver, event);
}
注意:任何线程中任何对象发生的事件,都会调用该函数,重写QApplication::notify()如果发生问题,会对整个应用程序造成严重影响,应谨慎使用。
方法二:重写QObject::event()
QObject::event()虽然可进行事件处理,但其真正用途是根据事件类型进行事件分发。试想一个按钮被禁用,那么鼠标按下事件将不会分发给按钮,而QObject::event()中对鼠标事件的处理仍会发生,可能会造成问题。所以不推荐使用该法处理事件。
方法三:重写特定事件
事件类型
QResizeEvent:窗⼝⼤⼩发⽣改变事件
QMouseEvent:⿏标事件
QKeyEvent:键盘事件
QCloseEvent:窗⼝关闭事件
QPaintEvent:绘图事件
QTimerEvent:定时器事件
……
处理这些事件,通常需要重写事件函数,以⿏标按下时触发⿏标事件为例:
void TestWidget::mousePressEvent(QMouseEvent *event)
{
// 调⽤⽗类的函数
QWidget::mousePressEvent(event);
// 获取⿏标按下的位置
QPoint pos = event->pos();
}
class MyLabel : public QLabel
{
Q_OBJECT
public:
explicit MyLabel(QWidget* parent):QLabel(parent)
{
//设置鼠标追踪
this->setMouseTracking(true);
//利用事件过滤器,拦截label的鼠标按下事件
ui->label->installEventFilter(this); //给控件安装过滤器;重写过滤器事件
}
void enterEvent(QEvent*)
{qDebug() << "鼠标进入了";}
void leaveEvent(QEvent*)
{qDebug() << "鼠标离开了";}
void mouseMoveEvent(QMouseEvent* ev)
{
if(ev->buttons() & Qt::LeftButton)
{
QString str = QString("鼠标移动了,x = %1, y = %2").arg(ev->x()).arg(ev->y());
qDebug() << str;
}
}
// 事件分发器
bool event(QEvent* e)
{
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent* ev = static_cast<QMouseEvent*>(e);
QString str = QString("在event事件中 鼠标按下了,x=%1,y=%2").arg(ev->x()).arg(ev->y());
qDebug() << str;
return true; //拦截事件,不再向下分发事件
}
//其他事件交给父类处理
return QLabel::event(e);
}
//过滤器事件
// 参数1 判断控件 参数2 判断事件
bool eventFilter(QObject* obj, QEvent* e)
{
if(obj == ui->label)
{
if(e->type() == QEvent::MouseButtonPress)
{
QMouseEvent* ev = static_cast<QMouseEvent*>(e);
QString str = QString("在事件过滤器中,鼠标按下了,x=%1,y=%2").arg(ev->x()).arg(ev->y());
qDebug() << str;
return true; //拦截事件,不再向下分发
}
}
//其他事件抛给父类处理
return QLabel::eventFilter(obj,e);
}
};
事件分发器
事件和信号槽区别
事件由外部事物生成,并在应用中的事件循环处理;
信号槽用于类之间的通信。
事件过滤器
⼀个 QObject 对象可以⽤来监视另⼀个 QObject 对象的事件。 设置事件过滤器分为以下两步:
安装事件过滤器: installEventFilter() ;
重写事件过滤器函数: eventFilter() 。
ui->widget_deepspace_bg->installEventFilter(this);
bool InitWidget::eventFilter(QObject *watched, QEvent *event)
{
switch (event->type())
{
case QEvent::Enter:
{
QString path("border-image: url(:/data/image/bottomWidget/subwindows/card_hover.png);");
if (watched->objectName() == ui->widget_deepspace_bg->objectName())
{
QString style = QString("QWidget#%1{%2;}").arg(ui->widget_deepspace_bg->objectName()).arg(path);
ui->widget_deepspace_bg->setStyleSheet(style);
}
else if (watched->objectName() == ui->widget_jd_bg->objectName())
{
QString style = QString("QWidget#%1{%2;}").arg(ui->widget_jd_bg->objectName()).arg(path);
ui->widget_jd_bg->setStyleSheet(style);
}
}
break;
case QEvent::Leave:
{
QString path("border-image: url(:/data/image/bottomWidget/subwindows/card_normal.png);");
if (watched->objectName() == ui->widget_deepspace_bg->objectName())
{
QString style = QString("QWidget#%1{%2;}").arg(ui->widget_deepspace_bg->objectName()).arg(path);
ui->widget_deepspace_bg->setStyleSheet(style);
}
else if (watched->objectName() == ui->widget_jd_bg->objectName())
{
QString style = QString("QWidget#%1{%2;}").arg(ui->widget_jd_bg->objectName()).arg(path);
ui->widget_jd_bg->setStyleSheet(style);
}
}
break;
case QEvent::MouseButtonPress:
{
if (watched->objectName() == ui->widget_deepspace_bg->objectName())
{
emit Signal_switchSceneWgt(STSceneType::ST_DeepSpace);
}
else if (watched->objectName() == ui->widget_jd_bg->objectName())
{
emit Signal_switchSceneWgt(STSceneType::ST_NearGround);
}
}
break;
default:
break;
}
return QWidget::eventFilter(watched, event);
}
事件传递
事件触发后会进⾏传递,从⼦ widget 向⽗ widget 逐层传递。
如主窗⼝中有⼀个 widget1,在 widget1 中有 widget2。
在 widget2 中按下⿏标,触发 widget2 的 mousePressEvent,然后⿏标事件会先传到 widget1,再传到主窗⼝当中。
如果不想让事件传递该怎么做呢?
可以在 widget2 的 mousePressEvent 中调⽤ QMouseEvent::accept(),这样⿏标事件便只会在widget2 中发⽣,不会传递给它的⽗对象了。
若接受事件,即QEvent::accept(),表明接收者想要该事件,事件不会继续传递;
若忽略事件,即QEvent::ignore(),表明接收者不想要该事件,事件会传递给父对象来处理。
作业5
截⽌⽬前,主窗⼝是⽆边框的,⿏标⽆法拖动。
-
请通过重写⿏标事件,实现主窗⼝的拖动功能。
-
为左侧QTreeWidget添加右键菜单。如果树没有根节点,则包含菜单项“添加根节点”,根节点只能是组类型;如果⿏标位置有类型是组的节点,包含菜单项“添加⼦项、删除、重命名”;对于类型是叶⼦的节点,包含菜单项“删除、重命名”(设定只有类型是组的节点可以添加⼦节点,并在添加⼦节点时,可以选择⼦节点类型是组还是叶⼦)。
-
实现上⾯右键菜单的所有功能,删除时提示是否确定。
-
添加快捷键:Del⽤于删除、F2⽤于重命名
读写配置文件
Qt提供了丰富的类来支持文件操作:
- QFileInfo获取文件和文件夹的属性,QDir封装了文件夹的操作,QFile封装了文件的操作
- QTextStream封装了文本文件的读写,QDataStream封装了二进制文件的读写
- QFileDialog可调用系统资源管理器实现选择文件和文件夹功能
- QDomNode和QXmlStream(Reader|Writer)封装了xml结构文件的功能
- QJsonObject封装了Json结构文件的读写
- QSetting封装了ini配置文件和注册表的读写
文件信息类 QFileInfo
QString strFileName = QFileDialog::getOpenFileName(this, "选择文件", ".");
QFileInfo info(strFileName);
qDebug() << "是否存在?" << info.exists() << "文件名不含后缀:" << info.baseName()
<< "后缀名:" << info.suffix().toUtf8().data() << "大小:" << info.size()
<< "文件名:" << info.fileName() << "文件路径:" << info.filePath()
<< "创建日期:" << info.created().toString("yyyy-MM-dd hh:mm:ss")
<< "最后修改日期:" << info.lastModified().toStrin("yyyy-MM-dd hh:mm:ss")
<< "文件权限:" << info.permissions();
目录操作 QDir
QDir dir("/home/dami");
qDebug() << "文件夹名称:" << dir.dirName();
qDebug() << "绝对路径" << dir.absolutePath();
qDebug() << "创建子文件夹" << dir.mkdir("newfolder1");
qDebug() << "进入子文件夹" << dir.cd("newfolder1");
qDebug() << "创建子文件夹" << dir.mkdir("newfolder2");
qDebug() << "删除子文件夹" << dir.rmdir("newfolder2");
qDebug() << "创建子文件夹" << dir.mkdir("newfolder3");
qDebug() << "创建子文件夹" << dir.mkdir("newfolder4");
qDebug() << "当前绝对路径" << dir.absolutePath();
//获取当前目录下文件和文件夹信息,不包括.和..
QFileInfoList dirlist = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
foreach(QFileInfo info, dirlist)
{
//现实子文件夹名称
qDebug() << info.fileName();
}
exists() 检测当前目录是否存在
entryList() 获取子目录的目录名信息
count() 获得当前目录下文件和文件夹的数量
remove() 删除指定文件
fromNativeSeparators() 把系统特定的路径格式转换为标准格式,比如Windows平台上 "c:\\winnt\\system32" 转换为 "c:/winnt/system32"
toNativeSeparators() 和 fromNativeSeparators() 相反,把标准路径格式转换为操作系统特定的路径格式
读写文件
文件读写使用QFile类。
QFile file("a.txt");
if(file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QString strText = file.readAll();
}
- QIODevice::ReadOnly 以只读方式打开文件
- QIODevice::WriteOnly 以只写方式打开文件
- QIODevice::ReadWrite 以读写方式打开文件;
- QIODevice::Append 以追加模式打开文件,新写入文件的数据添加到文件尾部
- QIODevice::Truncate 以截取方式打开文件,文件打开之前原有内容全部被清空
- QIODevice::Text 以文本方式打开文件,读取时 \n 被自动译为换行符。
QTextStream读写文件
QTextStream封装了更多数据类型的读写操作,使用起来更加方便。并且QTextStream的读写速率相比QFile有了很大的提升,并且文件越大提升幅度越明显。
//读文件
QFile file(path);//参数就是文件的路径
//设置打开方式
if(!file.open(QIODevice::ReadOnly))
{
getOpenFileName()是QFileDialog类的一个静态函数,返回用户选择的文件名,如果用户选择取消(Cancel),则返回一个空串
}
//用QTextStream类去读取文本信息
QTextStream stream(&file);
//用QString类去接收读取的信息
QString str;
while(stream.readLineInto(&str))
//while(str = stream.readLine())
{
//...
}
file.close();
//写文件
QFile file(path);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
return;
}
QTextStream out(&file);
out << QString("你好");
out << int(32);
out << double(3.141592653);
out << "\n"
file.close();
QDataStream 二进制文件
xml
xml是最常见的配置文件。对于xml文件的读写方式有很多,如流、SAX、DOM等。一般通过DOM对xml文件进行操作,因为这种方式简单、易懂、易于修改、维护。
使用Qt写xml,首先引入xml模块:
Qt+=xml
xml内容:
<?xml version="1.0" encoding="UTF-8"?>
<library>
<book id="01">
<title>Qt</title>
<author>Jack</author>
</book>
<book id="02">
<title>Linux</title>
<author>Bob</author>
</book>
</library>
首先读取文件内容,并创建QDomDocument对象。
QDomDocument doc(“mydocument”);
QFile file(“mydocument.xml”);
if(!file.open(QIODevice::ReadOnly))return;
if(!doc.setContent(&file))
{
file.close();
return;
}
file.close();
然后读取xml内容。
if(n.isElement())
{
QDomElement e=n.toElement();
qDebug()<<qPrintable(e.tagName())<<qPrintable(e.attribute(“id”));
//获取元素e的所有子节点的列表
QDocNodeList list=e.childNodes();
for(int i=0;i<list.count();++i)
{
QDocNode node=list.at(i);
if(node.isElement())
qDebug()<<” ”<<qPrintable(node.toElement().tagName())
<<qPrintable(node.toElement().text());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<class>
<student no="01" sex="male">
<name>张三</name>
<grade>98</grade>
</student>
<student no="02" sex="female">
<name>丽丝</name>
<grade>95</grade>
</student>
<student no="03" sex="male">
<name>王五</name>
<grade>89</grade>
</student>
</class>
void ReadXml()
{
//打开或创建文件
QFile file("test.xml"); //相对路径、绝对路径、资源路径都行
if(!file.open(QFile::ReadOnly))
{
return;
}
QString error;
int row = 0, column = 0;
QDomDocument doc;
if(!doc.setContent(&file, fasle, &error, &row, &column))
{
QMessageBox::information(this, "title", "parse file failed at line row and column:");
file.close();
return;
}
file.close();
QDomElement root = doc.documentElement(); //返回根节点,即class节点
// 学生节点
QDomNode studentNode = element.firstChild();
while (!studentNode.isNull())
{
QDomElement eleStudent = studentNode.toElement();
if ("student" == eleStudent.tagName())
{
// 学号
QString strNo = eleStudent.attribute("no"); //读取no标签
// 性别
QString strSex = eleStudent.attribute("sex"); //读取sex标签
//子节点,姓名和分数
for(QDomElement eleChild = eleStudent.firstChildElement(); !eleChild.isNull();
{
QString strTagName = eleChild.tagName();
if("name" = strTagName)
{
QString strName = eleChild.text(); //读取文本
}
else if("grade" = strTagName)
{
QString strGrade = eleChild.text(); //读取文本
}
}
}
//下一个学生节点
studentNode = studentNode.nextSibling();
}
}
xml的写入
void writeXML()
{
QFile file("test.xml");
//开打XML
if (!file.open(QFile::WriteOnly | QFile::Text))
{
qDebug() << "Error: cannot open file";
return;
}
QXmlStreamWriter stream(&file);
stream.setAutoFormatting(true); //自动缩进
stream.writeStartDocument(); //开始文档,写入文件头
stream.writeStartElement("class");//生成一个class项
//写入三个学生的信息
writeStudent(stream, "01", "male", "张三", 98);
writeStudent(stream, "02", "female", "李四", 95);
writeStudent(stream, "03", "male", "王五", 89);
stream.writeEndElement(); //结束class项
stream.writeEndDocument(); //结束文档
file.close();
}
void writeStudent(QXmlStreamWriter& writer, const QString& no, const QString& sex, const QString& name, int grade)
{
stream.writeStartElement("student"); //生成一个student项
stream.writeAttribute("no", no); //写入no标签
stream.writeAttribute("sex", sex); //写入no标签
stream.writeTextElement("name", name); //写入name项
stream.writeTextElement("grade", QString::number(grade)); //写入grade项
stream.writeEndElement(); //结束student项
}
日常开发中我们通常用DOM来读取XML,用SAX来写入XML,充分发挥两者的优势
Json
Json用于存储结构化的数据,存储的值有6种:bool、double、string、array、object、null
array数组是其他值类型的列表,用方括号”[]”表示;object对象是”键值对”组合,且都是字符串,不能包含重复的key,用大括号”{}”表示
{
"age": 26,
"color": [
"black",
"white"
],
"interest": {
"badminton": "羽毛球",
"basketball": "篮球"
},
"name": "张三",
"vip": true
}
void writeJson()
{
// 定义对象
QJsonObject interestObj;
// 插入元素,对应键值对
interestObj.insert("basketball", "篮球");
interestObj.insert("badminton", "羽毛球");
// 定义数组
QJsonArray colorArray;
// 往数组中添加元素
colorArray.append("black");
colorArray.append("white");
// 定义根节点
QJsonObject root;
root.insert("name", "张三");
root.insert("age", 26);
root.insert("vip", true);
root.insert("interest", interestObj);
root.insert("color", colorArray);
//创建一个JSON文档
QJsonDocument doc;
//把跟节点放进来
doc.setObject(root);
//写入到文件
QFile file("test.json");
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
return;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
// 写入文件
stream << doc.toJson();
file.close();
}
void readJson()
{
QFile file("test.json");
if (!file.open(QIODevice::ReadOnly))
{
return;
}
// 读取json文件的所有内容
QByteArray jsonArray = file.readAll();
file.close();
// QJsonParseError输出解析时的错误报告
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(jsonArray, &jsonError);
// 判断是否解析成功
if (QJsonParseError::NoError != jsonError.error && doc.isNull())
{
qDebug() << "read json file failed : " << jsonError.error;
return;
}
// 获取根节点
QJsonObject root = doc.object();
// 根据键获取值
QJsonValue nameValue = root.value("name");
ui->textEdit->append(QString("name:") + QString(nameValue.toString()));
QJsonValue ageValue = root.value("age");
ui->textEdit->append(QString("age:") + QString::number(ageValue.toInt()));
QJsonValue vipValue = root.value("vip");
ui->textEdit->append(QString("vip:") + QString(vipValue.toBool() ? "true" : "false");
// 解析对象
QJsonValue interestValue = root.value("interest");
// 判断是否为object类型
if (QJsonValue::Object == interestValue.type())
{
// 转为QJsonObject类型
QJsonObject interestObject = interestValue.toObject();
QJsonValue basketBallValue = interestObject.value("basketBall");
ui->textEdit->append(QString("basketball:") + basketBallValue.toString());
QJsonValue badmintonValue = interestObject.value("badminton");
ui->textEdit->append(QString("badminton:") + badmintonValue.toString());
}
// 解析数组
QJsonValue colorValue = root.value("color");
// 判断是否是Array类型
if (QJsonValue::Array == colorValue.type())
{
// 转换为QJsonArray类型
QJsonArray colorArray = colorValue.toArray();
QJsonValue color;
for (int i = 0; i < colorArray.size(); i++)
{
QJsonValue color = colorArray.at(i);
ui->textEdit->append(QString("color:") + color.toString());
}
}
}
ini
用于初始化程序。Windows系统后来以注册表的形式取代ini文件。
格式:
节/组 [section]
参数 name=value
注:name不要使用中文,否则会对读取造成麻烦。
注解
使用;表示。分号后面的文字,直到该行结束全为注解。
;comment text
//保存
QSetting setting(“YourOrg”,”YourApp”);
settings.beginGroup(“MainWindow”);
settings.setValue(“geomotry”,this->geometry());
settings.endGroup();
//加载
QSettings settings(“YourOrg”,”YourApp”);
settings.beginGroup(“MainWindow”);
QRect savadRect=settings.value(“geometry”).toRect();
this->setGeometry(savedRect);
settings.endGroup();
在windows系统中,这些信息是默认保存在注册表中,也可将其保存在文件中:
QSettings settings(“config.ini”,QSetting::IniFormat);
打开文件config.ini,可以看到:
[MainWindow]geometry=@Rect(0 0 600 400)
作业6
直到现在,⼀旦我们将思维导图软件关闭,界⾯上所有修改都会丢失。
因为数据都是保存在内存中的,为了能够在下次打开软件时恢复数据,我们需要将思维导图数据保存到⽂件中,请实现下⾯的功能:
-
通过菜单:⽂件 - 保存,或者通过快捷键Ctrl+S,将界⾯中的思维导图树保存到本地的配置⽂件中
-
通过菜单:⽂件 - 打开,或者通过快捷键 Ctrl+O,打开本地配置⽂件,并初始化左侧的树、以及中间的思维导图区域
配置⽂件可采⽤xml或者json任⼀种,参考格式如下:
<?xml version="1.0" encoding="utf-8"?>
<MindMap>
<Item type="group" name="⽣物">
...(不往下写了,请⾃⼰设计配置⽂件格式)
</Item>
</MindMap>
帮助与提示
- 需要在配置⽂件中保存每个树节点的⽂字、类型(组节点还是叶⼦节点)
- 配置⽂件保存:建议先根据树结构将数据保存到结构体中,再写⼊配置⽂件
QPainter绘图
通过QPainter实现Qt二维绘图:
1. 几何图形,点、线、多边形等;
2. 特殊效果,如渐变;
3. 变换:平移、旋转、缩放等。
用法
绘图时,需实现paintEvent函数,并创建QPainter对象。
void CustomWidget::painterEvent(QPainterEvent* event)
{
QPainter painter(this);
QPen pen(QColor(255,0,0));
pen.setWidth(3);
pen.setStyle(Qt::DotLine); //设置画笔风格
painter.setPen(pen);
QBrush brush(Qt::cyan);
brush.setStyle(Qt::Dense5Pattern);
painter.setBrush(brush);
//设置抗锯齿
painter.setRenderHint(QPainter::Antialiasing);
painter.drawLine(QPoint(0,0), QPoint(100,100));
painter.drawEllipes(QPoint(100,100), 50, 50);
painter.drawRect(QRect(20,20,50,50));
QFont font;
font.setFamily("Microsoft YaHei");
font.setPixelSize(20);
font.setBold(true);
painter.setFont(font);
//painter.setFont(QFont("华文彩云", 20);
painter.drawText(QRect(0,200,150,100), Qt::AlignCenter, "好好学习,天天向上");
//画成品图案
QPixmap pix;
pix.load(":/Image/Luffy.png");
pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);
if(posX > this->width())
posX = -pix.width();
painter.drawPixmap(posX, 0, pix);
}
{
posX = 0;
//点击移动按钮,移动图片
connect(ui->btn_move, &QPushButton::clicked, [=](){
posX += 10;
//手动调用绘图事件!!!!
update();
});
}
{
//实现自动让图片向右移动
QTimer* timer = new QTimer(this);
timer->start(10);
connect(timer, &QTimer::timeout, [=](){
posX++;
//!!!
update();
});
}
// 平铺图 通过平铺,可以将一张图片铺到整个widget中,常用于绘制棋盘形状的网格背景
painter.drawTiledPixmap(this->rect(), m_pixmap);
对于平铺,比如原图如下:
平铺之后:
画笔
通过QPainter::setPen()设置画笔,使用画笔设置线的属性。如:线宽、线色、线型
画刷
通过QPainter::setBrush()设置画刷,用于控制绘图时的填充属性:填充色、填充样式。
QBrush还可以设置渐变色:
QLinearGradient gradient(0, 0, 200, 0);
gradient.setColorAt(0, QColor(255, 0, 0));
gradient.setColorAt(0.5, QColor(0, 255, 0));
gradient.setColorAt(1, QColor(0, 0, 255));
QBrush brush(gradient);
painter.setBrush(brush);
QPainter绘制接口
使用QPainter可以画各种各样的图形或文字等元素,如:
1. 点:QPainter::drawPoint()
2. 线:QPainter::drawLine()
3. 矩形:QPainter::drawRect()
4. 圆角矩形:QPainter::drawRoundedRect()
5. 多边形:QPainter::drawPolygon()
6. 扇形:QPainter::drawPie()
7.弧线:QPainter::drawArc()
8. 文字:QPainter::drawText()
9. 图片:QPainter::drawPixmap()
...
除此之外,还有⼀个特殊的接⼝:
QPainter::drawPath(const QPainterPath &path);
通过 QPainterPath 可以根据连接路径来绘图,可以添加弧形、⻉塞尔曲线等路径,绘制出复杂的、不规则的形状。或通过它将矩形、椭圆、线等简单图形进行组合,还可以按照自定义路径进行绘制。
QPainterPath path;
path.moveTo(20, 80);
path.lineTo(20, 30);
path.cubicTo(80, 0, 50, 50, 80, 80);
QPainter painter(this);
painter.drawPath(path);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::green);
QPainterPath path;
path.addRect(10, 10, 200, 300);
path.addRect(250, 10, 200, 300);
painter.drawPath(path);
试想,如果这两个图形存在重合会发生什么?
QPainterPath有一个设置填充规则的接口,根据指定的规则来判断一个点是否在我们的图形中:
void QPainterPath::setFillRule(Qt::FillRule fillRule);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::green);
QPainterPath path;
path.setFillRule(Qt::OddEvenFill); //默认值即为Qt::OddEvenFill
path.addEllipse(QPointF(200, 200), 100, 100);
path.addEllipse(QPointF(266, 200), 100, 100);
painter.drawPath(path);
若把填充规则改为path.setFillRule(Qt::WindingFill);绘制的效果就变成了:
常用的绘图设备
QPixmap QBitmap QImage QPicture QWidget
坐标系统
在默认的坐标系中,点 (0, 0) 位于⼀个 widget 的左上⻆,x 坐标向右增⻓,y 坐标向下增⻓。
坐标系可以进⾏⼀些变换:
平移:QPainter::translate()
旋转:QPainter::rotate()
伸缩:QPainter::scale()
……
原始坐标系统如图 1左所示;若执行平移函数 translate(150,100),则坐标系统水平向右平移 150 像素,向下平移 100 像素,平移后的坐标系统如图 1 右所示,坐标原点在窗口的中心,而左上角的坐标变为(-150,-100),右下角的坐标变为(150.100)。如此将坐标原点变换到窗口中心在绘制某些图形时是非常方便的。
将坐标系统绕坐标原点顺时针旋转 angle 角度,单位是度。当 angle 为正数时是顺时针旋转,为负数时是逆时针旋转。
在图1右的基础上,若执行 rotate(90),则得到图2所示的坐标系统。在图1 的新坐标系下,窗口左上角的坐标变成了(-100,150),而右下角的坐标变成了(100,-150)。
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
使用QWidget进行自定义控件、窗口、创建布局时,存在一些局限性。当界面中图形元素很多,或需进行复杂变换时,QWidget处理棘手。此时,可选用Graphics View框架。
Graphics View提供了一个用于管理大量图形元素及其交互方式,且提供了用于显示这些元素并支持放缩、旋转的widget。
在图形元素多、坐标转换多时,更偏向与使用Graphics View,而非QWidget。
它是一种基于图形项(GraphicsItem)的模型/视图模式,这种方式可以在一个场景中绘制大量图元项,每个图元项都是可选择、可交互的。
QPainter采用面向过程的描述方式绘图,Graphics View采用面向对象的描述方式绘图。对于复杂的图像来说,若图像包含大量直线、曲线、多边形等图元对象,则管理图元对象比管理QPainter的绘制过程语句更容易。且图元对象更符合面向对象的思想,图形的可复用性更好。
场景QGraphicsScence
QGraphicsScence提供了图形显示场景,主要负责:提供用于管理大量item的场景;将事件传给每个item;管理item的状态,如选中和聚焦处理。
场景(Scene)是QGraphicsItem对象的容器,通过调用QGraphicsScene::addItem()添加Item,再获取item。
QGraphicsScene* pscene = new QGraphicsScene();
视图QGraphicsView
QGraphicsView继承自QWidget,用于显示场景中的内容。
QGraphicsView* pView = new QGraphicsView();
pView->setScene(pScene);
图元QGraphicsItem
QGraphicsItem是场景中图元的基类,如矩形(QGraphicsRectItem)、椭圆(QGraphicsEllipseItem)、文本(QGraphicsTextItem)。不过更强大的是,你可实现自定义item。QGraphicsItem支持以下特性:
-
鼠标按下、移动、松开和双击事件,鼠标悬浮事件、滚轮事件、上下文菜单事件等;
-
键盘输入、键盘事件
-
Drag、Drop
-
分组
-
碰撞检测
标准图元,如:
QGraphicsEllipseItem 椭圆
QGraphicsLineItem 直线
QGraphicsPathItem 路径
QGraphicsPixmapItem 图像
QGraphicsPolygonItem 多边形
QGraphicsRectItem 矩形
QGraphicsSimpleTextItem 简单文本
QGraphicsTextItem 文本
QGraphicsItem 图元的基类,用户可继承QGraphicsItem实现自定义图元。
若是自定义图元,首先需继承QGraphicsItem,然后实现以下的纯虚函数:
-
boundingRect(),返回可绘制区域的范围
-
paint(),进行绘图
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使用 Q_PROPERTY() 宏在一个类中声明一个属性,他们通过Qt的元对象系统增加了一些额外的特性。该宏是Qt特有的,需moc进行编译,必须继承QObject类。该宏原型如下:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL]
[REQUIRED])
其中,
1. 小括号“()”中的关键字是必须实现的,中括号“[]”中的关键字是可选的;
2. type name:属性的类型和名字。属性类型可以是QVariant支持的任何类型,也可以是自定义类型;
3. 如果没有MEMBER,就必须要有READ。READ后面定义获取属性值的函数,该函数是const的。WRITE设置属性值,是可选的;
4. NOTIFY定义信号函数,只要该属性值发生改变,就会发出该信号。该信号函数必须采用零个或一个参数,该参数必须与属性类型相同。
说明:属性是可以继承的。
使用元对象系统读写属性
一个属性可以使用常规函数 QObject::property() 和 QObject::setProperty() 进行读写,除
了属性的名字,不用知道属性所在类的任何细节。
比如 QPushButton 的基类 QAbstractButton 有一个属性叫做 down ,描述的是该按钮是否被按
下。通过这个属性,我们可以在不调用 QAbstractButton 函数而是通过属性修改这个值
QObject *pButton = new QPushButton(this);
bool bDown = pButton->property("down").toBool();
pButton->setProperty("down", !bDown);
//可以通过 QMetaObject 和 QMetaProperties 查询一个对象的所有属性和对应的值
QObject *pButton = new QPushButton(this);
const QMetaObject *metaobject = pButton->metaObject();
int count = metaobject->propertyCount();
for (int i=0; i<count; ++i)
{
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = pButton->property(name);
qDebug() << name << value;
}
动态属性
QObject::setProperty() 也可以用在运行时向一个类的实例添加新的属性。
当使用一个名字和值调用它时,如果QObject中该名称的属性已经存在,并且给定的值与属性类
型兼容,那么,值就被存储到属性中,然后返回true。如果值与属性类型不兼容,属性的值就不
会发生改变并返回false。
但是如果QObject中该名称的属性不存在,该指定名称和值的新属性就被自动添加到QObject中,但是依然会返回false。
QObject *pButton = new QPushButton(this);
pButton->setProperty("hgtec", true);
bool bIs = pButton->property("hgtect").toBool(); //true
注意: 动态属性的属性类型是以QVariant存储的,因此如果需要存储自定义的数据类型,需
要 Q_DECLARE_METATYPE() 注册
动画
//构造时指定要控制哪个对象的哪个属性
QPropertyAnimation* pAnimation = new QPropertyAnimation(pLabel, "geometry");
//设置持续时间
pAnimation->setDuration(1000);
//设置开始值
pAnimation->setStartValue(QRect(190, 230, 0, 0));
//设置结束值
pAnimation->setEndValue(QRect(120, 160, 140, 140));
//设置动画曲线
pAnimation->setEasingCurve(QEasingCurve::InOutQuad);
//启动动画,并在动画停止后释放该动画
pAnimation->start(QAbstractAnimation::DeleteWhenStopped);
串行动画组
QSequentialAnimationGroup* pPosGroup = new QSequentialAnimationGroup(this);
pPosGroup->addAnimation(pFirstScaleAnimation); //添加第一个动画
pPosGroup->addPause(500); //添加一个500毫秒的暂停,详见3.4
pPosGroup->addAnimation(pSecondScaleAnimation); //添加第二个动画
pPosGroup->start(); //启动动画
//添加到动画组内的动画,不再需要单独启动。只需要启动动画组即可,内部的动画会由动画组来控制启动和停止
并行动画组
QParallelAnimationGroup* pPosGroup = new QParallelAnimationGroup(this);
pPosGroup->addAnimation(pFirstScaleAnimation); //添加第一个动画
pPosGroup->addAnimation(pSecondScaleAnimation); //添加第二个动画
pPosGroup->start(); //启动动画
//假设第一个动画持续时间为200毫秒,第二个动画持续时间为500毫秒。那么动画组开始后,两个动画会同时开始。
//200毫秒后第一个动画先结束,500毫秒后第二个动画也结束。此时,整个并行动画组结束
暂停动画
//在串行动画组中插入 QPauseAnimation ,就可以实现延时播放的效果
QSequentialAnimationGroup* pPosGroup = new QSequentialAnimationGroup(this);
pPosGroup->addAnimation(pFirstScaleAnimation); //添加第一个动画
QPauseAnimation *pPauseAnimation = new QPauseAnimation(this);
pPauseAnimation->setDuration(500); //500毫秒的暂停动画
pPosGroup->addAnimation(pPauseAnimation);
pPosGroup->addAnimation(pSecondScaleAnimation); //添加第二个动画
pPosGroup->start();
//在串行动画组启动并且第一个动画结束后,会加入一个500毫秒的暂停,这段时间内串行动画处于空白期,什么都不会做。然后才会继续执行第二个动画。
//串行动画组本身提供了一个函数 addPause() 可以快速插入一个暂停动画,和构造一个 QPauseAnimation 然后通过 addAnimation() 插入从效果上看是完全一样的
动画方向
- QAbstractAnimation::Forward 默认值,正向,动画会按照正常顺序执行。
- QAbstractAnimation::Backward 反向,动画会从持续时间的最后往0方向执行
QPushButton* pButton = new QPushButton("按钮", this);
//设置动画
QPropertyAnimation* pPosAnimation = new QPropertyAnimation(pButton, "pos");
pPosAnimation->setDuration(1000);
pPosAnimation->setStartValue(QPoint(0, 0));
pPosAnimation->setEndValue(QPoint(100, 150));
//按钮点击后处理
connect(pButton, &QPushButton::clicked, [=](){
//根据按钮勾选状态
pPosAnimation->setDirection( \
(pPosAnimation->direction()==QAbstractAnimation::Backward) ? \
QAbstractAnimation::Forward : QAbstractAnimation::Backward);
//如果动画正在运行
if(pPosAnimation->state() == QAbstractAnimation::Running)
{
//则先停止动画
pPosAnimation->stop();
}
//启动动画
pPosAnimation->start();
});
循环次数
- 设置为正整数n时,会循环n次
- 设置为0时,动画不会启动
- 设置为-1时,动画会无限循环,直到主动停止动画
作业7
真相⼤⽩的时候到了!前⾯思维导图库就是通过GraphicsView实现的。
⾸先尝试下⾯2个⼩练习:
在 QWidget 窗⼝中⼼画⼀个边框红⾊、背景绿⾊、半径为 150 像素的圆形。
⿏标在边框区域按下,将该圆改为边框绿⾊、背景红⾊;
⿏标松开,恢复圆的边框为红⾊,背景为绿⾊。
使⽤ QGraphicsView,⼀个边框红⾊、背景绿⾊、半径为 150 像素的圆形 item。
将 item 放⼤ 1.5 倍,并旋转 90 度。
接下来实现下⾯的功能:
使⽤QGraphicsView,在场景中创建2个item,分别显示⽂字“⽜郎”、“织⼥”,并创建⼀条连线连接这两项。可通过⿏标拖动这两项,线也会跟根据item位置变化,始终连接这两项。
时间与定时器
定时器
Qt提供了2种使用定时器的方法,一是使用QTimer类,二是使用QObject提供的定时器接口和事件。
方式一 QTimer
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()));
方式二 QTimerEvent
// 启动⼀个定时器,返回定时器的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");
网络
字节对齐
//针对项目中的场景,我们指定数据结构以1字节方式对齐,足够我们解决绝大多数问题
//开始指定字节对齐为1字节
#pragma pack(push, 1)
#pragma pack(show) //查看当前编译器的字节对齐数
struct Node
{
int num;
char c;
};
//结束指定的字节对齐,恢复默认对齐
#pragma pack(pop)
#pragma pack(show) //查看当前编译器的字节对齐数
UDP服务端(发送方)
m_pUdpSocket = new QUDPSocket(this);
//...
QByteArray datagram = "Broadcast message " + QByteArray::number(messageNo);
m_pUdpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 45454);
UDP客户端(接收方)
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()));
}
}
线程与进程
在进行桌面应用程序开发时,假设应用程序在某些情况下需要处理比较耗时的操作,若只有一个线程去处理,会导致程序无法及时响应用户的窗口操作,表现在窗口卡顿。需要使用多线程,其中主线程处理窗口事件,其他线程进行逻辑运算或其他耗时操作,可提高用户体验和程序执行效率。
默认的线程在Qt中称为窗口线程,也叫主线程,负责窗口事件处理或者窗口渲染的更新。
进程内其他线程为子线程,子线程不能对窗口做任何操作,否则会导致程序大概率崩溃。
QThread
通过QThread创建线程有两种方式,一种是子类化QThread 并重写 run 函数实现,一种是把需要
在子线程中执行的对象通过 QObject::moveToThread 整体放到线程中执行。
QObject::moveToThread 的方式把逻辑实现和线程的控制完全剥离开,并通过信号槽在控制
端、逻辑端、线程端传递消息,结构虽稍微复杂但更适合程序扩展和修改,尤其是对规模和复杂
度较大的程序。实际开发中推荐优先考虑 QObject::moveToThread 的方式Qt还提供了 QMutex 、 QReadWriteLock 等保证线程同步。
QThread的常见接口如下:
- start ,启动线程,是子线程执行起来。在支持的操作系统上可通过形参调整子线程的运行优先级
- run ,虚函数,此函数内的代码运行于子线程内。调用 start 函数后其内部最终会执行到 run
- terminate ,强制停止线程。线程可能不会马上停下来,所以一般需要配合 wait 使用
- exec ,protected函数,使线程进入实现循环。默认不执行。只能在子线程内即 run 函数内调用(解决没有时间循环时槽函数响应不到的问题)
- exit ,退出时间循环,只有执行了 exec 之后才有效果。 quit 即 exit(0)
- isRunning ,判断线程是否在运行
- sleep ,静态方法,强制当前线程休眠一定时间
- started , finished ,信号,线程启动和结束时分别会发送
生存线程:一个QObject对象,它在哪个线程中构造,其生存线程就是哪个。
多线程开发中这是一个需要特别关注的点,经常会发现写好了线程但是某些函数执行的时候会阻
塞主线程,大概率是因为信号槽的连接方式有问题!在连接信号槽时直接指定 Qt::DirectConnection 或 Qt::QueuedConnection 。
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();
}
//总结:这种在程序中添加子线程的方式是非常简单的,但是也有弊端,假设要在一个子线程中处理多个任务,
//所有的处理逻辑都需要写到 run 函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护
在线程中运行时,若需要通知界面,通常使用发送信号的方式。
Qt内能够被元系统识别的数据类型只有Qt提供的数据类型和c++基础类型如int、double等,其他
如STL数据类型或自定义的数据结构等,需要在信号槽连接之前,通过 qRegisterMetaType() 注册
才能够被元系统识别,否则在运行时会得到如下错误:
QObject::connect: Cannot queue arguments of type 'YourCustomType'
struct MyStruct
{
int num;
};
void initSignalAndSlot()
{
qRegisterMetaType<MyStruct>();
qRegisterMetaType<std::string>();
connnect(first, SIGNAL(firstSginal(MyStruct)), second,SLOT(secondSlot(MyStruct)), Qt::QueuedConnection);
connnect(first,SIGNAL(firstSginal(std::string)),second,SLOT(secondSlot(std::string));
}
注意启动线程不能直接调用 run ,通过调用 start 线程内部会自动执行 run 函数。 run 函数
运行于子线程中,其他函数除非被 run() 方法直接调用或同步调用,否则都运行于其生存线程
内。
注意:多线程的退出与释放,是极其容易导致程序崩溃的点,一定要非常关注。
除此之外,还有moveToThread、QtConcurrent、线程池等方式使用线程。使用QMutex进行互斥处理。
GUI线程
当一个Qt程序启动时,就有一个进程被操作系统创建,同时一个线程也立即运行,这个线程称主线程,也叫GUI线程。
Qt中,所有与界面有关的操作只能在主线程中进行,不能在其他线程中处理界面,否则很可能造成程序崩溃。
线程同步
void GxFileSaver::push(const QString &str)
{
m_mutex.lock(); //上锁,写保护
m_queueContents.enqueue(str);
m_mutex.unlock(); //解锁,尽量减少锁保护的代码段
}
void GxFileSaver::work()
{
...
//取出一个操作请求
m_mutex.lock(); //上锁,读保护
QString strContent = m_queueContents.dequeue();
m_mutex.unlock(); //解锁,尽量减少锁保护的代码段
...
}
if(1)
{
QMutexLocker locker(&mutex); //QMutexLocker实现自动解锁
...//do something
}
//弊端是使用 QMutexLocker 大概率会扩大锁的锁定范围,有可能导致程序性能下降
作业8
- 计算过程中界⾯不能卡顿;
- 程序关闭时⽴即退出,并且不会出现崩溃;
- 分别使⽤QThread和moveToThread两种⽅式实现。
图表
Qt4未提供图标库,但是你可以通过使用QGraphicsView自己封装,也可以使用第三方库。Qt5中提供了图表库QCharts。
折线图、饼图、柱状图
桌面信息
QList<QScreen *> screens = QApplication::screens();
foreach(QScreen* pScreen, screens)
{
QPushButton *pButton = new QPushButton(pScreen->name(), this);
if (pScreen == qApp->primaryScreen()) //是否是主屏幕
{
pButton->setChecked(true);
}
}
...
QRect rectScreen = pScreen->geometry();
m_pBall->setGeometry(rectScreen);
正则表达式
在项目中经常会遇到对字符串进行操作的情况,我们可以直接使用QString的一些函数,但Qt提
供了一个更加强大的类——QRegExp,使用正则表达式来操作字符串。
QRegExp的常用场景:
- 对输入框LineEdit中的输入内容加以限制,比如只能输入数字,并且最多5位数(因为int类型不限制位数会导致溢出问题);
- 检查输入是否正确,比如判断是否是1-9999之间的数;
- 获取一个字符串中的一段内容,比如获取2015-11-20中的2015
用途:
1. 验证
判断字符串是否符合某个标准,比如“是一个整数”或者“没有空格”。
2. 搜索
正则表达式提供了比普通字符串匹配更为强大的匹配方式,比如匹配下面的词语:
mail, letter, correspondence,但是不包括email, mailman, letterbox等等。
3. 查找并替换
正则表达式能够用一个不同的字符串,替换所有出现另一个字符串的地方,比如用&替换&,如果原先&后面已经有了amp;那么不替换。
4. 分割字符串
比如,根据tab来分割字符串。
在Qt中的用法如下:
1. 对用户输入的限制
void QLineEdit::setValidator(const QValidator * v) QLineEdit中的这个函数意思是,令LineEidt只接受验证器v所匹配的输入,你可以对要输入的内容进行任意的限制。
比如:限制输入框只能输入0到99999
QRegExp regExp("0|[1-9]\\d{0,4}");
ui.lineEdit->setValidator(new QRegExpValidator(regExp, this));
Qt内置了一些已经实现好的验证器,如QIntValidator用来保证用户输入数字,QDoubleValidator用来保证用户输入浮点数的范围
//只允许用户输入10到20之间的数字
ui.lineEdit->setValidator(new QIntValidator(10, 20, this));
//只允许用户输入10.00-20.00之间的数字,且小数点最多2位
i.lineEdit->setValidator(new QDoubleValidator(10, 20, 2, this));
2. 检查字符串
函数原型为 QValidator::State QRegExpValidator::validate(QString &input, int &pos) const
- 如果输入与正则表达式相匹配,则返回 QValidator::Acceptable ;
- 如果部分匹配,则返回 QValidator::Intermediate (部分匹配,意思是如果给它增加额外的字符则能够匹配正则表达式);
- 如果不匹配则返回 QValidator::Invalid。
// 整形[1,9999]
QRegExp rx("[1-9]\\d{0,3}");
QRegExpValidator v(rx);
QString s;
int pos = 0;
s = "0"; v.validate(s, pos); // returns Invalid
s = "12345"; v.validate(s, pos); // returns Invalid
s = "1"; v.validate(s, pos); // returns Acceptable
void GxRegExp::slotInputChanged(const QString& strText)
{
QRegExp rx("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.([a-zA-Z0-9_-]+)){1,3}$");
QRegExpValidator v(rx);
int pos = 0;
QString strEmail = strText;
QValidator::State state = v.validate(strEmail, pos);
switch(state)
{
case QValidator::Invalid:
{
ui->labelFit->setText("不匹配");
ui->labelDomain->clear();
}
break;
case QValidator::Intermediate:
{
ui->labelFit->setText("部分匹配");
ui->labelDomain->clear();
}
break;
case QValidator::Acceptable:
{
ui->labelFit->setText("匹配");
grabDomain(strEmail);
}
break;
}
}
void GxRegExp::grabDomain(const QString& strEmail)
{
QRegExp rxlen("@([\\w-.]+)");
int pos = rxlen.indexIn(strEmail);
if (pos > -1)
{
QString value = rxlen.cap(0);
value.remove("@");
ui->labelDomain->setText(value);
}
}
3. 截取字符串
函数原型为 QString QRegExp::cap(int nth = 0) const
这个函数返回被第n个子表达式捕获的文本,整个匹配拥有下标0,带括号的子表达式下标从1开始。
QRegExp rxlen("(\\d+)(?:\\s*)(cm|inch)");
int pos = rxlen.indexIn("Length: 189cm");
if (pos > -1)
{
QString value = rxlen.cap(1); // "189"
QString unit = rxlen.cap(2); // "cm"
// ...
}
程序打包部署
准备工作
Qt 4 程序打包
要部署应⽤程序,必须确保将相关的动态库(dll ⽂件)以及可执⾏⽂件(exe ⽂件)复制到同⼀⽬录中。
可以通过使⽤依赖⼯具来检查你的应⽤程序需要链接哪些库,⽐如 Dependency Walker。插件的⼯作⽅式与普通的 dll 不⼀样,所以我们不能像对台 dll 那样,直接将它们复制到与应⽤程序可执⾏⽬录相同的⽬录中。在寻找插件时,应⽤程序会在应⽤程序可执⾏⽂件⽬录内的插件⼦⽬录中进⾏搜索。
以 Qt 4.8.7 为例,Qt ⾃带的插件在安装⽬录下的 plugins ⽬录下,⽐如: C:/qt-4.8.7/plugins 。
所以,为了能使⽤这些插件,必须创建插件⼦⽬录,并将相关的 dll 复制过来。
除此之外,还需考虑资源⽂件、配置⽂件等是否也加了进来,并且确保路径正确。
综上所述,Qt4 程序打包时需考虑以下内容:
可执⾏⽂件(exe)
可执⾏⽂件链接的动态库(dll)
插件(插件⼦⽬录、插件dll)
资源⽂件、配置⽂件
其它
Qt 5 程序打包

windeployqt xxx.exe
