IT评测·应用市场-qidao123.com

标题: 【Qt6】QWindow类可以做什么 [打印本页]

作者: 大号在练葵花宝典    时间: 2023-4-23 19:50
标题: 【Qt6】QWindow类可以做什么
原来的水文标题是“用 VS Code 搞 Qt6”,想想还是直接改为“Qt6”,反正这个用不用 VS Code 也能搞。虽然我知道大伙伴们都很讨厌 CMake,但毕竟这厮几乎成了 C++ 的玩家规范了。Qt 也算识大体,支持用 CMake 来构建程序。所以,只要你用的是能写 C++ 的工具,理论上都能搞 Qt。
创建应用程序界面的时候,我们一般会选用 QWidget 以及其子类的。不过,在 Gui 模块中,有一个 QWindow 类,干吗用的呢?写个程序试试看。
  1. #include <QGuiApplication>
  2. #include <QWindow>
  3. int main(int argc, char** argv)
  4. {
  5.     // 一定要先创建应用程序对象
  6.     QGuiApplication app(argc, argv);
  7.     // 创建窗口实例
  8.     QWindow win;
  9.     // create方法其实可以不调用
  10.     win.create();
  11.     // 调整窗口的大小
  12.     win.resize(300, 250);
  13.     // 设置标题栏文本
  14.     win.setTitle("番薯联盟");
  15.     // 显示窗口
  16.     win.show();
  17.     // exec进入事件(消息)循环
  18.     return QGuiApplication::exec();
  19. }
复制代码
这里说明一下,QWindow 类有个 create 方法,它的作用是创建平台相关的资源的,对应的是 destroy 方法,用来销毁这些平台相关的资源。这些平台相关的资源是为了实现跨平台的类型,如 QPlatformWindow、QPlatformSurfaceEvent 之类的。Windows 平台有单独的实现,Linux 平台也单独地实现。像 qwindowsguieventdispatcher、qunixeventdispatcher 这些也是。总之,QWindow 类可能会用到它们,于是,这些平台相关的资源,其生命周期始于 create 方法,终于 destroy 方法。
不过,create 方法这里其实可以不调用的,因为 show 方法会调用;destroy 方法也不可以不调用,它在 QWindow 类的析构函数中被调用。
咱们为上述代码写一个 CMakeLists.txt。
  1. cmake_minimum_required(VERSION 3.0.0)
  2. project(TestApp VERSION 0.1.0)
  3. find_package(Qt6 REQUIRED COMPONENTS Core Gui)
  4. add_executable(TestApp main.cpp)
  5. target_link_libraries(TestApp PRIVATE Qt6::Core Qt6::Gui)
复制代码
这里我们不到“铁三角”库,只用 core 和 gui 就够了,不需要 widgets。
好了,尝试运行,看看会出现什么。

这标题栏上的字体好像有问题。不管它,继续。
哦,直接实例化 QWindow 类会呈现一个空白窗口,而且这个窗口很诡异,你拖动一下改变它的大小后,就会变成这样。

 这是因为这个窗口是真的很空,空到连基本的绘制都没有,只是在启动的时候填充了个颜色。这个颜色是跟随系统主题的,刚才你看到的是深色主题下的背景色。现在我把系统主题调成浅色主题,它就会变成这样。

当你调整其大小后,发生重新绘制的部分变成了黑色(就是啥也没有)。
QWindow 类虽然定义了 paintEvent 方法,但是,它实现了个寂寞。
  1. void QWindow::paintEvent(QPaintEvent *ev)
  2. {
  3.     ev->ignore();
  4. }
