【Qt6】嵌套 QWindow

2023-05-03 05:04:10 来源:博客园

在上个世纪的文章中,老周简单介绍了 QWindow 类的基本使用——包括从 QWindow 类派生和从 QRasterWindow 类派生。

其实,QWindow 类并不是只能充当主窗口用,它也可以嵌套到父级窗口中,变成子级对象。咱们一般称之为【控件】。F 话不多讲,下面咱们用实际案例来说明。


(资料图片)

这个例子中老周定义了两个类:

MyControl:子窗口对象,充当控件角色。这里实现一个类似开关的控件。【关闭】状态下,控件的背景呈现为灰色,金色方块位于最左侧;当控件处于【开启】状态下,控件背景为红色,金色方块位于最右侧。MyWindow:作为窗口使用,里面包含 MyControl 对象。先看 MyControl 类。
class MyControl : public QRasterWindow{    Q_OBJECTpublic:    MyControl(QWindow *parent = nullptr);private:    // “开启”状态时的背景色    QColor _on_bgcolor;    // “关闭”状态时的背景色    QColor _off_bgcolor;    // 当前状态    bool _state;signals:    // 信号    void stateChanged(bool isOn);public:    // 获取状态    inline bool state() const { return _state; }    // 修改状态    inline void setState(bool s)    {        _state = s;        // 发出信号        emit stateChanged(_state);    }protected:    // 重写方法    void paintEvent(QPaintEvent *event) override;    void mousePressEvent(QMouseEvent* evebt) override;};

在私有成员中,两个 QColor 类型的变量分别表示控件处于【开】或【关】状态时的背景色。_state 是一个布尔值,true就是【开】,false就是【关】,用来存储控件的当前状态。

公共方法 state 获取当前状态,setState 方法用来修改当前状态,同时会发出 stateChanged 信号。信号成员咱们先放一下,后文再叙。

两个虚函数的重写。paintEvent 负责画出控件的模样;mousePressEvent 当鼠标左键按下时改变控件的状态。实现点击一下开启,再点击一下关闭的功能。

接下来是实现各个成员。先是构造函数。

MyControl::MyControl(QWindow* parent)    :QRasterWindow::QRasterWindow(parent),     _state(false),     _on_bgcolor(QColor("red")),     _off_bgcolor(QColor("gray")){}

构造函数主要用来初始化几个私有成员。下面代码实现鼠标左键按下后更改状态。

void MyControl::mousePressEvent(QMouseEvent *event){    if(! (event->buttons() & Qt::MouseButton::LeftButton))        return;  // 如果按的不是左键就 PASS        this->setState(!this->state());    update();}

每次点击后控件的状态都会取反(开变关,关变开),为了反映改变必须重新绘制控件,所以要调用 update 方法。

paintEvent 中实现绘制的过程。

void MyControl::paintEvent(QPaintEvent* event){    // 如果当前为不可见状态,就不绘图了    if(!isExposed())        return;    // 要绘制的区域    QRect rect = event -> rect();    QPainter painter;    painter.begin(this);    // 根据状态填充背景    QBrush bgbrush;    bgbrush.setStyle(Qt::SolidPattern);    if(_state)    {        bgbrush.setColor(_on_bgcolor);    }    else    {        bgbrush.setColor(_off_bgcolor);    }    painter.fillRect(rect, bgbrush);    QRect rectSq;    // 如果是“开”的状态,绿色矩形在右侧    if(_state)    {        rectSq.setX(rect.width() / 3 * 2);        rectSq.setWidth(rect.width() / 3);    }    // 如果为“关”的状态,绿色矩形在左侧    else{        rectSq.setWidth(rect.width() / 3);    }    rectSq.setHeight(rect.height());    painter.fillRect(rectSq, QColor("gold"));    painter.end();}

绘制分两步走。第一步是填充背景矩形,如果【开】就填充红色,如果【关】就填充灰色。此处用到了 QBrush 对象。根据不同颜色调用 setColor 方法来设置。这里要注意 QBrush 对象要调用 setStyle 方法设置画刷样式为SolidPattern。这是因为 QBrush 类默认的 style 是 NoBrush,因此要手动设置一下,不然看不到绘制。

第二步是绘制小方块。当控件状态为【开】时方块在右边,状态为【关】时方块在左边。小方块的宽度是控件宽度的 1/3,高度与控件相同。要显式调用 setHeight 方法设置矩形高度(因为它默认为0)。记得小方块的左上角的 X 坐标也要调整的。

下面是窗口 MyWindow 的成员。

class MyWindow : public QRasterWindow{    Q_OBJECTpublic:    MyWindow(QWindow *parent = nullptr);private:    MyControl *_control;    void initUI();protected:    void paintEvent(QPaintEvent *event) override;};

重写的 paintEvent 方法负责画窗口的背景。私有成员 initUI 用于初始化子窗口(MyControl对象)。initUI 方法在构造函数中调用。

MyWindow::MyWindow(QWindow *parent)    :QRasterWindow::QRasterWindow(parent){    initUI();}void MyWindow::initUI(){    // 初始化窗口    setTitle("示例程序");    resize(550, 450);    setMinimumSize(QSize(340, 200));    _control = new MyControl(this);    // 初始化子窗口    _control->setPosition(35, 40);    _control->resize(120, 35);    _control->setVisible(true);}

setVisible 方法使用控件变为可见,只有可见的对象才能被看到。

paintEvent 方法只负责画窗口背景。

void MyWindow::paintEvent(QPaintEvent *event){    // 只填充窗口    QPainter painter(this);    painter.fillRect(event->rect(), QColor("blue"));    painter.end();}

最后写 main 函数。

int main(int argc, char** argv){    QGuiApplication app(argc, argv);    MyWindow wind;    wind.show();    return QGuiApplication::exec();}

运行后,点一下子窗口,它就会改变颜色。

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

这个问题比较不确定,不同平台好像表现不一样。Windows 下在控件周围会多出白色区域(也可能是黑色);在 Debian 下没有出现白边。所以,为了尽可能地避免跨平台差异导致的问题,可以用 mask 让控件区域之外的地方变为透明,这样白边黑边就会消失。

调用 setMask 方法要在重写的 resizeEvent 方法中进行,这是为了响应控件大小改变后,及时调整要透明的区域。

void MyControl::resizeEvent(QResizeEvent *event){    setMask(QRect(QPoint(), event->size()));}

刚才,咱们 MyControl 类定义了 stateChanged 信号,并在修改控件状态后发出。接下来我们用一个 lambda 表达式来充当 slot,当控件状态改变后输出调试信息(使用 qDebug 宏)。

connect(_control, &MyControl::stateChanged, this, [](bool st){    qDebug() << "当前状态:" << st;});

再次运行程序,每点击一下控件,控制台就会输出一条调试信息。

setMask 还有一个经典用途——制作透明窗口。下面咱们顺便讨论一下如何做不规则形状的窗口。

a、调用 setFlags 方法设置WindowType::FramelessWindowHint 标志,去掉窗口的边框;

b、调用 setMask 方法时需要传递一个 QRegion 对象,它表示一个形状区域。这个区域可以是矩形,也可以是圆形,当然也可以是多边形。设置 mask 后,指定区域外的内容变成透明,并且不会响应用户的输入事件(鼠标、键盘等)。QRegion 对象可以进行 and、or 等运算,多个区域可以进行交集或并集处理,形成各种不规则图形。

咱位做个例子。

#ifndef APP_H#define APP_H#include #include #include #include #include #include #include #include #include #include #include #include class CustWindow : public QRasterWindow{    Q_OBJECTpublic:    CustWindow(QWindow *parent = nullptr);protected:    void paintEvent(QPaintEvent* event) override;    void resizeEvent(QResizeEvent* event) override;    // 窗口没了边框无法用常规操作,于是实现双击关闭窗口    void mouseDoubleClickEvent(QMouseEvent *event) override;};#endif
CustWindow::CustWindow(QWindow* parent)    :QRasterWindow::QRasterWindow(parent){    setFlags(Qt::WindowType::FramelessWindowHint|Qt::WindowType::BypassWindowManagerHint);    // 窗口位置和大小    setGeometry(600, 500, 400, 400);}void CustWindow::paintEvent(QPaintEvent* event){    QPainter p(this);    p.fillRect(event->rect(), QColor("deeppink"));    p.end();}void CustWindow::resizeEvent(QResizeEvent *event){    QRegion rg0(QRect(QPoint(), event->size()));    QList pt1 = {        {15,16},        {180, 300},        {320, 300},        {150, 57}    };    QPolygon pl1(pt1);    QRegion rg1(pl1);    QList pt2 = {        {315, 20},        {25, 160},        {160, 320},        {240, 150},        {330, 30}    };    QPolygon pl2(pt2);    QRegion rg2(pl2);    QRegion rg3 = rg0 & (rg1 | rg2);    setMask(rg3);}void CustWindow::mouseDoubleClickEvent(QMouseEvent *event){    if(event->button() == Qt::LeftButton)    {        this->close();   // 关闭窗口    }}

调用setFlags方法时,用到了WindowType::BypassWindowManagerHint 值。Windows 下没有影响,主要是针对 X11,去除第三方主题产生的窗口阴影。

在 resize 事件处理代码中,QPolygon 类用来构建多边形,通过 QList 对象来指定顶点列表。上面代码中是两个不规则图形并集后,再与窗口的矩形区域取交集。最后把这个区域外的内容都设置成了透明。

运行效果如下。

上一篇:

辣木籽的功效及吃法是什么_辣木籽的功效及吃法 世界关注

下一篇:

辣木籽的功效及吃法是什么_辣木籽的功效及吃法 世界关注

推荐阅读