QT基础与细节理解

前言

本博客旨在记录QT学习过程中的一些细节知识理解,由于问题的产生并非成体系,所以前期的记录可能会无序一些。烦请读者参阅目录进行快速的问题定位与跳转

正文部分

QT基础:正确理解: QWidget(parent), ui(new Ui::ui_mywidget)

先摘抄一个定义完备的基本窗口项目,项目的结构如下:

- mywidget.pro --- qmake项目管理文件
- mywidget.h --- 基本窗口头文件
- mywidget.cpp --- 基本窗口类文件
- main.cpp --- 主类文件
- mywidget.ui --- 基本窗口样式文件

其中,mywidget.cpp中有参构造函数的写法引起我的注意:

MyWidget::MyWidget(QWidget *parent)
    : QWidget(parent), ui(new Ui::ui_mywidget)

经过查阅资料,得知这是C++的一种参数初始化方式—参数初始化表,在正式进入构造函数前,会依次执行冒号后的初始化内容:

  • QWidget(parent) — 表征派生类MyWidget调用基类QWidget的有参构造方法QWidget(QWidget *parent),用于将由派生类初始化的对象注册为parent的子窗口(如果parent非空)
  • ui(new Ui::ui_mywidget) — 等价于Ui::ui_mywidget *ui = new Ui::ui_mywidget(也可以使用后者的写法,因为此参数的初始化不强调顺序),即完成指针变量ui的初始化工作

通过上述解析,我们可以清晰地认识到以下两个问题:

  • 冒号后的内容是C++的参数初始化表
  • 其有直接进行的函数调用,也有表征赋值的右值引用

QT基础:梳理图片拖动背后的逻辑

本小节内容来自《QT Creator第三版》5.3.2

要完成一个图片的拖动工作,关键在于四个基本虚函数的重写:

  • void mousePressEvent(QMouseEvent *event); // 鼠标按下事件
  • void dragEnterEvent(QDragEnterEvent *event); // 拖动进入事件
  • void dragMoveEvent(QDragMoveEvent *event); // 拖动事件
  • void dropEvent(QDropEvent *event); // 放下事件

这里要区分一下dragEnterEvent和dragMoveEvent,前一个只在拖动开始时,和刚进入目标控件时调用,后一个则是在拖动过程中被不断的调用

鼠标按下事件要完成的工作被总结为如下内容:

是目标控件
不是目标控件
获取按下位置的控件
获取图片&鼠标相对位置
结束
拖动显示&原图片处理
执行拖动

当鼠标按下事件中开始执行拖动后(也就是QDrag的exec方法),开始会首先调用dragEnterEvent事件,拖动过程中则会不断调用dragMoveEvent事件,拖动结束后会返回事件类型以及调用dropEvent事件。因此为实现移动图片功能,接下来需要完成的工作是:

  • 在dragEnterEvent和dragMoveEvent中设置事件类型为移动事件
  • 当QDrag.exec执行完毕后,检测事件类型,如果是移动事件则关闭原目标控件
  • 在dropEvent事件生成新目标控件

QT基础:键盘事件中的AutoRepeat()到底在做什么

我们以一个键盘按下与释放的简单demo为例来探查一下键盘长按情况下,AutoRepeat()这个方法到底在做些什么?
demo的关键逻辑如下:

void Widget::keyPressEvent(QKeyEvent *event)       // 键盘按下事件
{
        if(event->key() == Qt::Key_Up){                  // 如果是向上方向键
            qDebug() << "press:"<<event->isAutoRepeat(); // 是否自动重复
        }
}
void Widget::keyReleaseEvent(QKeyEvent *event)     // 按键释放事件
{
        if(event->key() == Qt::Key_Up){
            qDebug() << "release:"<< event->isAutoRepeat();
            qDebug() << "up";
        }
}

经过断点调试,我们得到了当长按Key_Up时的输出逻辑:

----初次长按时输出内容----
press: false
release: true
up
----持续长按过程中输出内容----
press: true
release: true
up
...(此处开始循环,直至释放为止)
----结束长按时输出内容----
press: true
release: false
up

如上所述,AutoRepeat()将键盘按下与释放事件进行绑定,并调整为三阶段的输出内容

QT基础:插件应该如何优雅的生成和加载

我们首先需要理解的是,要想正确的加载插件,插件就必须要位于项目根目录之后,下面这一文件结构的插件与项目就无法正常加载:

- plugins
	- example.a
	- example.dll
- build-example-[主机与QT版本]-Debug(此为项目根目录)
	- 依赖以及运行文件等

上述结构的插件加载最终会以instance()==null告终,因为插件需求的qt环境不存在(未处于项目管理下)
因此,要优雅的加载插件,就必须使得插件处于一个qt项目的根目录后。只要满足这一条件,这个插件就可以被该项目以及其子项目所使用(即使插件位于子项目前)