复制代码
从源代码中你会看到,默认的实现是直接把 paint 事件忽略了。
所以,我们只能从 QWindow 类派生,并重写 paintEvent 方法,绘制我们所需要的内容。
  1. cmake_minimum_required(VERSION 3.0.0)
  2. project(TestApp VERSION 1.2.3)
  3. find_package(Qt6 REQUIRED COMPONENTS Core Gui)
  4. set(CMAKE_AUTOMOC ON)
  5. set(CMAKE_CXX_STANDARD 20)
  6. set(CMAKE_CXX_STANDARD_REQUIRED ON)
  7. add_executable(TestApp MyWindow.h MyWindow.cpp main.cpp)
  8. target_link_libraries(TestApp PRIVATE Qt6::Core Qt6::Gui)
复制代码
 
  1. 1 #include <QWindow>
  2. 2 #include <QPaintEvent>
  3. 3 #include <QBackingStore>
  4. 4
  5. 5 #ifndef __MYWINDOW_H__
  6. 6 #define __MYWINDOW_H__
  7. 7 class MyWindow : public QWindow
  8. 8 {
  9. 9     Q_OBJECT
  10. 10 public:
  11. 11     // 构造函数
  12. 12     explicit MyWindow(QWindow* parent = nullptr);
  13. 13 protected:
  14. 14     // 重写事件
  15. 15     void paintEvent(QPaintEvent *ev) override;
  16. 16 private:
  17. 17     // 绘制窗口内容需要这个类
  18. 18     QBackingStore* m_backstore;
  19. 19 };
  20. 20 #endif
复制代码
要在窗口上涂鸦,需要用到 QBackingStore 类。这是由于 QPainter 类需要一个 QPaintDevice 指针才能完成绘图。QBackingStore类可以通过 paintDevice 方法返回一个 QPaintDevice 类的指针。
m_backstore 成员也可以用 QScopedPointer 封装,防止内存泄漏。
  1. private:
  2.     // 绘制窗口内容需要这个类
  3.     QScopedPointer<QBackingStore> m_backstore;
复制代码
当超出成员作用域时会自动删除指针。
下面是实现代码。
  1. #include "MyWindow.h"
  2. #include <QPaintDevice>
  3. #include <QPainter>
  4. #include <QColor>
  5. #include <QRect>
  6. #include <QtDebug>
  7. MyWindow::MyWindow(QWindow* parent)
  8.     : QWindow(parent),
  9.       m_backstore(new QBackingStore(this))
  10. {
  11.     // 设置当前窗口的位置和大小
  12.     setGeometry(799, 304, 425, 385);
  13.     // 设置绘画设备画布大小
  14.     m_backstore -> resize(QSize(400, 300));
  15.     // 设置窗口标题
  16.     setTitle("红红火火");
  17. }
  18. void MyWindow::paintEvent(QPaintEvent* ev)
  19. {
  20.     // 要进行绘图的区域
  21.     QRect rect = ev->rect();
  22.     // 开始
  23.     m_backstore->beginPaint(rect);
  24.     QPaintDevice* dev = m_backstore -> paintDevice();
  25.     // 创建painter实例
  26.     QPainter painter;
  27.     painter.begin(dev);
  28.     // 填充矩形
  29.     painter.fillRect(rect, QColor("red"));
  30.     painter.end();
  31.     // 结束
  32.     m_backstore->endPaint();
  33.     // 把绘图输出到窗口上
  34.     m_backstore->flush(rect);
  35. }
复制代码
QBackingStore 类的构造函数需要一个 QWindow 类或子类的指针,一般是当前窗口类。这里注意的是,QBackingStore 对象不能使用默认大小(程序会闪退),一定要调用 resize 方法设置画布的大小(或者说你能看到的视窗大小)。
当窗口需要绘制时会引发 paint 事件,重写 paintEvent 方法自行绘制窗口内容。在上面代码中,只是简单的矩形填充(填充为红色)。
  1.     m_backstore->beginPaint(rect);
  2.     QPaintDevice* dev = m_backstore -> paintDevice();
  3.     // 创建painter实例
  4.     QPainter painter;
  5.     painter.begin(dev);
  6.     // 填充矩形
  7.     painter.fillRect(rect, QColor("red"));
  8.     painter.end();
  9.     // 结束
  10.     m_backstore->endPaint();
  11.     // 把绘图输出到窗口上
  12.     m_backstore->flush(rect);
