【Qt6】嵌套 QWindow

打印 上一主题 下一主题

主题 907|帖子 907|积分 2731

在上个世纪的文章中,老周简单介绍了 QWindow 类的基本使用——包括从 QWindow 类派生和从 QRasterWindow 类派生。
其实,QWindow 类并不是只能充当主窗口用,它也可以嵌套到父级窗口中,变成子级对象。咱们一般称之为【控件】。F 话不多讲,下面咱们用实际案例来说明。
这个例子中老周定义了两个类:
MyControl:子窗口对象,充当控件角色。这里实现一个类似开关的控件。【关闭】状态下,控件的背景呈现为灰色,金色方块位于最左侧;当控件处于【开启】状态下,控件背景为红色,金色方块位于最右侧。MyWindow:作为窗口使用,里面包含 MyControl 对象。先看 MyControl 类。
  1. class MyControl : public QRasterWindow
  2. {
  3.     Q_OBJECT
  4. public:
  5.     MyControl(QWindow *parent = nullptr);
  6. private:
  7.     // “开启”状态时的背景色
  8.     QColor _on_bgcolor;
  9.     // “关闭”状态时的背景色
  10.     QColor _off_bgcolor;
  11.     // 当前状态
  12.     bool _state;
  13. signals:
  14.     // 信号
  15.     void stateChanged(bool isOn);
  16. public:
  17.     // 获取状态
  18.     inline bool state() const { return _state; }
  19.     // 修改状态
  20.     inline void setState(bool s)
  21.     {
  22.         _state = s;
  23.         // 发出信号
  24.         emit stateChanged(_state);
  25.     }
  26. protected:
  27.     // 重写方法
  28.     void paintEvent(QPaintEvent *event) override;
  29.     void mousePressEvent(QMouseEvent* evebt) override;
  30. };
复制代码
在私有成员中,两个 QColor 类型的变量分别表示控件处于【开】或【关】状态时的背景色。_state 是一个布尔值,true就是【开】,false就是【关】,用来存储控件的当前状态。
公共方法 state 获取当前状态,setState 方法用来修改当前状态,同时会发出 stateChanged 信号。信号成员咱们先放一下,后文再叙。
两个虚函数的重写。paintEvent 负责画出控件的模样;mousePressEvent 当鼠标左键按下时改变控件的状态。实现点击一下开启,再点击一下关闭的功能。
接下来是实现各个成员。先是构造函数。
  1. MyControl::MyControl(QWindow* parent)
  2.     :QRasterWindow::QRasterWindow(parent),
  3.      _state(false),
  4.      _on_bgcolor(QColor("red")),
  5.      _off_bgcolor(QColor("gray"))
  6. {
  7. }
复制代码
构造函数主要用来初始化几个私有成员。下面代码实现鼠标左键按下后更改状态。
  1. void MyControl::mousePressEvent(QMouseEvent *event)
  2. {
  3.     if(! (event->buttons() & Qt::MouseButton::LeftButton))
  4.         return;  // 如果按的不是左键就 PASS
  5.    
  6.     this->setState(!this->state());
  7.     update();
  8. }
复制代码
每次点击后控件的状态都会取反(开变关,关变开),为了反映改变必须重新绘制控件,所以要调用 update 方法。
paintEvent 中实现绘制的过程。
  1. void MyControl::paintEvent(QPaintEvent* event)
  2. {
  3.     // 如果当前为不可见状态,就不绘图了
  4.     if(!isExposed())
  5.         return;
  6.     // 要绘制的区域
  7.     QRect rect = event -> rect();
  8.     QPainter painter;
  9.     painter.begin(this);
  10.     // 根据状态填充背景
  11.     QBrush bgbrush;
  12.     bgbrush.setStyle(Qt::SolidPattern);
  13.     if(_state)
  14.     {
  15.         bgbrush.setColor(_on_bgcolor);
  16.     }
  17.     else
  18.     {
  19.         bgbrush.setColor(_off_bgcolor);
  20.     }
  21.     painter.fillRect(rect, bgbrush);
  22.     QRect rectSq;
  23.     // 如果是“开”的状态,绿色矩形在右侧
  24.     if(_state)
  25.     {
  26.         rectSq.setX(rect.width() / 3 * 2);
  27.         rectSq.setWidth(rect.width() / 3);
  28.     }
  29.     // 如果为“关”的状态,绿色矩形在左侧
  30.     else{
  31.         rectSq.setWidth(rect.width() / 3);
  32.     }
  33.     rectSq.setHeight(rect.height());
  34.     painter.fillRect(rectSq, QColor("gold"));
  35.     painter.end();
  36. }
