现代应用程序希望有一个与众不同的界面。系统提供的窗口的标题栏显然太千篇一律,无法满足我们的需求。但是,标题栏是由操作系统提供的,而操作系统没有提供任何方便修改标题栏的 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 评论
也是参考的别人的实现技术。
博主的帖子总是浅显易懂,让人茅塞顿开啊!