复制代码
QBackingStore.paintDevice 方法所返回的 QPaintDevice 指针只在 beginPaint 和 endPaint 方法之间有效。QPaintDevice 是一个虚拟设备,用于构建二维坐标空间,然后才能在上面绘图。绘图用到 QPainter 类。这个类在实例化后,调用 begin 方法开始绘图,前面获取的 QPaintDevice 指针就在这里传递。绘制完后调用 end 方法结束。如果实例化 QPainter 类时向构造函数传递了 QPaintDevice 指针,那就不需要调用 begin 方法了。
  1.     QPainter painter(dev);
  2.     //painter.begin(dev);
  3.     // 填充矩形
  4.     painter.fillRect(rect, QColor("red"));
  5.     painter.end();
复制代码
最后,main 函数中实例化 MyWindow,并显示它。
  1. int main(int argc, char** argv)
  2. {
  3.     // 一定要先创建应用程序对象
  4.     QGuiApplication app(argc, argv);
  5.     // 创建窗口实例
  6.     MyWindow win;
  7.     // 显示窗口
  8.     win.show();
  9.     // exec进入事件(消息)循环
  10.     return QGuiApplication::exec();
  11. }
复制代码
运行程序,看到红红的一块,就说明通正确运行了。

当然,这个窗口还是有问题的。由于 QBackingStore 对象的画布大小是硬编码的,当调整了窗口大小后,红色矩形只能看到一部分,没看到的那部分仍然是黑乎乎的。
为了完善一下,我们还要重写 resizeEvent 函数,在窗口的大小被调整后,手动修改 QBackingStore 的画布大小。
  1. class MyWindow : public QWindow
  2. {
  3.     Q_OBJECT
  4.     ……
  5. protected:
  6.     ……
  7.     // 调整窗口大小后发生
  8.     void resizeEvent(QResizeEvent* ev) override;
  9.     ……
  10. };
复制代码
  1. void MyWindow::resizeEvent(QResizeEvent *ev)
  2. {
  3.     this->m_backstore->resize(ev->size());
  4. }
