1 引言
1.1 QT
- QT是一个跨平台的C++图像用户界面应用程序框架
- QT在1991年由奇趣科技开发
1.2 QT的优点
- 跨平台,几乎支持所有平台
- 接口简单,容易上手
- 一定程度上简化了内存回收机制
- 有很好的社区氛围
- 可以进行嵌入式开发
2 QWidget
2.1 QWidget关系图
2.2 QT注意事项
- 命名规范
- 类名 首字母大写,单词和单词之间首字母大写
- 函数名 变量名称 首字母小写,单词和单词之间首字母大写
- 快捷键
- 注释
ctrl + /
- 运行
ctrl + r
- 编译
ctrl + b
- 查找
ctrl + f
- 帮助文档
F1
- 自动对齐
ctrl + i
- 同名的.h和.cpp切换
F4
- 注释
2.3 常用API
show()
以顶层方式弹出窗口控件setParent()
指定父类对象setText()
设置文本resize()
重置窗口大小move()
移动setWindowTitle()
设置窗口大小setFixedSize()
设置固定窗口大小
2.4 QT中的对象树
在Qt中创建一个对象时,需要提供一个Parent对象指针。下面解释这个Parent对象指针的作用:
QObject是以对象树的形式组织起来
- 当创建一个QObject对象时,会看到QObject对象的构造函数接收一个父对象指针parent。也就是说,我们在创建一个对象时,提供一个父对象指针,我们创建的这个对象会自动添加到父对象的children()列表。
- 当父对象析构时,这个列表中的所有对象也会被析构。仔细想一下,这种设计理念十分合理。比如说,我们创建了一个按钮,按钮有一个快捷键对象作为其子对象,当我们删除按钮时,这个按钮的快捷键理应删除。
- 当创建的对象在堆区的时候,如果指定的父亲是QObject 派生下来的类或者子类派生下来的类,可以不需要管理释放操作,会将对象放入对象树一定程度上简化了内存回收机制
指定父对象的两种方法:
setParent()
- 通过构造函数传参,例如:
this->zt = new Teacher(this);
获取对象与其父对象的名称:
qDebug()<<zt->metaObject()->className();
qDebug()<<zt->parent()->metaObject()->className();
2.5 QT的窗口坐标系
笛卡尔坐标系[左上角为0,0点]
2.6 QT信号和槽
(1) 基本语法
connect( 信号的发送者(指针)
,信号
, 信号的接受者(指针)
,信号的处理[槽函数]
)
(2)信号槽的优点 松散耦合
信号发送端 和 接收端本身是没有关联的,通过connect连接,将两者耦合在一起
事件:点击按钮,关闭主窗口
connect(btn,&QPushButton::clicked,this,&QWidget::close)
信号关键字:Signals
以QPushButton
为例:
clicked(bool) 点击
pressed() 按下
released() 释放
toggled(bool) 切换状态
槽的关键字:Slots
(3)自定义信号和槽位函数
自定义信号
* 写在类的signals下,返回值为void,可以有参数,支持重载,不需要实现
自定义槽函数
(1)不能写在signals下,public slots[公共的槽函数] 5.4版本以后全局函数或者public都行
(2)返回值也是void,需要声明,也需要实现,可以有参数,支持重载。
使用用法: 首先用connect连接信号和槽, 触发信号 emit
+ 信号和槽函数的重载,需要函数指针
,明确指向函数的地址
void (Teacher:: *teacherSignal)(QString) = &Teacher::hungry;
void (Student:: *studentSlot)(QString)=&Student::treat;
QString
类型的变量转为char *
要使用.toUtf8().data()
,例如:foodName.toUtf8().data()- 信号和槽连接 :触发这个信号才能触发槽
* 信号是可连接信号的
* 一个信号可以连接多个槽
* 多个信号也可以连接同一个槽函数
* 信号和槽的参数和类型必须对应
* 信号的参数个数可以多于槽的参数个数
函数指针示例:
//信号-老师饿了,槽函数-学生请客
void (Teacher:: *teacherSignal)(QString) = &Teacher::hungry;
void (Student:: *studentSlot)(QString)=&Student::treat;
- 断开信号
disconnect(parameters list)
,参数列表与connect一样
2.7 Lambda表达式
基本概念:
Lambda表达式是C++11版本特性,在.pro文件中用CONFIG += c++11
表示使用,Lambda表达式是匿名函数对象。
基本语法:
[capture](parameters) mutable ->return-type{statement}
1. [capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。捕捉列表描述了上下文中哪些数据可以被Lambda使用,
以及使用方式(以值传递的方式或引用传递的方式)。
1). [var]表示值传递方式捕捉变量var;
2). [=]表示值传递方式捕捉所有父作用域的变量(包括this);
3). &var]表示引用传递捕捉变量var;
4). [&]表示引用传递方式捕捉所有父作用域的变量(包括this);
5). [this]表示值传递方式捕捉当前的this指针。
2. (parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;
3. mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。
4. ->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。
此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;
5. {statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
示例代码:
#include<iostream>
using namespace std;
int main()
{
int j = 10;
auto by_val_lambda = [=]{ return j + 1; };
auto by_ref_lambda = [&]{ return j + 1; };
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;//11
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;//11
++j;
cout<<"by_val_lambda: "<<by_val_lambda()<<endl;//11
cout<<"by_ref_lambda: "<<by_ref_lambda()<<endl;//11
return 0;
}
3 QMainWindow
QMainWindow包含
3.1 菜单栏 QMenuBar
// 菜单栏最多只能有一个
QMenuBar * bar = menuBar(); setMenuBar(bar);
// 创建菜单
QMenu * fileMenu = bar->addMenu("文件");
// 创建菜单栏目
QAction * newAction = fileMenu->addAction("新建");
//添加分隔符
fileMenu->addSeparator();
3.2 工具栏 QToolBar
//工具栏可以有多个
QToolBar * toolBar = new QToolBar(this);
addToolBar(toolBar);
//可选参数 默认停靠范围
addToolBar(Qt::BottomToolBarArea,toolBar);
//只允许左右停靠
toolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
//取消浮动
toolBar->setFloatable(false);
//设置禁止移动
toolBar->setMovable(false);
//给工具栏设置栏目
toolBar->addAction("绝了"或者QAction);
//给工具栏添加控件
toolBar->addWidget(QPushButton按钮);
3.3 状态栏 QStatusBar
//状态栏最多只能有一个
QStatusBar * stBar = statusBar();
setStatusBar(stBar);
//添加标签控件
QLabel * label = new QLabel("左侧提示的信息",this);
QLabel * label1 = new QLabel("右侧提示的信息",this);
stBar->addWidget(label);
stBar->addPermanentWidget(label1);
3.4 铆接部件 QDockWidget
// 铆接部件可以有多个
QDockWidget * dockWidget = new QDockWidget("浮动",this);
addDockWidget(Qt::BottomDockWidgetArea,dockWidget);
//放置位置-下面
//如果没有中心部件默认占满只允许上下
dockWidget->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
3.5 中心部件
// 中心内容也只能有一个
//文本窗口 QTextEdit
QTextEdit * edit = new QTextEdit(this);
setCentralWidget(edit); //设置中心部件
3.6 resource资源文件
- 将图片文件文件夹拷贝到项目文件夹下
- 右键项目->添加新文件->Qt->Qt recourse File
- res 生成 res.qrc
- 右键res.qrc->open in editor 编辑资源
- 添加前缀 添加文件
- 使用 “: + 前缀名 + 文件名”
3.7 小结
以上部件的接口函数区别是set还是add的方法:在窗口中,只能有一个的部件接口函数是set开头 ,可以允许多个存在的部件接口函数是add开头
#include "mainwindow.h"
#include<QMainWindow>
#include<QMenuBar>
#include<QToolBar>
#include<QDebug>
#include<QPushButton>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//重置窗口大小
resize(600,400);
//菜单栏-----最多只能有一个
//菜单栏创建
QMenuBar * bar =menuBar();
//将菜单栏放入到窗口中
setMenuBar(bar);
//创建菜单,有返回值
//bar->addMenu("文件");
QMenu * fileMenu=bar->addMenu("文件");
QMenu * editMenu=bar->addMenu("编辑");
//创建菜单项-addAction
//fileMenu->addAction("新建");//可以选中addAction查看其返回值类型,第一行参数
QAction *newAction = fileMenu->addAction("新建");
//添加分隔符
fileMenu->addSeparator();
//fileMenu->addAction("打开");
QAction *openAction =fileMenu->addAction("打开");
//工具栏----可以有多个
QToolBar * toolBar =new QToolBar(this);
addToolBar(Qt::TopToolBarArea,toolBar);//点界面上的冒号进行拖动
//后期设置 只允许左右停靠
//toolBar->setAllowedAreas(Qt::LeftToolBarArea|Qt::RightToolBarArea);
//设置浮动
toolBar->setFloatable(0);
//设置移动(总开关)
toolBar->setMovable(0);
//工具栏可以设置内容,同样可以利用addSeparator添加分割线
toolBar->addAction(newAction);
toolBar->addSeparator();
toolBar->addAction(openAction);
//工具栏中可以添加控件
QPushButton * btn = new QPushButton("嘻嘻");
toolBar->addWidget(btn);
//状态栏----只有一个
QStatusBar * stBar =statusBar();
//放置到窗口中
setStatusBar(stBar);
//放置标签控件
QLabel * label =new QLabel("提示信息",this);
stBar->addWidget(label);
//将提示信息放置到右侧
QLabel * label2 =new QLabel("右侧提示信息",this);
stBar->addPermanentWidget(label2);
//铆接部件(浮动窗口)-----------可以有多个
QDockWidget *dockWidget = new QDockWidget("浮动",this);
addDockWidget(Qt::BottomDockWidgetArea,dockWidget);
//设置后期停靠区域,只允许上下
dockWidget->setAllowedAreas(Qt::TopDockWidgetArea|Qt::BottomDockWidgetArea);
//设置中心部件
QTextEdit * textEdit =new QTextEdit(this);
setCentralWidget(textEdit);
}
MainWindow::~MainWindow()
{
}
4 对话框
4.1 基本概念
- (1) 模态对话框 :不可以对其他窗口进行操作
- QDialog dlg(this);
- dlg.exec();
- (2) 非模态对话框: 可以对其他窗口进行操作
- QDialog *dlg2 = new QDialog(this); //为了确保不释放,开在堆上
- dlg2->show();
- dlg2->setAttribute(Qt::WA_DeleteOnClose);//55号 用于按关闭键自动释放[QWidge的对象树是在关闭总的窗口才会全部释放]
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//点击新建按钮,弹出一个窗口
connect(ui->actionNew,&QAction::triggered,[=](){
//对话框-dialog,可分为:模态对话框和非模态对话框
//模态对话框---不可以对其他窗口进行操作,非模态----可以对其他窗口操作
// //1.模态的创建
// QDialog dlg(this);
// dlg.exec();//阻塞
// qDebug()<<"模态对话框";
//2.非模态对话框创建
QDialog *dlg=new QDialog(this);
dlg->show();
//不关闭mainWindow的情况下,不会释放掉dlg,所以需要我们人工设定避免内存泄露
dlg->setAttribute(Qt::WA_DeleteOnClose);
qDebug()<<"非模态对话框";
});
}
4.2 标准对话框
**概念:**所谓标准对话框是指Qt内置的一些列对话框,用于简化开发
4.2.1 消息对话框 QMessageBox
- 错误对话框 QMessageBox::critical(this,“critical”,“错误”);
- 信息对话框 information
- 提问对话框 question
- 警告对话框warning
//消息对话框
//crictical
//QMessageBox::critical(this,"crictical","错误");
//information
//QMessageBox::information(this,"info","信息");
//question,参数4是按键类型,参数5 按键默认关联
QMessageBox::StandardButton stdBtn=QMessageBox::question(this,"question","问题",QMessageBox::Save|QMessageBox::Cancel);
if(stdBtn)
{
qDebug()<<"选择的是保存";
}
else
{
qDebug()<<"选择的是取消";
}
//warning
QMessageBox::warning(this,"warning","警告");
4.2.2其他标准对话框
- **颜色对话框 **
- QColor a = QColorDialog::getColor(QColor(255,0,0));
- 文件对话框 最后一个是过滤
- QString str = QFileDialog::getOpenFileName(this,“打开文件”,“./”,“(*.cpp)”);
- **字体对话框 **
- bool flag;
- QFont font = QFontDialog::getFont(&flag,QFont(“华文彩云”,12));
- setFont(font);//设置字体
//颜色对话框
QColor c= QColorDialog::getColor(QColor(0,0,255));
qDebug()<<c.red()<<c.blue()<<c.green();
//文件对话框-参数1 父亲,参数2 标题,参数3 默认打开路径,参数4 过滤文件格式
QString str= QFileDialog::getOpenFileName(this,"打开","D:\\QtProject\\getLANUser","(*.txt)");
qDebug()<<str;
//字体对话框
bool flag;
QFont font =QFontDialog::getFont(&flag,QFont("华文彩云",36));
qDebug()<<"字体:"<<font.family().toUtf8().data()<<"字号:"<<font.pointSize()<<"是否加粗:"<<font.bold();
//问题描述:出现这种情况的可能是在使用Qt的时候采用了msvc编译,
源代码文件是无BOM的utf-8编码格式,QtCreator用MinGw编译OK,切换成MSVC后报错。
//在.pro文件中添加如下代码
msvc {
QMAKE_CFLAGS += /utf-8
QMAKE_CXXFLAGS += /utf-8
}
5 常用的控件
5.1 列表控件 QListWidget
- QListWidgetItem * item = new QListWidgetItem(“锄禾日当午”);
- ui->listWidget->addItem(item); //添加进去
- item->setTextAlignment(Qt::AlignCenter); //居中
功能要求:
在 QListWidget 中选中项鼠标右键显示:修改和删除,空白处鼠标右键是:添加、修改和删除。
实现方法:
- 开启 QListWidget 鼠标右键,
- 绑定鼠标右键事件
- 将坐标点由原来的全局坐标改为局部组件坐标
- 判断当前点击的点是否在,item 项上
ui->iniListWidget->setContextMenuPolicy(Qt::CustomContextMenu); // 设置鼠标右键
//UI界面,右键listWidget转到槽,建立槽函数
void on_iniListWidget_customContextMenuRequested(const QPoint &pos);
void ParaConfig::on_iniListWidget_customContextMenuRequested(const QPoint &pos)
{
Q_UNUSED(pos);
QMenu *menu = new QMenu(this);
QAction *mAdd;
QAction *mModify;
QAction *mDel;
mAdd = new QAction("添加", this);
mModify = new QAction("修改", this);
mDel = new QAction("删除", this);
if(ui->iniListWidget->itemAt(mapFromGlobal(QCursor::pos())) == nullptr){
menu->addAction(mAdd);
}
menu->addAction(mModify);
menu->addAction(mDel);
connect(mAdd, SIGNAL(triggered(bool)), this, SLOT(mAddActionSlot(bool)));
connect(mModify, SIGNAL(triggered(bool)), this, SLOT(mModifyActionSlot(bool)));
connect(mDel, SIGNAL(triggered(bool)), this, SLOT(mDelActionSlot(bool)));
menu->exec(QCursor::pos());
}
5.2 QTreeWidget
5.3 QTableWidget
5.4 QStackedWidget使用
//栈控件的使用
//设置默认页面
ui->stackedWidget->setCurrentIndex(1);
connect(ui->btn_ScrollArea,&QPushButton::clicked,[=](){
ui->stackedWidget->setCurrentIndex(1);
});
connect(ui->btn_ToolBox,&QPushButton::clicked,[=](){
ui->stackedWidget->setCurrentIndex(2);
});
connect(ui->btn_TabWidget,&QPushButton::clicked,[=](){
ui->stackedWidget->setCurrentIndex(0);
});
5.5 label控件显示图片和动图
//给lBl_image添加图片
ui->lbl_image->setPixmap(QPixmap(":/image/namei.jpg"));
//给lbl_movie添加动图
QMovie * qMovie =new QMovie(":/image/GIF.gif");
ui->lbl_movie->setMovie(qMovie);
//播放动图
qMovie->start();
5.6 ui窗口自布局
- Spacers 弹簧 Widget div盒子
- Group Box 分组[适用于Radio Button]
- 主窗口设置垂直布局后可以在sizePolicy->垂直策略->Fixed来使组件高度合适
- 如果找不到某个组件的信号或者槽,找基类
5.7 自定义组件
ui->setupUi(this);
//设置对象spinBox的值范围
ui->spinBox->setRange(0,100);
//设置对象horizontalSlider的值范围
ui->horizontalSlider->setRange(0,100);
//spinBox信号
//QSpinBox中信号重载,需要使用函数指针语法
void(QSpinBox:: * spnSignal)(int) = &QSpinBox::valueChanged;
connect(ui->spinBox, spnSignal,ui->horizontalSlider, &QSlider::setValue);
//Qslider滑动
connect(ui->horizontalSlider,&QSlider::valueChanged,ui->spinBox,&QSpinBox::setValue);
ui->setupUi(this);
//use pushButton GET VALUE
connect(ui->btn_get,&QPushButton::clicked,[=](){
qDebug()<<ui->smallWidget->getValue();
});
//use pushButton SET VALUE
connect(ui->btn_set,&QPushButton::clicked,[=](){
ui->smallWidget->setValue(50);
5.8 QCombox
signal:currentIndexChanged( int index)
触发
1.改变CurrentIndex肯定会触发该信号
2.如果当前QComboBox为空,即其中没有内容item,那么调用AddItems()方法,向其中增加item,
也会触发currentIndexChanged(int index)信号
3.如果当前QComboBox不为空,那么调用Clear()方法,也会触发currentIndexChanged(int index)
信号,此时,index等于-1
qt5中的信号槽绑定
QComboBox中的currentIndexChanged信号存在重载,在使用时,如果使用QT5新版的信号槽语法,须进行强制类型转换。
connect(ui->comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,&Widget::onCurrentIndexChanged);
connect(ui->comboBox, static_cast<void (QComboBox::*)(const QString&)>(&QComboBox::currentIndexChanged),this,&Widget::onCurrentStringChanged);
qt4中的信号槽绑定
connect(ui->comboBox,SIGNAL(currentIndexChanged(int)),this,SLOT(onCurrentIndexChanged(int)));
connect(ui->comboBox,SIGNAL(currentIndexChanged(QString)),this,SLOT(onCurrentStringChanged(QString)))
//函数指针重载
void (QComboBox:: * IndexChanged)(int) = &QComboBox::currentIndexChanged;
connect(ui->comboBox, IndexChanged, [=]() {
ui->SeverLineEdit->setText(ui->comboBox->currentText());
});
6 QT事件 QEvent
6.1 鼠标事件
- 事件是虚函数,可以进行重载
//鼠标进入事件
virtual void enterEvent(QEvent *event);
//鼠标离开事件
virtual void leaveEvent(QEvent *event);
//鼠标按下
virtual void mouseReleaseEvent(QMouseEvent *ev);
//鼠标释放
virtual void mousePressEvent(QMouseEvent *ev);
//鼠标移动
virtual void mouseMoveEvent(QMouseEvent *ev);
6.2 定时器 QTimeEvent
#include <QTimer>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//启动定时器,id_label_2和id_label_3需要在widget.h中进行声明一下
id_label_2=startTimer(1000);//参数表示时间间隔,单位:ms
id_label_3=startTimer(3000);
//定时器的第二种方式
QTimer *timer=new QTimer(this);
timer->start(500);
connect(timer,&QTimer::timeout,[=](){
static int num4=1;
ui->label_4->setText(QString::number(num4++));
});
//创建一个按钮,控制计时器的暂停开始
connect(ui->btn_pause,&QPushButton::clicked,[=](){
timer->stop();
});
//创建一个按钮,控制计时器的暂停开始
connect(ui->btn_start,&QPushButton::clicked,[=](){
timer->start();
});
}
void Widget::timerEvent(QTimerEvent *e)
{
//每隔1秒加一
if(e->timerId() == id_label_2)
{
static int num2=1;
ui->label_2->setText(QString::number(num2++));
}
if(e->timerId() == id_label_3)
{
static int num3=1;
ui->label_3->setText(QString::number(num3++));
}
}
- (1)利用事件实现定时器
- startTimer(1000); 启动定时器,单位毫秒,返回一个唯一定时器id
- void timerEvent(QTimerEvent * ev)
- 定时器函数,可以通过ev->timerId()== id1来判断当前是哪个id进来的
- (2)定时器类QTimer
//通过定时器类
QTimer * timer = new QTimer(this);
//启动定时器 每隔500秒发一个信号
timer->start(500);
//连接信号
connect(timer,&QTimer::timeout,中括号小括号{
static int num = 1;
ui->label_5->setText(QString::number(num++));
});
6.3 event事件分发器
bool myLabel::event(QEvent *e)
{
//如果鼠标按下,再event事件中做拦截操作
if(e->type() ==QEvent::MouseButtonPress)
{
QMouseEvent *ev =static_cast<QMouseEvent *> (e);
QString str =QString("QEvent中:x = %1,y = %2 ").arg(ev->x()).arg(ev->y());
qDebug()<<str;
//true代表用户自己处理这个事件,不向下分发
return true;
}
//其他事件 交给父类处理
return QLabel::event(e);
}
- bool event(QEvent * ev)
- 返回值是bool类型,如果返回true,代表用户要处理这个事件,不向下分发事件了[类似于钩子]
- 事件枚举QEvent
- ev.type();
- 拦截后使用子类的操作可以使用静态类型转换
- QMouseEvent _ev = static_cast(QEvent中形参);
- 但是尽量别拦截
- 但是尽量别拦截
6.3.1 额外补充静态类型转换static<target_type>()
//1.基本数据类型转换
char a = 'a';
double d = a;
//无法编译成功,需要使用静态类型转换
char a = 'a';
double d = static_cast<double>(a);
//2.父类转换为子类
Base * base = NULL;
Child * child = NULL;
//把base转为 Child*类型 向下 不安全
Child * child2 = static_cast<Child*>(base);
6.4 事件过滤器
bool Widget::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;
//true代表用户自己处理这个事件,不向下分发
return true;
}
}
return QWidget::eventFilter(obj,e);
}
6.5 常见Qt Creator使用问题:
使用Qt Creator集成开发环境进行项目编码时,经常需要重写父类的虚函数去实现自己想要的功能,
但是当声明这些函数时,经常遇到函数名可以自动补齐,但是函数的参数却不能自动补齐的情况,
可能是有些Qt Creator的版本支持只要敲函数名的前几个字符就会把函数名连同参数一并给补全,
但是博主使用的IDE版本中大都都只是补全函数名,函数参数不会自动补全。
解决方法:就是遇到函数声明参数不能补齐时,首先去掉声明的左右括号和分号;
然后,按住Shift键+'9'组合键;最后敲下回车键补齐分号。
6.6 绘图 QPainter
- 绘图事件 void paintEvent(QPaintEvent *)
- 画家类 QPainter(构图的设备)
- 拿起笔 .setPen(笔)
- 拿起刷子 .setBrush(刷子)
- 画笔类 QPen(笔的颜色)
- 画刷类 QBrush(笔的颜色)
- 高级操作
- 效率降低的抗锯齿
- painter.setRenderHint()
- 改变画家位置
- painter.save();保存当前位置
- painter.restore(); 还原到保存的位置
- painter.translate(); 移动画家
- 画家绘制图片drawPixmap
- 效率降低的抗锯齿
6.6 绘图设备
- QPixmap 专门对图像显示做了优化
- QBitmap 色深限定为1
- QImage 专门为图像的像素级访问做了优化
- QPicture 可以记录和重视画家的QPainter的各类命令
- 自定义绘图操作
6.7 文件读写 QFile
- file.open(打开方式)
QtODevice::readOnly
- 全部读取
file.readAll()
按行读 ``file.readLine()判断文件末尾
atend()` - QFile默认支持的是utf-8 指定格式 QTextCodec
- QTextCodec *codec = QTextCodec::codecForName(“gbk”);
- ui->textEdit->setText(codec->toUnicode(array));
- 关闭文件对象
file.close()
;
//1.点击按钮,弹出文件对话框
connect(ui->pushButton,&QPushButton::clicked,[=](){
QString path =QFileDialog::getOpenFileName(this,"打开文件","D:\\C++学习");
//2.将路径写入
ui->lineEdit->setText(path);
//3.读入内容,写再textEdit中
//QFile默认支持utf-8,需要用编码格式类支持其他编码
QFile file =(path);//参数就是读入路径
// QTextCodec * codec =QTextCodec::codecForName("gbk");
//4.设置打开方式
file.open(QIODevice::ReadOnly);
//4.1将所有数据读入array中
// QByteArray array=file.readAll();
//4.2按行读
QByteArray array;
while(!file.atEnd())
{
array+=file.readLine();
}
//5.将array写入textEdit
ui->textEdit->setText(array);
//ui->textEdit->setText(codec->toUnicode(array));
//6.文件对象关闭
file.close();
6.8 文件信息 QFileInfo
- QFileInfo info(path);
- 后缀名 info.suffix()
- 创建日期 info.birthTime().toString(“yyyy/MM/dd hh:mm:ss”);
- 修改日期 info.lastModified().toString(“yyyy/MM/dd hh:mm:ss”);
//QFileInfo
QFileInfo info(path);
//打印文件的基本信息
qDebug()<<"size:"<<info.size()<<"suffix:"<<info.suffix()<<"name:"<<info.fileName()<<"path:"<<info.filePath();
//创建日期
qDebug()<<"create date:"<<info.created().toString("yyyy/mm/dd hh:mm:ss").toUtf8().data();
qDebug()<<"modified date:"<<info.lastModified().toString("yyyy/mm/dd hh:mm:ss").toUtf8().data();
7 线程
7.1为什么需要线程
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
#include <QDebug>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::myWidget)
{
ui->setupUi(this);
myTimer =new QTimer(this);
connect(myTimer, &QTimer::timeout, this, &myWidget::dealTimeout);
}
myWidget::~myWidget()
{
delete ui;
}
void myWidget::dealTimeout()
{
static int i = 0;
ui->lcdNumber->display(++i);
}
void myWidget::on_pushButton_clicked()
{
if(myTimer ->isActive() == false)
myTimer->start(100);
//需要进行一个复杂的数据处理
QThread::sleep(5);
//处理完数据之后,关闭定时器
myTimer->stop();
qDebug()<<"over!";
}
7.2 线程
(1) qt 4.7版本之前的实现方式
写法:
- 创建单独线程MyThread
#include "mythread.h"
MyThread::MyThread(QObject *parent)
: QThread{parent}
{
}
void MyThread::run()
{
//复杂耗时的操作
//需要耗时5s
sleep(5);
emit isDone();
}
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QThread>
#include <QDebug>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::myWidget)
{
ui->setupUi(this);
myTimer =new QTimer(this);
connect(myTimer, &QTimer::timeout, this, &myWidget::dealTimeout);
//初始化对象
thread = new MyThread(this);
//处理线程发来的信号
connect(thread, &MyThread::isDone, this, &myWidget::dealDone);
//当点击右上角的关闭窗口,窗口出发destroyed
connect(this, &myWidget::destroyed, this, &myWidget::stopThread)
}
myWidget::~myWidget()
{
delete ui;
}
void myWidget::dealTimeout()
{
static int i = 0;
ui->lcdNumber->display(++i);
}
void myWidget::on_pushButton_clicked()
{
if(myTimer ->isActive() == false)
myTimer->start(100);
//启动线程,用start
thread->start();
}
void myWidget::dealDone()
{
myTimer->stop();
qDebug()<<"over";
}
void myWidget::stopThread()
{
//退出线程
thread->quit();
//等待线程处理完手头工作,回收资源
thread->wait();
}
(2) qt5的实现方式
#include "mythread.h"
#include <QThread>
#include <QDebug>
MyThread::MyThread(QObject *parent)
: QObject{parent}
{
isStop = false;
}
void MyThread::myTimeout()
{
while(isStop == false)
{
QThread::sleep(1);
emit mySignal();
qDebug()<<"子线程号: "<<QThread::currentThread();
if(true == isStop)
{
break;
}
}
}
void MyThread:: setFlag(bool flag )
{
isStop = flag;
}
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QDebug>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::myWidget)
{
ui->setupUi(this);
//动态分配内存空间,不指定父对象
myT =new MyThread;
//创建子线程对象
thread =new QThread(this);
//把自定义线程加入到子线程
myT->moveToThread(thread);
connect(myT, &MyThread::mySignal, this, &myWidget::dealSignal);
qDebug()<<"主线程号: "<<QThread::currentThread();
connect(this, &myWidget::startThread, myT, &MyThread::myTimeout);
}
myWidget::~myWidget()
{
delete ui;
}
void myWidget::on_buttonStart_clicked()
{
//启动线程,并没有启动线程处理函数
thread->start();
myT->setFlag(false);
//不能直接调用线程处理函数。
//直接调用导致线程处理函数和主线程是在同一个线程。
//myT->myTimeout();
//只能通过信号槽的方式的调用
emit startThread();
}
void myWidget::dealSignal()
{
static int i = 0;
i++;
ui->lcdNumber->display(i);
}
void myWidget::on_buttonStop_clicked()
{
myT->setFlag(true);
thread->quit();
thread->wait();
}
注意:1. 线程内部不能操作UI界面
- 记得要把线程子对象delete掉
void myWidget::dealClose()
{
myT->setFlag(true);
thread->quit();
thread->wait();
delete myT;
}
-
connect()中的第五个参数的作用,只有在多线程下会体现
** 默认的链接方式**:默认,队列,直接
(1)默认–如果是多线程,默认使用队列;如果是单线程,使用直接方式
//队列:槽函数所在的线程和接受者一样。
//直接:槽函数所在线程
和信号发送者
一样。
这样会使得子线程处理函数
在主线程内,会导致无响应。
connect(this, &myWidget::startThread, myT, &MyThread::myTimeout,Qt::DirectConnection);
8 Qt通信编程
8.1 TCP客户端
8.2 TCP 服务端
8.3 FTP服务器搭建
(2)FileZilla 客户端下载
https://www.filezilla.cn/download/client
(3)FileZilla服务端下载
https://www.filezilla.cn/download/server
8.4 FTP客户端实现
9 日常充电
1. toggle()、triggered()、clicked()区别
clicked()用于Button发射的信号
triggered()用于QAction发射的信号,原型:void triggered(bool checked = false);
toggle()用于ChekBox,非开即关,原型:void toggled(bool);
2. 结构体转字符串,字符串转结构体
** QByteArray在串口通讯中经常被使用**,有一定必要较为全面详细的对QByteArray进行阐述。
struct Head head;
//填充数据
head.fileName = m_fileName;
head.fileSize = m_fileSize;
QByteArray data;
data.resize(sizeof(Head));
memcpy(data.data(), &head, sizeof(Head));
QByteArray array = "xxxx";
Head head;
char *src = array.data();
memcpy(&head, src, sizeof(Head));
3. label显示多行字体,第二行省略号…
QFont font(name);
QFontMetrics fontWidth(font);
QString elidnote = fontWidth.elidedText(name, Qt::ElideRight, 360);
m_ui->projectNameLabel->setText(elidnote);
4. QLabel添加超链接
label超链接
(1) 网址超链接
void AboutWidget::setWebsiteContent(const QString& strWebsite)
{
m_ui->websiteLbl->setOpenExternalLinks(true);
m_ui->websiteLbl->setText(QString("<a style='color: #00A0A6;' href=\"%1\">%2</a>").arg(strWebsite, strWebsite));
}
(2) 文件超链接----点击许可证书
void AboutWidget::initConnect()
{
connect(m_ui->licenseLbl, &QLabel::linkActivated, this, &AboutWidget::slotOpenUrl);
}
//这里url 只能使用正斜杠 "/"
void AboutWidget::setProtocolContent(const QString& strProtocol)
{
m_ui->licenseLbl->setText(QString("<a style='color: #00A0A6;' href=\"C:/GitWorkspace/MxReal3/code/appui/mainwindow/abc.txt\">%1</a>").arg(strProtocol));
}
void AboutWidget::slotOpenUrl(const QString& strUrl)
{
QString strPath = "file:///" + strUrl;
QDesktopServices::openUrl(QUrl(strPath));
}
(3) 关于正斜杠“/”与反斜杠""
正反斜杠的区别
5. 弹簧的隐藏和显示
/**************************隐藏 弹簧******************************/
ui->horizonSpacer->changeSize(0, 0); //
/**************************显示 horizon 类型的 弹簧******************************/
ui->horizonSpacer->changeSize(20, 20, QSizePolicy::Expanding); //显示 horizon 类型的 弹簧
/**************************显示 vertical 类型的 弹簧******************************/
ui->verticalSpacer->changeSize(20, 20, QSizePolicy::Preferred, QSizePolicy::Expanding);
Qt QSpacerItem (Horizontal Spacer 、Vertical Spacer) 的隐藏与显示
6. AJSON学习文档
6.1 Json语法规则
(1)数据在名称/值对中
,如: {“domain”: “sojson.com”}
(2)可以多对键值对
,如 :{“domain”: “sojson.com”,“author”:“soゝso”,“email”:“i@itboy.net”}
(3)值里面可以有数组(Array)
,如:
{“domain_array”, [“sojson.com”,“soso.pub”,“soso.run”,“isoso.xin”,“isoso.xin”]}
6.2 ajson基本示例
https://gitee.com/lordoffox/ajson/blob/master/example2_basic.cpp#
String jsonArray = "[{\"id\":1,\"name\":\"test01\"},{\"id\":2,\"name\":\"test02\"}]";
\"转义后代表 ",json字符串中的key必须加引号,所以需要在字符串中再加一层引号,这时候需要用到转义
字符,如果不加转义,程序会默认识别为" 而字符串之间的连接需要使用+ 进行连接,不然就会报错了。
(1) 反序列化
#include <iostream>
#include <string>
#include "ajson.hpp"
using namespace std;
using namespace ajson;
struct demo
{
string hello;
string world;
};
AJSON(demo,hello,world)
int main(int argc, char * argv[])
{
char * buff = "{\"hello\" : \"Hello\", \"world\" : \"world.\"}";
demo the_demo;
load_from_buff(the_demo,buff);
cout << the_demo.hello << " " << the_demo.world << std::endl;
cin.get();
return 0;
}
(2)序列化
#include <iostream>
#include <string>
#include <vector>
#include "ajson.hpp"
using namespace std;
using namespace ajson;
struct Education
{
string School;
double GPA;
Education():GPA(0.0){}
Education(const string& school , double gpa)
:School(school),GPA(gpa)
{
}
};
struct Person
{
string Name;
int Age;
vector<Education> Educations;
};
AJSON(Education , School , GPA)
AJSON(Person, Name , Age , Educations)
int main(int argc, char * argv[])
{
Person person;
person.Name = "Bob";
person.Age = 28;
person.Educations.push_back(Education("MIT",600));
string_stream ss;
save_to(ss,person);
std::cout << ss.str() << std::endl;
//保存到文件
save_to_file(person, "./out.json");
//从Json加载
Person p;
load_from_buff(p, ss.str().data());
//从文件加载
Person p2;
load_from_file(p2, "./out.json");
cin.get();
return 0;
}
7. Qt 采用QString为什么会出现乱码?
真的是 QString 乱码了吗?QString 采用的unicode编码,在中文支持上不存在任何问题。
“我是汉字” 是C语言中的字符串,它是char型的窄字符串。
8. 弹窗的隐藏与显示
(1) 隐藏
//因为弹簧没有hide()这个函数,所以设置弹簧的宽高为0,0
ui->horizontalSpacer->changeSize(0,0);
//更新弹簧所在的布局
ui->horizontalLayout_2->update();
(2) 显示
//设置弹簧的宽高为40*20,自由拉伸
ui->horizontalSpacer->changeSize(40,20,QSizePolicy::Expanding);
//更新弹簧所在的布局
ui->horizontalLayout_2->update();
9. QObject的setProperty
Qt setData使用
setData使用
Qt之QMetaObject::invokeMethod()使用简介
引言
很多时候需要界面存储一些数据或信息,这样可以根据这些数据或信息来了解或获取点什么。可以说给界面增加一个标记,让我们更容易辨识。
我之前一直习惯给列表项QListWidgetItem或者树项QTreeWidgetItem携带一些数据,比如id,用setData来实现。所以这次就想QWidget有没有类似的方法,结果就找到了setUserData。后来在mac os中重新编译时,发现setUserData不可用了,建议用setProperty替换。
使用目标:给每一个自定义widget设定结构体类型的值,方便后面取出widget的信息。
bool setProperty(const char *name, const QVariant &value);
QVariant property(const char *name) const;
- 定义一个存储播放框播放信息的struct结构体,需要宏定义结构体指针类型,后面获取property的时候编译器才能识别。
struct materialPlayingInfo
{
QString materialName;
int DPI;
int DPIShow;
int x;
int y;
ST_Test(){}
ST_Test(QString name, int dpi=1080,int DPIShows=1080,int xShow=0,int yShow=0)
{
materialName = name;
DPI = dpi;
DPIShow = DPIShows;
x = xShow;
y = yShow;
}
};
Q_DECLARE_METATYPE(materialPlayingInfo*)
- 每次创建widget将materialPlayingInfo 绑定到窗口
materialPlayingInfo *playingInfo = new materialPlayingInfo{file_name,1080,1080,endPos.x(),endPos.y()};
widgetList.push_front(new MyVideoWidget);
widgetList.at(0)->setProperty("materialPlayingInfo",QVariant::fromValue(playingInfo));
- 获取widget的绑定值
this->property("materialPlayingInfo").value<materialPlayingInfo*>()->x = x.x();
10.子控件QSS设置不生效
// Assume childWidget is a pointer to a child QWidget object of parentWidget
childWidget->setAttribute(Qt::WA_StyledBackground);
11.findChildren与findChild
// Assume parentWidget is a pointer to a QWidget object
// Find all child widgets with the object name "myChildWidget"
QList<QObject*> childWidgets = parentWidget->findChildren<QObject*>("myChildWidget");
// Iterate over the list of child widgets and do something with each one
foreach (QObject* childWidget, childWidgets) {
// Do something with childWidget
}
// Assume parentWidget is a pointer to a QWidget object
// Find all child widgets
QList<QObject*> childWidgets = parentWidget->findChildren<QObject*>();
// Iterate over the list of child widgets and do something with each one
foreach (QObject* childWidget, childWidgets) {
// Do something with childWidget
}
12.很多空间都带有viewport, QListView 无法捕获鼠标事件
很多控件都带有viewport,比如QTextEdit/QTableWidget/QScrollArea,有时候对这些控件直接
处理的时候发现不起作用,需要对其viewport()设置才行,比如设置滚动条区域背景透明,需要使
用scrollArea->viewport()->setStyleSheet("background-color:transparent;");
而不是scrollArea->setStyleSheet("QScrollArea{background-color:transparent;}");
m_ui->controlSurfaceListWidget->viewport()->installEventFilter(this);
//这里必须时viewport()安装事件过滤器
bool DeviceDetectionWidget::eventFilter(QObject* watched, QEvent* event)
{
QEvent::Type type = event->type();
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QListWidgetItem* pItem = m_ui->controlSurfaceListWidget->itemAt(mouseEvent->pos());
if (pItem == nullptr)
{
sendDeviceUnlockedInfo();
m_ui->controlSurfaceListWidget->clearSelection();
return true;
}
else
{
return QObject::eventFilter(watched, event);
}
}
return QObject::eventFilter(watched, event);
}
13.正则表达式
//1.可输入所有字符,限制200个字符,200可以修改为任何数字
ui->lineEdit->setValidator(new QRegularExpressionValidator
(QRegularExpression("^[(\\s|\\S)+]{0,200}$"),this));
//2.正则表达式限制输入框范围-999.99~999.99
QRegularExpressionValidator* pIntValidator = new QRegularExpressionValidator(QRegularExpression(QString("^-?([1-9]\\d{0,2}|0)(\\.\\d{0,2})(\.[0-9]{1,2})?$")));
m_ui->xPosEdit->setValidator(pIntValidator);
14.获取QListWidget所有item
QList<QListWidgetItem *> QListWidget::findItems( constQString & text, QT::MatchFlags flags ) const
//使用
auto mylist = ui->listwidget->findItems("*", Qt::MatchWildCard);
15.blockSignals阻塞控件信号的发出
当设置为true时,QObject对象[子类]不会发出信号
在动态为QComboBox动态添加item的时候,它会发出xxxChanged信号,但是初始情况下我们不希望其发出,
待初始化完成后再发出。则可以:
(1)先设置blockSignals(true);
(2)动态添加item;
(3)设置blockSignals(false)
16.控件滚动的禁用以及滚动方式
ui->listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//禁用纵向滚动条
ui->listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
//设置横向按照像素值为单位滚动
ui->listWidget->setHorizontalScrollMode(QListWidget::ScrollPerPixel);
//设置纵向按照像素值为单位滚动
ui->listWidget->setVerticalScrollMode(QListWidget::ScrollPerPixel);
//设置滚动对象以及滚动方式为鼠标左键拉动滚动
QScroller::grabGesture(ui->listWidget, QScroller::LeftMouseButtonGesture);
//还有个QScrollerProperties可以设置滚动的一些参数
17.样式表不生效的三种解决方法
方法1: 设置属性,this->setAttribute(Qt::WA_StyledBackground, true);
方法2:改成继承QFrame,因为QFrame自带paintEvent函数已做了实现,在使用样式表时会进
行解析和绘制。
方法3:重新实现QWidget的paintEvent函数时,使用QStylePainter绘制
void Widget::paintEvent(QPaintEvent *)
{
QStyleOption option;
option.initFrom(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
}
18.鼠标跟踪失效
有时候设置了鼠标跟踪setMouseTracking为真,如果该窗体上面还有其他控件,当鼠标移到其他控件上面的时候,父类的鼠标移动事件MouseMove识别不到了,此时需要用到HoverMove事件,需要先设置setAttribute(Qt::WA_Hover, true);
19.Qt模态界面设置setWindowModality禁止其他界面响应
模态:启动模态界面时,例如弹出对话框强制用户从其他正在进行的业务中聚焦到当前对话框,除了该对话框整个应用程序窗口都无法接受用户响应,无法切换界面,无法切换当前Qt应用。这可以保证用户按照自己设计的操作逻辑进行动作。只有关闭和退出该模态界面,才可以访问本应用程序的其他界面和功能。
非模态:与模态相反,默认状态为非模态,即可以随意切换,可以在同一应用下的各个窗口界面任意切换。
半模态:介于二者之间,冻结窗口界面,但其他应用继续执行响应。
Qt中的QWidget对象自带setWindowModality(type)方法,用以设置窗口模态类型。
参数type可选为一下三种:
(1)Qt::NonModal 非模态:正常模式
(2)Qt::WindowModal 半模态:窗口级模态对话框,阻塞父窗口、父窗口的父窗口及兄弟窗口。
(3)Qt::ApplicationModal 模态:应用程序级模态对话框,阻塞整个应用程序的所有窗口。
如果是dialog窗体,需要在exec以后还能让其他代码继续执行,请在dialog窗体exec前增加一行代
码,否则会阻塞窗体消息。
QDialog dialog;
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
20.安全删除Qt对象类
安全的删除Qt的对象类,强烈建议使用deleteLater而不是delete,因为deleteLater会选择在合适
的时机进行释放,而delete会立即释放,很可能会出错崩溃。如果要批量删除对象集合,可以用qDeleteAll,比如 qDeleteAll(btns);
21.设置窗口无边框之后,通过qss样式无法设置成功边框颜色
//场景:使用下述代码设置窗口隐藏标题栏之后,无法修改边框样式
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool | Qt::WindowStaysOnTopHint);
//解决:重写窗口的paintEvent事件
void KCustomDlg::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.save();
painter.setRenderHint(QPainter::Antialiasing); // 反锯齿
QColor boderColor(255, 255, 255, 30);
painter.setPen(boderColor);
QRect rect = this->rect();
painter.drawRect(rect);
painter.restore();
QDialog::paintEvent(event);
}
22.qss中padding与margin的区别
23.QString字符串的截取
//主要介绍QString的三个接口 left/right/mid
QString str = "Hello, World!";
QString newStr1 = str.left(5); // 从字符串的左侧开始,截取5个字符,newStr1的值为"Hello"
QString newStr2 = str.right(6); // 从字符串的右侧开始,截取6个字符,newStr2的值为"World!"
QString newStr3 = str.mid(7);//从字符串的第7个字符开始,截取之后的所有字符,newStr的值为"World!"
24.QLineEdit内有光标但是无法通过键盘输入
m_ui->projSearchEdit->setFocus();
m_ui->projSearchEdit->grabKeyboard();
根据搜索内容,对工程下拉列表中的内容隐藏,并自适应高度
void SearchWidget::searchProj(const QString& strText)
{
if (strText.isEmpty())
setListItemAllVisible();
for (int i = 0; i < m_ui->projListWidget->count(); ++i)
{
auto pMatchItem = dynamic_cast<ProjectItemWidget*>(m_ui->projListWidget->itemWidget(m_ui->projListWidget->item(i)));
if (!pMatchItem)
continue;
bool bContain = pMatchItem->getProject().contains(strText, Qt::CaseInsensitive);
if(!bContain)
m_ui->projListWidget->setRowHidden(i, !bContain);
}
setFixedHeight(visibleItemsNum() >= 5 ? 250 : g_nItemHeight * visibleItemsNum());
}
25.搜索内容,下拉列表中的匹配项文字高亮
void SearchWidget::searchProj(const QString& strText)
{
if (strText.isEmpty())
setListItemAllVisible(false);
for (int i = 0; i < m_ui->projListWidget->count(); ++i)
{
auto pMatchItem = dynamic_cast<ProjectItemWidget*>(m_ui->projListWidget->itemWidget(m_ui->projListWidget->item(i)));
if (!pMatchItem)
continue;
if(!bContain)
m_ui->projListWidget->setRowHidden(i, !bContain);
//根据搜索内容高亮
pMatchItem->setSelection(strText);
}
setFixedHeight(visibleItemsNum() >= g_nMaxDisplayItemNum ? g_nListMaxHeight : g_nItemHeight * visibleItemsNum());
}
//之前无法实现高亮思路一直在,将显示路径的控件固定为QLabel
//现更换为:使用QLineEdit,所谓高亮即设置选中的样式为绿色
void ProjectItemWidget::setSelection(const QString& strText) const
{
int nLastBackSlashPos = m_ui->pathEdit->text().lastIndexOf("\\");
int fistPos = m_strProjName.indexOf(strText) + 1;
if (fistPos != 0)
{
m_ui->pathEdit->setSelection(nLastBackSlashPos + fistPos, strText.length());
}
else
{
m_ui->pathEdit->clearSelection();
}
}
自定义ItemWidget的qss样式
QLineEdit#pathEdit
{
border:none;
background: transparent;
color:rgba(255, 255, 255, 0.98);
}
QLineEdit#pathEdit
{
selection-background-color: transparent;
selection-color: rgba(18, 221, 0, 1);
}
最终实现效果如图
26.点击输入框,下方出现模糊搜索后的列表
步骤1:调用接口设置弹窗为非活动窗口, 去掉窗口的Qt::popup属性(实现不了输入框有焦点和出现弹窗的情况下,输入框仍能输入的需求)
//关键步骤:当下方的弹窗show出时,焦点会自动转移到弹窗上,所以需要用接口设为非活动窗口
this->setAttribute(Qt::WA_ShowWithoutActivating);
//Show the widget without making it active.
//搜索弹窗中的部分实现如下
SearchWidget::SearchWidget(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::SearchWidget)
{
m_ui->setupUi(this);
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);
setAttribute(Qt::WA_ShowWithoutActivating);
loadStyleSheet(":/searchwidget.qss");
initUI();
initConnect();
}
步骤2:自定义一个QLineEdit,重写focusInEvent和focusOutEvent事件
void SearchEdit::focusInEvent(QFocusEvent *event)
{
QLineEdit::focusInEvent(event);
emit sigFocusIn();
}
void SearchEdit::focusOutEvent(QFocusEvent *event)
{
QLineEdit::focusOutEvent(event);
emit sigFocusOut();
}
步骤3:绑定自定义QLineEdit的信号,做一些操作
connect(m_ui->projSearchEdit, &SearchEdit::sigFocusIn, this, &UeWidget::slotFocusIn);
connect(m_ui->projSearchEdit, &SearchEdit::sigFocusOut, this, &UeWidget::slotHidePopup);
void UeWidget::slotFocusIn()
{
m_ui->projSearchEdit->activateWindow();
showPopup();
}
void UeWidget::slotHidePopup()
{
if (m_bClickedInProjItem)
return;
hidePopup();
}
//由于没有了Qt::popup支持的点击弹窗区域外的地方消失特性,需要自己写鼠标事件控制消失
void UeWidget::mousePressEvent(QMouseEvent* event)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPoint listLeftTopPoint = mapToGlobal(m_pSearchWidget->geometry().topLeft());
QRect editRange = QRect(listLeftTopPoint, m_ui->projSearchEdit->size());
if (m_pSearchWidget->isVisible() && (!m_pSearchWidget->geometry().contains(mouseEvent->pos()) || !editRange.contains(mouseEvent->globalPos())))
{
m_pSearchWidget->close();
m_ui->projSearchEdit->clearFocus();
m_bClickedInProjItem = false;
return;
}
QWidget::mousePressEvent(event);
}
26.QSignalMapper信号转发
应用场景:该类的一个典型的使用场合是,大量控件都要相应槽函数,而这些槽函数的实现又大致相同。这种情况下,最直接的办法就是仍然为每一个控件的相应信号创建一个槽函数。但这会导致代码的大量重复。此时,我们就可以使用QSignalMapper来实现这种需求。
QSignalMapper类的功能核心是要建立一个从原始信号的object到需要的数据的映射(setMapping函数)。
map()作为QSignalMapper的一个槽函数,将根据setMapping规则转发mapped()信号。
QSignalMapper可将多个有类似处理方式signal用一个slot实现,相当于将N个一对一映射通过集中转换成多对一映射。
QSignalMapper* pSignalMapper = new QSignalMapper(this);
connect(m_ui->metlScreenButton, &QPushButton::toggled, pSignalMapper, QOverload<>::of(&QSignalMapper::map));
connect(m_ui->captureScreenButton, &QPushButton::toggled, pSignalMapper, QOverload<>::of(&QSignalMapper::map));
connect(m_ui->ueScreenButton, &QPushButton::toggled, pSignalMapper, QOverload<>::of(&QSignalMapper::map));
pSignalMapper->setMapping(m_ui->metlScreenButton, g_nMetlScreenIndex);
pSignalMapper->setMapping(m_ui->captureScreenButton, g_nCaptureScreenIndex);
pSignalMapper->setMapping(m_ui->ueScreenButton, g_nUEScreenIndex);
connect(pSignalMapper, &QSignalMapper::mappedInt, this, &DirectedWidget::slotChangeShowScreen);
void DirectedWidget::slotChangeShowScreen(int nScreenIndex)
{
m_ui->stackedWidget->setCurrentIndex(nScreenIndex);
}
映射关系可以通过removeMappings()移除;setMapping函数的参数只有四种
,并且要严格按照格式写入,第一种const QString&,第二种int,第三种QObject*,第四种QWidget *,对于后两种,需要的是他们的子类,则在信号处理的函数里进行类型转化.
void removeMappings(QObject* sender);
//设置对象和转发参数的映射关系
void setMapping(QObject *sender, int id);
void setMapping(QObject *sender, QString text);
void setMapping(QObject *sender, QWidget *widget);
void setMapping(QObject *sender, QObject *object);
//获取对象和转发参数的映射关系
QObject * mapping(int id) const
QObject * mapping(const QString &id) const
QObject * mapping(QWidget *widget) const
QObject * mapping(QObject *object) const
27.重载的信号与槽函数的绑定
#include <QObject>
class MyClass : public QObject
{
Q_OBJECT
signals:
void mySignal(int value);
void mySignal(const QString &value);
public slots:
void mySlot(int value);
void mySlot(const QString &value);
};
//信号与槽都发生重载
MyClass *obj = new MyClass();
// 绑定重载的信号和槽
QObject::connect(obj, QOverload<int>::of(&MyClass::mySignal),
obj, QOverload<int>::of(&MyClass::mySlot));
QObject::connect(obj, QOverload<const QString &>::of(&MyClass::mySignal),
obj, QOverload<const QString &>::of(&MyClass::mySlot));
static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map))
与QOverload<>::of(&QSignal::map)
两种写法一致吗?
(1)static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map) 是将 &QSignalMapper::map
的函数指针转换为特定类型 void (QSignalMapper::*)() 的函数指针。
这种写法用于将 &QSignalMapper::map 函数指针与特定的函数签名进行匹配。
(2)QOverload<>::of(&QSignal::map) 则是使用 QOverload 模板类的成员函数 of
来指定特定的函数签名。
(3)两者的作用是相似的,都用于帮助 Qt 在连接信号和槽时进行匹配。
然而,使用 QOverload 更加类型安全,因为它会在编译时检查函数签名,
而 static_cast 则在运行时进行函数指针的转换。
因此,推荐使用 QOverload 以确保类型的正确性和安全性。
28.更改QListWidget内部布局方向
QListWidget* listWidget = new QListWidget();
listWidget->setFlow(QListView::LeftToRight); // 设置布局方向为水平
// 添加项
listWidget->addItem("Item 1");
listWidget->addItem("Item 2");
listWidget->addItem("Item 3");
应用场景
29 Qt快捷键相关类 Qt::keyBoardModifiers 、QKeyCombination、 QSequence、QShortCut
(1)Qt::keyBoardModifiers
,Qt中的枚举类型,表示键盘修饰符,用于捕获用户在按键时按下的修饰键,例如Shift、Ctrl、Alt等。可以通过位运算符来组合多个修饰键。
(2) QKeyCombination
Qt中的类,用于表示键盘组合键。
// 创建一个表示Ctrl+Shift+L键的组合键
QKeyCombination combination(Qt::Key_L, Qt::ControlModifier | Qt::ShiftModifier);
// 将组合键转换为字符串进行显示
QString str = combination.toString();
qDebug() << "Combo string: " << str;
// 解析字符串为组合键对象
QKeyCombination parsedCombo = QKeyCombination::fromString(str);
// 获取组合键的按键和修饰键
int key = parsedCombo.key();
Qt::KeyboardModifiers modifiers = parsedCombo.modifiers();
qDebug() << "Parsed key: " << key;
qDebug() << "Parsed modifiers: " << modifiers;
(3) QSequence
和QShortCut
// 创建一个QSequence表示按键的顺序
QKeySequence sequence(Qt::Key_Control, Qt::Key_S);
// 创建一个QShortcut来检测按键组合
QShortcut shortcut(sequence, &mainWindow);
// 连接shortcut的activated信号到槽函数
QObject::connect(&shortcut, &QShortcut::activated, [&]() {
qDebug() << "Shortcut activated!";
});
应用:有一个QTreeWidget,在双击item的时候能够进入编辑状态,按下键盘中的任意键位能够捕捉到键位,并将其显示到item上。
- 键盘事件
void ShortcutEdit::keyPressEvent(QKeyEvent* event)
{
if (m_bReadOnly)
return;
int nextKey = event->key();
if (QString selectedText = m_pLineEdit->selectedText(); (!selectedText.isEmpty() && selectedText == m_pLineEdit->text())
|| nextKey == Qt::Key_Backspace)
{
clear();
return;
}
if (nextKey == Qt::Key_Enter || nextKey == Qt::Key_Return)
{
emit finishEditing();
return;
}
if (nextKey == Qt::Key_Control
|| nextKey == Qt::Key_Shift
|| nextKey == Qt::Key_Meta
|| nextKey == Qt::Key_Alt
|| nextKey == Qt::Key_unknown)
{
return;
}
nextKey |= translateModifiers(event->modifiers());
QKeySequence keySeq(nextKey);
m_keySequence = keySeq;
m_pLineEdit->setText(keySeq.toString(QKeySequence::NativeText));
event->accept();
}
- 双击树节点,进入编辑状态,需要写一个代理
#include <QStyledItemDelegate>
#include <QTreeWidgetItem>
class ShortcutTreeDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
ShortcutTreeDelegate(int nColumn = -1, QObject* parent = nullptr);
QWidget *createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
signals:
void textChanged(const QString& strText);
void editingFinished(QTreeWidgetItem* item, int column) const;
private:
int m_nColumn = -1;
};
30 关于xml文件的一些学习点
- 基本概念
XML是一种标记语言,全称为可扩展标记语言(Extensible Markup Language),用于描述数据的结构和内容。
XML的基本语法主要由标签、元素、属性和文本内容等语法规范,文档声明(Document Declaration)以及可选的约束文件(例如DTD或XSD)组成。
(1)标签(Tags):XML文档由标签组成。标签是被尖括号包围的元素名称,例如<book>和</book>。
它们用于定义元素的开始和结束。
(2)元素(Elements):元素是XML文档的基本构建块。它由一个开始标签、一个结束标签和之间的内容组成。
例如,<title>Introduction to XML</title>中的title是一个元素,Introduction to XML是元素的内容。
(3)属性(Attributes):属性提供了关于元素的额外信息。它们用于在开始标签中提供元素的属性值。
例如,<book category="programming">中的category是一个属性,它的值是programming。
(4)文本内容(Text Content):文本内容是元素内的纯文本信息。
例如,<author>John Doe</author>中的John Doe是文本内容。
(5)文档声明(Document Declaration):文档声明用于指定XML文档的版本和字符编码。
它位于XML文档的最开始,通常是<?xml version="1.0" encoding="UTF-8"?>。
(6)约束文件(DTD或XSD):约束文件用于定义XML文档的结构和规则。
它们可以是文档类型定义(DTD)或XML模式定义(XSD)文件。
约束文件定义了XML元素、属性、文档结构和合法的值范围等。
- xml语法:
(1)在xml文件中,标签可以使用不同的方式进行关闭双标签:
双标签是XML中最常见的标签形式,需要有开始标签和结束标签。开始标签用尖括号包围,结束标签使用相同的标签名,并在标签名之前加上斜杠(/)
<book>
<title>Introduction to XML</title>
<author>John Doe</author>
</book>
单标签:
单标签也称为空标签或自闭合标签,不需要有结束标签。它们在开始标签的尾部使用斜杠(/)进行关闭
<book category="programming" />
(2)在XML中,标签可以嵌套在其他标签中,形成层级关系。嵌套的标签称为子标签,被嵌套的标签称为父标签。例如:
<book>
<title>Introduction to XML</title>
<author>John Doe</author>
</book>
在上面的例子中,
<book>
<title>Introduction to XML</author>
<author>John Doe</title>
</book>
(3)在XML中,属性是位于标签内部的附加信息,用于提供更多的描述或特定的数据。属性需要有一个名称和对应的值。属性的格式如下:
<tagname attribute="value" />
其中,attribute是属性的名称,value是属性的值。需要注意的是,属性值必须加引号,可以使用单引号或双引号。
- 关于读、写、修改xml文件的例子
软件本身有一份关于快捷键的配置文件,软件打包后会拷贝到bin目录中,原有的想法是直接修改bin目录中的配置文件。这种方式最终被驳回,思路修改为:当用户修改快捷键时,生成一份用户数据,保存于appdata(%appdata%的软件目录下),每次打开时对比两份快捷键的数据,以用户数据为主。
<?xml version='1.0' encoding='UTF-8'?>
<Shortcut>
<GlobalControl XRShortcut="-" DMXShortcut="-" MIDIXRShortcut="-"/>
<Brightness XRShortcut="NULL" DMXShortcut="15" MIDIXRShortcut="NULL"/>
<VolumeAdjust XRShortcut="NULL" DMXShortcut="16" MIDIXRShortcut="NULL"/>
<BlackScreen XRShortcut="NULL" DMXShortcut="18" MIDIXRShortcut="NULL"/>
<Frozen XRShortcut="NULL" DMXShortcut="19" MIDIXRShortcut="NULL"/>
<BlendEffectSwitch XRShortcut="NULL" DMXShortcut="20" MIDIXRShortcut="NULL"/>
<Director XRShortcut="Ctrl+Shift+D" DMXShortcut="-" MIDIXRShortcut="-"/>
<ImportUEPicture XRShortcut="Ctrl+Shift+U" DMXShortcut="-" MIDIXRShortcut="-"/>
<MeidaLibrary XRShortcut="Ctrl+Shift+M" DMXShortcut="-" MIDIXRShortcut="-"/>
<MultiView XRShortcut="Ctrl+Shift+V" DMXShortcut="-" MIDIXRShortcut="-"/>
<OutputMgr XRShortcut="Ctrl+Shift+O" DMXShortcut="-" MIDIXRShortcut="-"/>
<OutputSwitch XRShortcut="Shift+H" DMXShortcut="7" MIDIXRShortcut="NULL"/>
<OpenOrClosePosition XRShortcut="NULL" DMXShortcut="14" MIDIXRShortcut="NULL"/>
<SwitchFirstWorkflow XRShortcut="NULL" DMXShortcut="8" MIDIXRShortcut="NULL"/>
<SwitchSecondWorkflow XRShortcut="NULL" DMXShortcut="9" MIDIXRShortcut="NULL"/>
<SwitchThirdWorkflow XRShortcut="NULL" DMXShortcut="10" MIDIXRShortcut="NULL"/>
<SwitchForthWorkflow XRShortcut="NULL" DMXShortcut="11" MIDIXRShortcut="NULL"/>
<SwitchLastWorkflow XRShortcut="NULL" DMXShortcut="12" MIDIXRShortcut="NULL"/>
<SwitchNextWorkflow XRShortcut="NULL" DMXShortcut="13" MIDIXRShortcut="NULL"/>
<ResourceLibrary XRShortcut="Ctrl+Shift+R" DMXShortcut="-" MIDIXRShortcut="-"/>
<StageResource XRShortcut="Ctrl+Shift+S" DMXShortcut="-" MIDIXRShortcut="-"/>
<TimeLine XRShortcut="Ctrl+Shift+T" DMXShortcut="-" MIDIXRShortcut="-"/>
<PlayOrPause XRShortcut="NULL" DMXShortcut="1" MIDIXRShortcut="NULL"/>
<Stop XRShortcut="NULL" DMXShortcut="2" MIDIXRShortcut="NULL"/>
<LastSection XRShortcut="NULL" DMXShortcut="3" MIDIXRShortcut="NULL"/>
<NextSection XRShortcut="NULL" DMXShortcut="4" MIDIXRShortcut="NULL"/>
<VolumeOpenOrClose XRShortcut="NULL" DMXShortcut="5" MIDIXRShortcut="NULL"/>
<ShowOrHideMedia XRShortcut="NULL" DMXShortcut="6" MIDIXRShortcut="NULL"/>
<WorkFlow XRShortcut="Ctrl+Shift+W" DMXShortcut="-" MIDIXRShortcut="-"/>
</Shortcut>
(1)读取
ShortcutPropertyVector KeyboardShortcutWidget::getShortcutData(const QString& strUrl) const
{
ShortcutPropertyVector vecShortcutData;
QFile file(strUrl);
if (!file.open(QIODevice::ReadOnly))
return {};
QXmlStreamReader reader(&file);
while (!reader.atEnd() && !reader.hasError())
{
QXmlStreamReader::TokenType token = reader.readNext();
if (token != QXmlStreamReader::StartElement || reader.name().toString() == "Shortcut")
continue;
nova_xr::ShortcutProperty shotcutInfo;
shotcutInfo.strXRShortcut = reader.attributes().value("XRShortcut").toString().toStdString();
shotcutInfo.strMIDIShortcut = reader.attributes().value("MIDIXRShortcut").toString().toStdString();
shotcutInfo.strDMXShortcut = reader.attributes().value("DMXShortcut").toString().toStdString();
vecShortcutData.emplace_back(reader.name().toString(), shotcutInfo);
}
return vecShortcutData;
}
(2)修改
void KeyboardShortcutWidget::modifyShortcutData(const QString& strTargetTagName, const QString& strAttributeKey, const QString& strAttribute)
{
// 读取XML文件
QString strAppDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QString strAppName = QCoreApplication::applicationName();
QString strUserPath = strAppDataPath.remove(strAppName) + g_strConfigFileName + QString("/%1/%2.xml").arg(strAppName).arg(g_shortcutConfigName);
QFile file(strUserPath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
//用户数据不存在需要从系统数据bin目录拷贝数据到appdata
copySystemDataToUserData(strUserPath);
}
QDomDocument doc;
if (!doc.setContent(&file))
{
file.close();
return;
}
file.close();
QDomNodeList nodeList = doc.elementsByTagName(strTargetTagName);
if (nodeList.isEmpty())
return;
// 修改目标页签的属性内容
QDomElement targetElement = nodeList.at(0).toElement();
targetElement.setAttribute(strAttributeKey, strAttribute);
// 保存修改后的XML内容
QFile fileToWrite(strUserPath);
if (!fileToWrite.open(QIODevice::WriteOnly | QIODevice::Text))
return;
QTextStream out(&fileToWrite);
doc.save(out, 4); // 格式化输出,缩进为4个空格
fileToWrite.close();
}
31.关于QTableWiget按照某一列去排序
需求:有个表格控件tableWidget,按照事件戳对其排序
三个标签在时间线中各自有一个时间戳,但实际表格里没有关于时间戳这一列,可以额外增加一列内容,但是对这列内容隐藏就可以按照这列内容排序。
QTableWidget的排序是按照字符串比较实现的,可以直接按照时间戳hh:mm:ss:lll
排序,这样也真实反映可时间的前后。
//表头的初始化,关于每一列的宽度闲置
QStringList headerList;
headerList << QString::fromStdString("")
<< language.value("Index")
<< language.value("TagType")
<< language.value("Mark")
<< language.value("Annotation")
<< language.value("Remain")
<< QString::fromStdString("");
m_ui->cueTableWidget->setColumnCount(8);
m_ui->cueTableWidget->setHorizontalHeaderLabels(headerList);
m_ui->cueTableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
m_ui->cueTableWidget->setColumnWidth(0, 24);
m_ui->cueTableWidget->setColumnWidth(1, 48);
m_ui->cueTableWidget->setColumnWidth(2, 80);
m_ui->cueTableWidget->setColumnWidth(3, 100);
m_ui->cueTableWidget->setColumnWidth(4, 152);
m_ui->cueTableWidget->setColumnWidth(5, 100);
m_ui->cueTableWidget->setColumnWidth(6, 24);
m_ui->cueTableWidget->setColumnWidth(7, 24);
m_ui->cueTableWidget->horizontalHeader()->setStretchLastSection(true);
m_ui->cueTableWidget->setColumnHidden(7, true);
//隐藏垂直表头
m_ui->cueTableWidget->verticalHeader()->hide();
//设置交替属性开启
m_ui->cueTableWidget->setAlternatingRowColors(true);
//设置表头不可点击选中
m_ui->cueTableWidget->horizontalHeader()->setSectionsMovable(false);
m_ui->cueTableWidget->horizontalHeader()->setSectionsClickable(false);
// 设置全部单元格不可编辑
m_ui->cueTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_ui->cueTableWidget->setFocusPolicy(Qt::NoFocus);
m_ui->cueTableWidget->setSelectionMode(QAbstractItemView::NoSelection);
//不显示网格线
m_ui->cueTableWidget->setShowGrid(false);
//设置水平滚动条无
m_ui->cueTableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 设置表头文字内容居左
m_ui->cueTableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
表格按照时间戳排序
m_ui->cueTableWidget->sortByColumn(7, Qt::AscendingOrder);
根据传入的ms数返回时间戳
QString CUEDockWidget::generateTimeStamp(uint64_t ms) const
{
QTime t(0, 0, 0);
t = t.addMSecs(static_cast<int>(ms));
return t.toString("hh:mm:ss:zzz");
}
32.在QTableWidget某个单元格设置图片或动图
这种方式设置的图片非常模糊
QTableWidgetItem *item = new QTableWidgetItem();
QIcon icon("image.png"); // 替换为你的图片文件路径
item->setIcon(icon);
tableWidget->setItem(row, col, item);
改用以下方式
//设置图片
QLabel* pTypeLabel = new QLabel(this);
QPixmap typePixmap(getTagTypeUrl(newTag.type));
typePixmap = typePixmap.scaled(QSize(32, 20));
pTypeLabel->setPixmap(typePixmap);
pTypeLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);//设置图片居中
m_ui->cueTableWidget->setCellWidget(nRow, 2, pTypeLabel);
//设置动图,此处m_pMovie已在构造函数中初始化结束
QLabel* pLabel = new QLabel(this);
pWidget->setMovie(m_pMovie);
m_pMovie->start();
m_ui->cueTableWidget->setCellWidget(nRow, 0, pLabel);
//有趣的是,QMovie可以控制播放状态
(1)当调用 start() 方法时,QMovie 开始播放动画。如果动画已经在播放,start() 方法将重新开始播放
动画,从第一帧开始。如果动画处于暂停状态,start() 方法将恢复播放动画。
(2)setPaused() 方法用于暂停或恢复动画播放。当 paused 参数为 True 时,动画暂停;当 paused 参数为
False 时,动画恢复播放。