首页 Qt 自定义 Qt 窗口标题栏

自定义 Qt 窗口标题栏

2 1.9K

现代应用程序希望有一个与众不同的界面。系统提供的窗口的标题栏显然太千篇一律,无法满足我们的需求。但是,标题栏是由操作系统提供的,而操作系统没有提供任何方便修改标题栏的 API。首先想到的是,我们隐藏掉系统标题栏,然后绘制自己的进行替代。但是这么做还有一些额外的工作需要完成:提供窗口最大化、最小化等通用操作(必要的话);提供移动窗口的方法。下面就来看一下,如何完成这些操作。

本文提到的代码仅仅作为一种示例,很可能无法直接用到正式项目,算是提供一种思路,起到抛砖引玉的作用吧!

原文出处:http://developer.qt.nokia.com/faq/answer/how_can_i_handle_events_in_the_titlebar_and_change_its_color_etc,感谢原作者的精彩工作!

首先,我们需要有一个不带系统标题栏的窗口。我们可以使用QFrame子类,设置其 flag 为Qt::FramelessWindowHint。但是,这么做的副作用是,这个窗口再也不能被改变大小或者移动位置(这两个操作是由底层窗口系统提供,既然我们已经去除了底层系统的窗口支持,当然也就无法使用了)。然后,我们添加自己的标题栏:可以为这个 frame 添加一个成员,将其添加为 vertical layout 的第一个成员。然后,我们需要添加另外的 content 组件,用于添加其它组件。最后,这个QFrame的子类需要重新实现鼠标事件,以便处理窗口大小改变和移动。

下面就是我们的代码示例(为了方便起见,我们将实现代码放在了 .h 文件中。实际应用中不应该这么做):

#include <QtGui>

class TitleBar : public QWidget
{
    Q_OBJECT
public:
    TitleBar(QWidget *parent)
    {
        // 不继承父组件的背景色
        setAutoFillBackground(true);
        // 使用 Highlight 作为背景色
        setBackgroundRole(QPalette::Highlight);

        minimize = new QToolButton(this);
        maximize = new QToolButton(this);
        close= new QToolButton(this);

        // 设置按钮图像的样式
        QPixmap pix = style()->standardPixmap(QStyle::SP_TitleBarCloseButton);
        close->setIcon(pix);

        maxPix = style()->standardPixmap(QStyle::SP_TitleBarMaxButton);
        maximize->setIcon(maxPix);

        pix = style()->standardPixmap(QStyle::SP_TitleBarMinButton);
        minimize->setIcon(pix);

        restorePix = style()->standardPixmap(QStyle::SP_TitleBarNormalButton);

        minimize->setMinimumHeight(20);
        close->setMinimumHeight(20);
        maximize->setMinimumHeight(20);

        QLabel *label = new QLabel(this);
        label->setText("Window Title");
        parent->setWindowTitle("Window Title");

        QHBoxLayout *hbox = new QHBoxLayout(this);

        hbox->addWidget(label);
        hbox->addWidget(minimize);
        hbox->addWidget(maximize);
        hbox->addWidget(close);

        hbox->insertStretch(1, 500);
        hbox->setSpacing(0);
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);

        maxNormal = false;

        connect(close, SIGNAL( clicked() ), parent, SLOT(close() ) );
        connect(minimize, SIGNAL( clicked() ), this, SLOT(showSmall() ) );
        connect(maximize, SIGNAL( clicked() ), this, SLOT(showMaxRestore() ) );
    }

public slots:
    void showSmall()
    {
        parentWidget()->showMinimized();
    }

    void showMaxRestore()
    {
        if (maxNormal) {
            parentWidget()->showNormal();
            maxNormal = !maxNormal;
            maximize->setIcon(maxPix);
        } else {
            parentWidget()->showMaximized();
            maxNormal = !maxNormal;
            maximize->setIcon(restorePix);
        }
    }

protected:
    void mousePressEvent(QMouseEvent *me)
    {
        startPos = me->globalPos();
        clickPos = mapToParent(me->pos());
    }
    void mouseMoveEvent(QMouseEvent *me)
    {
        if (maxNormal)
            return;
        parentWidget()->move(me->globalPos() - clickPos);
    }

private:
    QToolButton *minimize;
    QToolButton *maximize;
    QToolButton *close;
    QPixmap restorePix, maxPix;
    bool maxNormal;
    QPoint startPos;
    QPoint clickPos;
};

class Frame : public QFrame
{
public:

    Frame()
    {
        mouseDown = false;
        setFrameShape(Panel);

        // 设置无边框窗口
        // 这会导致该窗口无法改变大小或移动
        setWindowFlags(Qt::FramelessWindowHint);
        setMouseTracking(true);

        titleBar = new TitleBar(this);

        content = new QWidget(this);

        QVBoxLayout *vbox = new QVBoxLayout(this);
        vbox->addWidget(titleBar);
        vbox->setMargin(0);
        vbox->setSpacing(0);

        QVBoxLayout *layout = new QVBoxLayout;
        layout->addWidget(content);
        layout->setMargin(5);
        layout->setSpacing(0);
        vbox->addLayout(layout);
    }

    // 通过 getter 允许外界访问 frame 的 content 区域
    // 其它子组件应该添加到这里
    QWidget *contentWidget() const { return content; }

    TitleBar *titleBar() const { return titleBar; }

    void mousePressEvent(QMouseEvent *e)
    {
        oldPos = e->pos();
        mouseDown = e->button() == Qt::LeftButton;
    }

    void mouseMoveEvent(QMouseEvent *e)
    {
        int x = e->x();
        int y = e->y();

        if (mouseDown) {
            int dx = x - oldPos.x();
            int dy = y - oldPos.y();

            QRect g = geometry();

            if (left)
                g.setLeft(g.left() + dx);
            if (right)
                g.setRight(g.right() + dx);
            if (bottom)
                g.setBottom(g.bottom() + dy);

            setGeometry(g);

            oldPos = QPoint(!left ? e->x() : oldPos.x(), e->y());
        } else {
            QRect r = rect();
            left = qAbs(x - r.left()) <= 5;
            right = qAbs(x - r.right()) <= 5;
            bottom = qAbs(y - r.bottom()) <= 5;
            bool hor = left | right;

            if (hor && bottom) {
                if (left)
                    setCursor(Qt::SizeBDiagCursor);
                else
                    setCursor(Qt::SizeFDiagCursor);
            } else if (hor) {
                setCursor(Qt::SizeHorCursor);
            } else if (bottom) {
                setCursor(Qt::SizeVerCursor);
            } else {
                setCursor(Qt::ArrowCursor);
            }
        }
    }

    void mouseReleaseEvent(QMouseEvent *e)
    {
        mouseDown = false;
    }

private:
    TitleBar *titleBar;
    QWidget *content;
    QPoint oldPos;
    bool mouseDown;
    bool left, right, bottom;
};

注意,当我们以很小的幅度改变窗口大小时,窗口可能会移动而不是改变大小。这是由于 Windows 系统限制引起的,对此我们也没什么好的解决办法。

再次提醒,这段代码仅仅是抛砖引玉的,希望大家可以由此获得思路,以实现更完善的代码。

2 评论

DevBean 2011年11月12日 - 20:31

也是参考的别人的实现技术。

回复
polokang 2011年11月22日 - 23:34

博主的帖子总是浅显易懂,让人茅塞顿开啊!

回复

发表评论

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2