本文中所说的窗口都是指 decorated top-level widget,对于简单程序来说通常就是 QMainWindow 的子类对象。
之前玩 Qt 的时候一直以为 QMainWindow 的大小就是 .geometry()
报告的大小(也就是 .width()
和 .height()
),结果今天发现其实不对的。
起因是我想将一个窗口放在整个桌面的右下角。那么这里有几个问题。
首先 Windows 平台默认下面是任务栏,那么我们肯定不希望窗口的内容被任务栏挡住(或者挡住任务栏),所以这里需要使用 QDesktopWidget.availableGeometry()
来获取可用的屏幕。在 Windows 平台上,这个方法计算除了任务栏之外的屏幕区域;在 Mac 上会计算除了 Dock 栏和菜单栏的屏幕区域。(什么?Linux?抱歉我已经不在桌面上使用 Linux)
那么可能我们就会写出这样的代码
1
2
3
4
5
6
7
8
9
|
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
self.move(
desktop_geometry.width() - self.width(),
desktop_geometry.height() - self.height()
)
|
那么显然我们就是 too young。因为根据 Qt 文档,.geometry()
这一类的方法返回窗口 widget 区域(相对应地,.width()
和 .height()
都是返回窗口 widget 的宽高),而真实的窗口还带有操作系统窗口管理器附加的装饰,例如标题栏和额外的边框。看下图就懂了(图片来自 Qt 文档,链接在本段头部)。
好吧,那么我们现在改成这样就可以了吧?
1
2
3
4
5
6
7
8
9
10
|
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
self_geometry = self.frameGeometry()
self.move(
desktop_geometry.width() - self_geometry.width(),
desktop_geometry.height() - self_geometry.height()
)
|
那么显然我们又是 too simple。因为我测试了发现,如果这个窗口还没显示(__init__
里显然还没显示),那么 .frameGeometry()
这类方法的返回结果其实跟 .geometry()
这一类是一样的。换句话说,只有窗口被至少显示过一次,窗口管理器才有机会去装饰它,这样 Qt 才能知道最终窗口的实际占用区域大小。
那么我们只要在首次 showEvent
信号被触发的时候来获取窗口大小,然后据此移动窗口就可以了(当然如果你想每次显示都移动一下我也没话说 =,=
)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.__first_show = True
def showEvent(self, evt):
evt.accept()
if self.__first_show:
desktop_geopmetry = QtGui.qApp.desktop().availableGeometry()
self_geometry = self.frameGeometry()
self.move(
desktop_geometry.width() - self_geometry.width(),
desktop_geometry.height() - self_geometry.height()
)
self.__first_show = False
|