问题
在Baidu上搜索 "PyQt setWindowFlags" 会有各种烂大街的讲述,但无非都围绕这样的写法:
self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint|QtCore.Qt.WindowStaysOnTopHint)
别的不说,很多人根本不懂这样的写法是怎么回事,而许多文章互相抄袭,也不知道为什么这样写,所以干脆不写。
但是我的需求不光在窗口初始化时调用,而且在使用中需要更改setWindowFlags函数set的内容。
这样的写法就很有弊端了,我举个栗子:
self.setWindowFlags(QtCore.Qt.FramelessWindowHint|QtCore.Qt.Tool|QtCore.Qt.WindowStaysOnTopHint)
在初始化时我已经设定了,但是我在中途突然想让窗口不再置顶
那么:
self.setWindowFlags(QtCore.Qt.FramelessWindowHint|QtCore.Qt.Tool)
如果我要的功能是任意二者组合呢?
有3种组合,那么我如果按照传统写法,需要写3个self.setWindowFlags(...)备用,这显然是不合理的,如果我的功能需求增加,用来设置的备选项就会指数增加。所有我要详细说一下setWindowFlags背后的原理。
setWindowFlags 的本质是什么
在Python中参数传入让人丈二和尚摸不着头脑,简直不知道是干嘛的
def setWindowFlags(self, Union, Qt_WindowFlags=None, Qt_WindowType=None): # real signature unknown; restored from __doc__
""" setWindowFlags(self, Union[Qt.WindowFlags, Qt.WindowType]) """
pass
但是传入参数上我找到了突破口,不管是什么说法,只要合并Flags项,都用到"xxx|xxx"的写法。在C++和Python中,"|"的含义都是"按位与"。
"按位与"的运算是基于二进制位的,如果有一个数字,先转换成二进制,对应位比较,其中任意一位为1则结果为1
那么如果这样使用,那么|的两边的数据类型我盲猜是int,果然被我猜中:
其余的在这里:
WindowStaysOnTopHint = 262144
Tool = 11
FramelessWindowHint = 2048
我们模拟一下合并的过程:
十进制下是:264,203?
可以看出每一个二进制位都应该充当开关的作用,控制着一些东西。
argument 1 has unexpected type ‘int‘
这样想就被打脸了,因为Qt的想法很奇妙:
print(type(QtCore.Qt.WindowStaysOnTopHint))
返回结果:
<class ‘PyQt5.QtCore.Qt.WindowType‘>
这说明我们传入的QtCore.Qt.xxx其实在Python里面是被WindowType这个class接管的,那么我们找到它:
C++版本的对应定义:
Qt::Widget //是一个窗口或部件,有父窗口就是部件,没有就是窗口
Qt::Window //是一个窗口,有窗口边框和标题
Qt::Dialog //是一个对话框窗口
Qt::Sheet //是一个窗口或部件Macintosh表单
Qt::Drawer //是一个窗口或部件Macintosh抽屉
Qt::Popup //是一个弹出式顶层窗口
Qt::Tool //是一个工具窗口
Qt::ToolTip //是一个提示窗口,没有标题栏和窗口边框
Qt::SplashScreen //是一个欢迎窗口,是QSplashScreen构造函数的默认值
Qt::Desktop //是一个桌面窗口或部件
Qt::SubWindow //是一个子窗口
其实是与C++里面的一堆东西一一对应的,同时传入的int参数也暴露了本质。那么在self.setWindowFlags(QtCore.Qt.FramelessWindowHint|QtCore.Qt.Tool)
的写法下其实是传入了被Qt包装好的开关代号(FramelessWindowHint之类常数)按位与后的结果,但是在真正到setWindowFlags之前经过了QtCore.Qt.WindowType一层包装,所以我们才可以如此"优雅"的使用Qt。
修改代码
那么找到问题就可以动刀了
self.setWindowFlags(QtCore.Qt.WindowType(Flags))
把原来的调用方式写成这样,Flags就可以传入int值了
WindowFlags = set()
def FramelessWindowHint(Choose=False):
""" Choose: 是否选择使用Tool这个选项 """
if Choose:
WindowFlags.add(int(QtCore.Qt.FramelessWindowHint))
else:
WindowFlags.remove(int(QtCore.Qt.FramelessWindowHint))
def WindowStaysOnTopHint(Choose=False):
""" Choose: 是否选择使用Tool这个选项 """
if Choose:
WindowFlags.add(int(QtCore.Qt.WindowStaysOnTopHint))
else:
WindowFlags.remove(int(QtCore.Qt.WindowStaysOnTopHint))
def Tool(Choose=False):
""" Choose: 是否选择使用Tool这个选项 """
if Choose:
WindowFlags.add(int(QtCore.Qt.Tool))
else:
WindowFlags.remove(int(QtCore.Qt.Tool))
def ChangeWindowFlags(init = True):
Flags = 0
for i in Space[‘WindowFlags‘]:
Flags = Flags | i
self.setWindowFlags(QtCore.Qt.WindowType(Flags))
if init:
return
if not self.isVisible():
self.setVisible(True)
把需要的功能加入集合,将集合里面的数据(int)按位与操作后传入self.setWindowFlags(QtCore.Qt.WindowType(Flags))
的Flags中,问题迎刃而解。
疑难
def ChangeWindowFlags(self,init = True):
中init的作用是进行执行控制的,在窗口没有显示的时候,Flags是没有意义的,此时窗口的Visible一定为False,强行设置为True会导致Qt的后续操作异常,所以在窗口正常显示前的ChangeWindowFlags调用都传入True,窗口显示后再调用setWindowFlags时会同时执行hide()导致Visible变为False,这时需要把Visible重新设置为True,使得窗口重写显示,所以ChangeWindowFlags传入False