QT基础:QOpenGL中,绘图坐标与材质坐标分别是怎样的

在QOpenGL中,绘图坐标是非常标准的以窗口中心为原点的右手坐标系,如下所示:
在这里插入图片描述
而材质坐标则是以图片的左下角为原点,建立标准坐标系,材质图片将全部处于第一象限中

题外话:读取材质时,由于QOpenGL将QImage读取的图片做了垂直镜像(即Y坐标取反),因此为在QOpenGL中还原,需要在QImage再次使用垂直镜像

QT基础:怎么快速发布QT程序

最简单和最方便的做法是使用qt编译依赖下的windeployqt来执行自动发布,我们以Min_GW_64编译器为例来展现具体的做法:

首先,需要在QT-Creator中,使用Min_GW_64编译器完成release包的构建任务,构建完成后,你可以得到如下所示的文件夹:

- build-XXXProject-Desktop_Qt_6_6_0_MinGW_64_bit-Release
	- release
		- XXXProject.exe

只需要提取出XXXProject.exe放置在单独的文件夹下,再找到Min_GW_64编译器的控制台(如下所示)

在这里插入图片描述
单击后打开,cd到XXXProject.exe放置的单独文件夹,再使用命令:

windeployqt XXXProject.exe

即可自动完成发布工作(此步需要确保C:\Qt\6.6.0\mingw_64\bin已被加入Path环境变量)

发布过程中只有一点格外需要注意:构建时所使用的编译器一定要与windeployqt的编译器一致,否则就会出现无法定位程序输入点的错误

在进行上述操作的时候,我们也收获了一个关于Qt资源文件意义的思考。在编程的时候,我时常认为使用路径形式的图片引用,和使用资源文件的图片引用没有区别,甚至后者反而更加麻烦。其实,资源文件可以在打包的时候将图片以数组形式转为全局变量,以链接库的形式生成到本地,再组合成exe文件。经过上述过程,我们在发布exe时,即使未将图片拷贝到发布包里,exe也可以直接使用图片

此外,在使用资源文件时,有一点需要格外引起注意,如果你发现运行的exe程序未能正确读取资源文件下的图片,一定要先尝试重新执行qmake再运行,大部分情况下的未能读取都是由于没有重新qmake导致的。这样一个简单的疏忽却可能让我们排查很久

QGraphicsItem碰撞检测Q&A详析

本记录以Q&A形式对QGraphicsItem碰撞检测中一些容易忽视或者错误理解的内容进行记录,所以Q&A内容均以碰撞检测的默认模式Qt::ItemSelectionMode为准,其他模式可参考理解

  • Q1: Item的boundingRect和shape分别起什么作用
  • A1:boundingRect规定了item的绘制区域(不可超过该区域),而shape则提供给碰撞检测函数用于判断item是否与其他item的shape发生由模式规定的交互
  • Q2:默认模式Qt::ItemSelectionMode规定的交互是什么意思,能不能详细解释一下
  • A2:默认模式规定了两种类型的交互会被判定为碰撞:1. 完全包围;2. 线段相交。线段相交很好理解,完全包围则分为降维覆盖和同维包容。降维覆盖:例如一个item的shape被设定为一个点,另一个item的shape为一条线段且通过了该点,那么两者会被判断为碰撞;同维包容:分为形状完全重合,或是完全内含两种形式,均会被判断为碰撞

如何使得QWidget嵌入QGraphicsScene以备View显示,却不被Scene所管理

这是一个很有趣的技巧,由于QGraphicsScene用于嵌入QWidget的proxy类需要做父类判断,并只能嵌入topLevel widget进QGraphicsScene,这也就意味着,如果使用下面的代码进行嵌入,嵌入的QWidget就可以实现在view上显示,但不被QGraphicsScene所管理

//当前代码块处于view类的cpp文件下,因此this指代的是view类
QPushButton *startBtn = new QPushButton("开始游戏", this);
this.scene()->addWidget(startBtn);

由上述代码所声明的pushbutton将脱离scene管理而存在,因此,即使scene调用scene->clear();,该按钮仍然存在于view的显示中。这就实现了可能有用的按钮持久化功能

QSlider的sliderMoved和valueChanged信号的不同妙用

有别于valueChanged只对value变化进行检测,sliderMoved还会对标志位sliderDown进行true检测。这意味着通过sliderMoved信号可以进行更细粒度的控制。

例如在音频播放器中,我们使用QSlider来表示播放进度,此时需关联QMediaPlayer的positionChanged信号以及QSlider的setValue函数:

connect(player, &QMediaPlayer::positionChanged, this, &MyWidget::positionChanged);
...
void MyWidget::positionChanged(int playerPos){
	int sliderPos = 100 * playerPos / player.duration();
	slider.setValue(sliderPos);
}