复制代码
这样处理之后,窗口的背景色就能正常绘制了,哪怕你调整了窗口大小。

 
直接从 QWindow 类继承还是不太方便的,内部还要使用 QBackingStore 类。于是,我们可以考虑用 QWindow 的派生类。比如 QRasterWindow。这个类是用于创建基于像素呈现的窗口——相对应的是 QOpenGLWindow。两者用法差不多,只是绘制方式不同罢了。
QRasterWindow和QOpenGLWindow类都是 QPaintDevice 和 QWindow 的子类,所以从 QRasterWindow 派生的自定义窗口不需要定义 QBackingStore成员了,窗口自身的实例就可以传递给 QPainter 对象。
接下来咱们演示一下。先编好 CMakeLists.txt 文件。
  1. cmake_minimum_required(VERSION 3.8)
  2. project(HelloApp VERSION 1.0.0 LANGUAGES CXX)
  3. # Qt内裤包
  4. find_package(Qt6 REQUIRED COMPONENTS
  5.                 Core
  6.                 Gui)
  7. # 开启MOC等选项
  8. set(CMAKE_CXX_STANDARD 20)
  9. set(CMAKE_CXX_STANDARD_REQUIRED YES)
  10. set(CMAKE_AUTOMOC YES)
  11. # 代码目录
  12. file(GLOB SRCS src/*.cpp includes/*.h)
  13. # 添加可执行代码
  14. add_executable(HelloApp ${SRCS})
  15. # 链接Qt内裤
  16. target_link_libraries(HelloApp PRIVATE
  17.                         Qt6::Core
  18.                         Qt6::Gui)
复制代码
这里老周学会了偷懒,用 file 指令找出 includes 目录下所有扩展名为 .h 的文件, 以及 src 目录下所有扩展名为 .cpp 的文件。然后把结果存到 SRCS 变量中,在 add_executable 命令执行时直接把 SRCS 传给它。这样做的好处是不用每新建一个文件都要手动添加一次了。当 IDE 提示找不到头文件时,执行一次 CMake 配置就会触发 file 命令。项目的目录结构大致长这样:

同理,这里咱们只用到 Core 和 Gui 两个模块,不需要 Widgets。
CustWindow 类派生自 QRasterWindow 类,重写 paintEvent 方法,自行绘制窗口内容。
  1. #include <QRasterWindow>
  2. #include <QPaintEvent>
  3. #ifndef __CUSTWINDOW_H__
  4. #define __CUSTWINDOW_H__
  5. class CustWindow : public QRasterWindow
  6. {
  7.     Q_OBJECT
  8. protected:
  9.     void paintEvent(QPaintEvent* event) override;
  10. };
  11. #endif
复制代码
下面是实现代码。
  1. #include "../includes/CustWindow.h"
  2. #include <QPainter>
  3. void CustWindow::paintEvent(QPaintEvent *event)
  4. {
  5.     QPainter painter;
  6.     painter.begin(this);
  7.     // 要绘制的区域
  8.     QRect rect = event->rect();
  9.     // 先刷刷墙壁
  10.     painter.fillRect(rect, QColor("blue"));
  11.     // 刷累了画个大饼充饥
  12.     // 换支笔
  13.     QPen pen(QColor("yellow"), 3.0f);
  14.     painter.setPen(pen);
  15.     rect.adjust(50, 50, -50, -50);
  16.     painter.drawEllipse(rect);
  17.     // 收工
  18.     painter.end();
  19. }
复制代码
在实例化 QPainter 时,可以把当前窗口指针 this 传递给 QPainter 的构造函数;或者先调用无参构造函数,然后调用 begin 方法传递 this。前面说过,QRasterWindow 类的父类中有 QPaintDevice,所以咱们的窗口类自然就能直接传给 QPainter 对象了。
这里头的继承关系是这样的:
QWindow、QPaintDevice => QPaintDeviceWindow => QRasterWindow => CustWindow
C++ 是可以多继承的,所以 QPaintDevice 能有两个基类。
app.cpp 文件中写 main 函数。
  1. #include "../includes/CustWindow.h"
  2. #include <QGuiApplication>
  3. int main(int argc, char* argv[])
  4. {
  5.     QGuiApplication app(argc, argv);
  6.     // 实例化窗口
  7.     CustWindow window;
  8.     // 设置标题和大小
  9.     window.setTitle("Bug App");
  10.     window.resize(450, 450);
  11.     // 显示窗口
  12.     window.show();
  13.     return app.exec();
  14. }
复制代码
运行一下,看看咱们画的大饼,又大又黄。

 
看到这里,相信大伙伴们都了解 QWindow 怎么玩了。于是,咱们回归标题,这个类到底干吗呢?与 QWidget 类比如何?
1、QWindow 比 QWidget 更复杂,更难用,更麻烦,是一盏很浪费油的灯;
2、可是,它也不是没用的。QWidget 测重组件化,封装得好,开柜即用,方便组装。而 QWindow 更抽象,更高级,更灵活,用来装逼直接爆表。比如你有一个窗口只用来画一个图表,告诉用户,他最近抑郁症发作的频率和趋势,以及预测什么时候无可救药。这种情形就很适合使用 QWindow 来创建窗口。
总的来说,QWindow 类能做的事情更多,但需要投入的开发成本更高,代码量更吓人。我们知道,其实窗口上的控件(比如按钮、标签、复选框等)本质上也是窗口对象——只是嵌套在顶层窗口中,成了子窗口罢了。
QWindow 对象也可以嵌套使用的,这个老周会在下一篇水文中介绍。故,QWindow 类不仅能灵活的创建窗口,也能自制许多控件。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4