复制代码
绘制分两步走。第一步是填充背景矩形,如果【开】就填充红色,如果【关】就填充灰色。此处用到了 QBrush 对象。根据不同颜色调用 setColor 方法来设置。这里要注意 QBrush 对象要调用 setStyle 方法设置画刷样式为 SolidPattern。这是因为 QBrush 类默认的 style 是 NoBrush,因此要手动设置一下,不然看不到绘制。
第二步是绘制小方块。当控件状态为【开】时方块在右边,状态为【关】时方块在左边。小方块的宽度是控件宽度的 1/3,高度与控件相同。要显式调用 setHeight 方法设置矩形高度(因为它默认为0)。记得小方块的左上角的 X 坐标也要调整的。
 
下面是窗口 MyWindow 的成员。
  1. class MyWindow : public QRasterWindow
  2. {
  3.     Q_OBJECT
  4. public:
  5.     MyWindow(QWindow *parent = nullptr);
  6. private:
  7.     MyControl *_control;
  8.     void initUI();
  9. protected:
  10.     void paintEvent(QPaintEvent *event) override;
  11. };
复制代码
重写的 paintEvent 方法负责画窗口的背景。私有成员 initUI 用于初始化子窗口(MyControl对象)。initUI 方法在构造函数中调用。
  1. MyWindow::MyWindow(QWindow *parent)
  2.     :QRasterWindow::QRasterWindow(parent)
  3. {
  4.     initUI();
  5. }
  6. void MyWindow::initUI()
  7. {
  8.     // 初始化窗口
  9.     setTitle("示例程序");
  10.     resize(550, 450);
  11.     setMinimumSize(QSize(340, 200));
  12.     _control = new MyControl(this);
  13.     // 初始化子窗口
  14.     _control->setPosition(35, 40);
  15.     _control->resize(120, 35);
  16.     _control->setVisible(true);
  17. }
复制代码
setVisible 方法使用控件变为可见,只有可见的对象才能被看到。
paintEvent 方法只负责画窗口背景。
  1. void MyWindow::paintEvent(QPaintEvent *event)
  2. {
  3.     // 只填充窗口
  4.     QPainter painter(this);
  5.     painter.fillRect(event->rect(), QColor("blue"));
  6.     painter.end();
  7. }
复制代码
 
最后写 main 函数。
  1. int main(int argc, char** argv)
  2. {
  3.     QGuiApplication app(argc, argv);
  4.     MyWindow wind;
  5.     wind.show();
  6.     return QGuiApplication::exec();
  7. }
复制代码
 
运行后,点一下子窗口,它就会改变颜色。

 
 关于控件周围有白边(或黑边)的问题:

这个问题比较不确定,不同平台好像表现不一样。Windows 下在控件周围会多出白色区域(也可能是黑色);在 Debian 下没有出现白边。所以,为了尽可能地避免跨平台差异导致的问题,可以用 mask 让控件区域之外的地方变为透明,这样白边黑边就会消失。
调用 setMask 方法要在重写的 resizeEvent 方法中进行,这是为了响应控件大小改变后,及时调整要透明的区域。
  1. void MyControl::resizeEvent(QResizeEvent *event)
  2. {
  3.     setMask(QRect(QPoint(), event->size()));
  4. }
复制代码
 
刚才,咱们 MyControl 类定义了 stateChanged 信号,并在修改控件状态后发出。接下来我们用一个 lambda 表达式来充当 slot,当控件状态改变后输出调试信息(使用 qDebug 宏)。
  1. connect(_control, &MyControl::stateChanged, this, [](bool st){
  2.     qDebug() << "当前状态:" << st;
  3. });
复制代码
调用setFlags方法时,用到了 WindowType::BypassWindowManagerHint 值。Windows 下没有影响,主要是针对 X11,去除第三方主题产生的窗口阴影。
在 resize 事件处理代码中,QPolygon 类用来构建多边形,通过 QList 对象来指定顶点列表。上面代码中是两个不规则图形并集后,再与窗口的矩形区域取交集。最后把这个区域外的内容都设置成了透明。
运行效果如下。

 

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

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表