现在的软件界面多姿多彩,越来越少软件直接使用系统默认的窗口边框。这样一来,Qt Widgets默认的风格反而显得有些土里土气。
可是,一个C++工程师,美工不行,搞qss都有些费劲了。如何才能优雅地(无感地)实现无边框程序呢?
目标
- 工程师写代码时无需考虑边框问题,一切都使用默认风格
- 程序完成后,将 QApplicaiton 直接换成 FramelessApplication ,完工!
- 适用 C++ Qt 和 PySide/PyQt
愿望很美好,可是网上铺天盖地的BorderlessWidget
,FramelessWindow
,...,都是对Widget这些控件直接上手。你怎么整了个 Application ?能好使吗??
FramelessApplication 从哪儿来??
没有现成的,手撸就好了,做成一个基础库:
- FramelessWindow:毫无疑问,首先要实现这个东西。从QWidget派生成,实现无边框风格。这一部分有很多杂乱的细节,网上的各种实现各有特点,此处不赘述。
- FramelessDockWidgetTitleBar:QMainWindow的停靠窗口,一旦悬浮,也有自己的边框风格。
- FramelessApplication:为程序中所有的窗口(QMainWindow,QDialog,等)自动应用上面两个类,隐藏实现细节。
FramelassApplication
接口很简单,如果不是为了处理个别情况,它不需要添加任何额外的API:
一般来说,需要为以下所有的窗口分别自动包裹一个无边框的widget:
- QMainWindow
- QDialog
- QMessageBox
- 其他非
Qt::Popup
的QWidget窗口等
对QDockWidget,自动应用自定义的titlebar。
另外,特别注意,Qt有时候会使用原生窗口(样式表对他们不生效),需要考虑好是否想包裹他们,尤其是:
- QFileDialog
- QFontDialog
- QColorDialog
无论如何,遇到如下标识,我们自动包裹它们是安全的:
1 2 3 4 5 |
|
准备工作做好后,注意,从类图可以看到,我们重写了notify函数,去截获我们要处理对象的如下两个事件就好了,要点:
QEvent::Show
:如果需要,用无边框窗口包裹QEvent::Close
:如果存在自动包裹的无边框窗口,将其移除【确保无内存泄漏】
另外,样式表也可以由FramelessApplication统一处理。
FramelessWindow
网上实现方式多样,反正接口也很简单:
我们知道,显示一个窗口,有三种方式:
- show():非模态,不阻塞
- open():模态,不阻塞
- exec():模态,阻塞(因为事件循环嵌套的问题,大程序中尽量避免使用)
为了偷懒,我不想为QDialog、QMainWindow 等分别定制无边框窗口,所以所有东西都塞到一个类里面了(注意处理Qt::WindowFlags
)。
特别注意,一个窗口一旦被Frameless窗口包裹后,Qt将不认为原窗口是一个窗口(废话),所以它关闭时收不到CloseEvent,需要特别处理。
python
Python下实现和这个一样,只不过,我不想用python再写一遍,但需要为我们前面实现的类库,使用SIP或Shibokenw创建python绑定。
这是另一个有趣故事。
使用方式
所有的代码封装到一个库中,写应用程序时,无需关注细节,直接使用就好:
让 FramelessApplication 自动包裹
1 2 3 4 5 6 7 8 9 |
|
手动包裹
对个别窗口或所有窗口,手动创建FramelessWindow并显示。
没问题
1 2 3 4 5 6 7 8 9 10 11 12 |
|
注意事项
尽管主打一个用户无感,但是有时可能是会有点影响,具体和FramelessWindow的实现也有关系。
场景1
1 2 3 4 5 |
|
我们需要清楚,在被包裹以后,这个dlg就不是一个真正的Dialog了。尽管一般都是无感的。
但如果这个QDialog内部还做了很多奇奇怪怪更精细的事情,特别时需要多次显示、隐藏、关闭,可能如下写法更安全一些:
1 2 3 4 5 |
|
不管它是否被包裹,都和肉眼所见的逻辑一样。