如何解决PySide2 - 带有自定义标题栏的无框窗口 - 将其拖动到其他屏幕时出现问题 (Windows)
不时地,我尝试为我的 PyQt5/PySide2 应用程序创建自定义标题栏,但还没有想出如何正确地做到这一点。隐藏原始的 Windows 标题栏很容易,覆盖 mouseMoveEvents
以启用移动无框窗口也是如此。调整大小更具挑战性,但也是可能的。但对我来说最大的问题是我无法弄清楚如何维护 Windows Aero Snap 功能(通过按 Windows 键 + 箭头键或将其拖动到屏幕边框来将 Windows 对齐到位的能力)。
今天,我在GitHub上找到了解决这个问题的代码示例。它工作得很好,除非我将应用程序移动到另一个屏幕...... 当我将它拖到我的第二个屏幕时,我不能再移动它,也不能再按任何按钮。我只能调整它的大小。如果我将其调整回主屏幕,我可以再次拖动它。
代码如下:
import sys
import ctypes
from ctypes import wintypes
import win32api
import win32con
import win32gui
from PySide2.QtCore import Qt,QRect
from PySide2.QtGui import QColor,QWindow,QScreen,QCursor
from PySide2.QtWidgets import QWidget,QPushButton,QApplication,\
QVBoxLayout,QSizePolicy,QHBoxLayout
from PySide2.QtWinExtras import QtWin
class MINMAXINFO(ctypes.Structure):
_fields_ = [
("ptReserved",wintypes.POINT),("ptMaxSize",("ptMaxPosition",("ptMinTrackSize",("ptMaxTrackSize",]
class TitleBar(QWidget):
def __init__(self):
super().__init__()
self._layout = QHBoxLayout()
# set size
self.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Fixed)
self.setMinimumHeight(50)
self.button = QPushButton("EXIT",clicked=app.exit)
self.button.setStyleSheet("""
QPushButton{
border: none;
outline: none;
background-color: rgb(220,0);
color: white;
padding: 6px;
width: 80px;
font: 16px consolas;
}
QPushButton:hover{
background-color: rgb(240,0);
}
""")
# set background color
self.setAutoFillBackground(True)
p = self.palette()
p.setColor(self.backgroundRole(),QColor("#212121"))
self.setPalette(p)
self._layout.addStretch()
self._layout.addWidget(self.button)
self.setLayout(self._layout)
class Window(QWidget):
BorderWidth = 5
def __init__(self):
super().__init__()
# get the available resolutions without taskbar
self._rect = QApplication.instance().desktop().availableGeometry(self)
self.resize(800,600)
self.setWindowFlags(Qt.Window
| Qt.FramelessWindowHint
| Qt.WindowSystemMenuHint
| Qt.WindowMinimizeButtonHint
| Qt.WindowMaximizeButtonHint
| Qt.WindowCloseButtonHint)
self.current_screen = None
# Create a thin frame
style = win32gui.GetWindowLong(int(self.winId()),win32con.GWL_STYLE)
win32gui.SetWindowLong(int(self.winId()),win32con.GWL_STYLE,style | win32con.WS_THICKFRAME)
if QtWin.isCompositionEnabled():
# Aero Shadow
QtWin.extendFrameIntoClientArea(self,-1,-1)
pass
else:
QtWin.resetExtendedFrame(self)
# Window Widgets
self._layout = QVBoxLayout()
self._layout.setContentsMargins(0,0)
self._layout.setSpacing(0)
self.titleBar = TitleBar()
self.titleBar.setObjectName("titleBar")
# main widget is here
self.mainWidget = QWidget()
self.mainWidgetLayout = QVBoxLayout()
self.mainWidgetLayout.setContentsMargins(0,0)
# content
self.test_button = QPushButton('Test')
self.test_button.clicked.connect(self.on_test_button_clicked)
self.mainWidgetLayout.addWidget(self.test_button)
self.mainWidget.setLayout(self.mainWidgetLayout)
self.mainWidget.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
# set background color
self.mainWidget.setAutoFillBackground(True)
p = self.mainWidget.palette()
p.setColor(self.mainWidget.backgroundRole(),QColor("#272727"))
self.mainWidget.setPalette(p)
self._layout.addWidget(self.titleBar)
self._layout.addWidget(self.mainWidget)
self.setLayout(self._layout)
self.show()
def on_test_button_clicked(self):
self.updateGeometry()
def nativeEvent(self,eventType,message):
retval,result = super().nativeEvent(eventType,message)
# if you use Windows OS
if eventType == "windows_generic_MSG":
msg = ctypes.wintypes.MSG.from_address(message.__int__())
# Get the coordinates when the mouse moves.
x = win32api.LOWORD(ctypes.c_long(msg.lParam).value) - self.frameGeometry().x()
y = win32api.HIWORD(ctypes.c_long(msg.lParam).value) - self.frameGeometry().y()
# Determine whether there are other controls(i.e. widgets etc.) at the mouse position.
if self.childAt(x,y) is not None and self.childAt(x,y) is not self.findChild(QWidget,"titleBar"):
# passing
if self.width() - 5 > x > 5 and y < self.height() - 5:
return retval,result
if msg.message == win32con.WM_NCCALCSIZE:
# Remove system title
return True,0
if msg.message == win32con.WM_GETMINMAXINFO:
# This message is triggered when the window position or size changes.
info = ctypes.cast(
msg.lParam,ctypes.POINTER(MINMAXINFO)).contents
# Modify the maximized window size to the available size of the main screen.
info.ptMaxSize.x = self._rect.width()
info.ptMaxSize.y = self._rect.height()
# Modify the x and y coordinates of the placement point to (0,0).
info.ptMaxPosition.x,info.ptMaxPosition.y = 0,0
if msg.message == win32con.WM_NCHITTEST:
w,h = self.width(),self.height()
lx = x < self.BorderWidth
rx = x > w - self.BorderWidth
ty = y < self.BorderWidth
by = y > h - self.BorderWidth
if lx and ty:
return True,win32con.HTTOPLEFT
if rx and by:
return True,win32con.HTBOTTOMRIGHT
if rx and ty:
return True,win32con.HTTOPRIGHT
if lx and by:
return True,win32con.HTBOTTOMLEFT
if ty:
return True,win32con.HTTOP
if by:
return True,win32con.HTBOTTOM
if lx:
return True,win32con.HTLEFT
if rx:
return True,win32con.HTRIGHT
# Title
return True,win32con.HTCAPTION
return retval,result
def moveEvent(self,event):
if not self.current_screen:
print('Initial Screen')
self.current_screen = self.screen()
elif self.current_screen != self.screen():
print('Changed Screen')
self.current_screen = self.screen()
self.updateGeometry()
win32gui.SetWindowPos(int(self.winId()),win32con.NULL,win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_NOZORDER |
win32con.SWP_NOOWNERZORDER | win32con.SWP_FRAMECHANGED | win32con.SWP_NOACTIVATE)
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
sys.exit(app.exec_())
有人知道如何解决这个问题吗?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。