此外,我们还希望当拖动slider时,可以改变MediaPlayer的播放位置,但如果使用valueChanged信号,就会出现循环的关联,从而导致失效

因此,正确的做法是运用sliderMoved信号,通过对sliderDown进行判断,来区分改变position的到底是来自于slider的拖动,还是循环关联的slider的valueChanged,如果是后者,则直接返回从而打破循环,完整代码如下:

connect(player, &QMediaPlayer::positionChanged, this, &MyWidget::positionChanged);
connect(slider, &QSlider::sliderMoved, this, &MyWidget::changePosition);
...
void MyWidget::positionChanged(int playerPos){
	// 即使通过此处进行设置,也不会导致再次调用sliderMoved,因为sliderDown标志位已为false,所以不会出现循环
	if(silder.isSliderDown()) return; //这一行不能少,否则会导致拖动失效
	int sliderPos = 100 * playerPos / player.duration();
	slider.setValue(sliderPos);
}

void MyWidget::changePosition(int sliderPos){
	// 拖动时允许直接修改player的position
	int playerPos = (sliderPos * player.duration) / 100;
	player.setPosition(playerPos); //此时会进入关联1,但由于关联1已经切断到2的通路,因此不会造成循环
}

使用valueChanged时的关系:

QSlider::valueChanged
QMediaPlayer::positionChanged
MyWidget::positionChanged

使用sliderMoved时的关系:

QMediaPlayer::positionChanged
MyWidget::positionChanged
QSlider::sliderMoved

mouseDoubleClickEvent如何区分左右键双击

这个问题产生于一个小功能实现时,该功能是:我们需要实现一个音乐列表,左键双击播放音乐而右键双击则不做任何事。经过测试发现,该事件与mousePress或mouseRelease区分左右键并无任何区别,都是通过event->button()拿取到事件产生的按键

此外,我们还注意到,在帮助文档中,对mouseDoubleClickEvent还有一条引申说明:

Note: The widget will also receive mouse press and mouse release events in addition to the double click event. And if another widget that overlaps this widget disappears in response to press or release events, then this widget will only receive the double click event. It is up to the developer to ensure that the application interprets these events correctly.
提示:本窗口仍然会在接收到双击事件下,接收到鼠标按下或释放事件。除非有另一个窗口拦截了鼠标按下或释放事件并处理了它

因此,如果我们还为音乐列表实现了一个单击右键弹出处理菜单的功能,那么由于单击右键事件已由contextMenuEvent去处理,所以本窗口将无法再接收到右键双击事件(除非重新实现click->mouseUp->mouseDown的逻辑判断)

QTableView显示时,item前面为什么带了一个白色复选框?

如下,某一次使用QTableView进行自定义模型数据显示时,每一个item前面都带上了一个白色复选框:
在这里插入图片描述

调试后发现,显示异常是因为QTableView在获取item的data时,EditRole的显示优先级会高于DisplayRole(这是合理的,否则EditRole如何能够覆盖DisplayRole)。而我在自设模型的data函数中使用了以下返回代码:

if(column < columnCount()) return musicMsg->value(columnHeadersSt[column]);

也就是仅做了List合法性判断,而没有进行返回的role筛选,因此,QTableView请求数据时,EditRole和DisplayRole都可以获取到musicMsg->value(columnHeadersSt[column])。从而使得QTableView显示了EditRole的格式,这就是为什么上述图片中出现复选框

解决方法很简单,在上述代码的判断语句中多加入一条role判断:

if(column < columnCount() && role == Qt::DisplayRole) return musicMsg->value(columnHeadersSt[column]);

修改后显示正常
在这里插入图片描述

QML和C++编程优劣的一点小总结

总的来说,QML方便在提供了很多内置的绑定,例如属性绑定,两个对象间可以通过属性绑定,实现一个对象属性改变时,另一个对象的属性也跟着改变。

该功能在C++下依然能实现,只不过需要更为复杂的步骤:

  • 首先需要在在宏定义NOTIFY中绑定属性与该类中已定义的信号函数,只要该属性的值发生更改,就会发出该信号
  • 然后需要在另一个类里通过connect来接收属性改变信号,并修改自身属性

如何在TextEdit(包括它的衍生类,如TextBrowser)中,只修改某一行/某几个字的字体(font)以及颜色(color)

参考文献:QTextEdit设置当前行的文本的颜色和字体

简单来说,就是利用cursor来完成精细化的控制,通过cursor来控制光标后的文字字体与颜色改变,关键函数:

QTextCharFormat fmt;//文本字符格式
fmt.setForeground(color);// 前景色(即字体色)设为color色
fmt.setFont(font);//字体
cursor.mergeCharFormat(fmt);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值