問題
在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