По крайней мере, я нахожу вопрос от wxappbars и appbar . После некоторой адаптации к PyQt5 у меня это сработало.
"""
Registering an Application Desktop Toolbar in Windows for PyQt5 window.
Taken from https://gist.github.com/swdevbali/495f902162446b30cd567b2a44d86d79 and
https://github.com/sabren/ceomatic/blob/master/wxappbars.py,
thanks for swdevbali and sabren
Adapted by Elendiar.
"""
import ctypes, sys
from ctypes import wintypes
from ctypes import *
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QDesktopWidget, QWidget
from PyQt5.QtCore import *
from copy import deepcopy
shell32 = windll.shell32
user32 = windll.user32
import win32api, win32con, win32gui
class APPBARDATA(Structure):
_fields_= [
("cbSize", wintypes.DWORD),
("hWnd", wintypes.HWND),
("uCallbackMessage", ctypes.c_ulong),
("uEdge", c_ulong),
("rc", wintypes.RECT),
("lParam", wintypes.LPARAM),
]
PAPPBARDATA = POINTER(APPBARDATA)
class ABEdge:
Left = 0
Top = 1
Right = 2
Bottom = 3
Float = 4
class ABMsg:
ABM_NEW = 0
ABM_REMOVE = 1
ABM_QUERYPOS = 2
ABM_SETPOS = 3
ABM_GETSTATE = 4
ABM_GETTASKBARPOS = 5
ABM_ACTIVATE = 6
ABM_GETAUTOHIDEBAR = 7
ABM_SETAUTOHIDEBAR = 8
ABM_WINDOWPOSCHANGED = 9
ABM_SETSTATE = 10
class ABNotify:
ABN_STATECHANGE = 0
ABN_POSCHANGED = 1
ABN_FULLSCREENAPP = 2
ABN_WINDOWARRANGE = 3
class RegisterInfo(object):
def __init__(self):
self._window = None
self.callbackId = 0
self.isRegistered = False
self.edge = ABEdge.Float
self.originalStyle = None
self.originalPosition = None
self.originalSize = None
self.originalResizeMode = None
@property
def window(self):
return self._window
@window.setter
def window(self, window):
self._window = window
self._hWnd = window.winId().__int__()
self._oldWndProc = win32gui.SetWindowLong(self._hWnd,
win32con.GWL_WNDPROC,
self.WndProc)
# http://wiki.wxpython.org/HookingTheWndProc
def WndProc(self, hWnd, msg, wParam, lParam ):
if msg == win32con.WM_DESTROY:
self._restoreOldWndProc()
elif msg == self.callbackId:
if wParam == ABNotify.ABN_POSCHANGED:
_ABSetPos(self.edge, self.window)
else:
return win32gui.\
CallWindowProc(self._oldWndProc, hWnd, msg, wParam, lParam)
def _restoreOldWndProc(self):
win32api.SetWindowLong(self._hWnd,
win32con.GWL_WNDPROC,
self._oldWndProc)
_registeredWindowInfo = {}
def _GetRegisterInfo(appbarWindow):
geometry = appbarWindow.geometry()
reg = RegisterInfo()
reg.callBackId = 0
reg.window = appbarWindow
reg.isRegistered = False
reg.edge = ABEdge.Top
reg.originalStyle = appbarWindow.windowFlags()
reg.originalPosition = QPoint(geometry.x(), geometry.y())
reg.originalSize = QSize(geometry.width(), geometry.height())
_registeredWindowInfo[appbarWindow] = reg
return _registeredWindowInfo[appbarWindow]
def _RestoreWindow(appbarWindow):
info = _GetRegisterInfo(appbarWindow)
appbarWindow.setWindowFlags(info.originalStyle)
appbarWindow.move(info.originalPosition)
appbarWindow.resize(info.originalSize)
def SetAppBar(appbarWindow, edge, barSize):
info = _GetRegisterInfo(appbarWindow)
info.edge = edge
abd = APPBARDATA()
abd.cbSize = wintypes.DWORD(sizeof(abd))
abd.hWnd = wintypes.HWND(appbarWindow.winId().__int__())
if (edge == ABEdge.Float) and info.isRegistered:
shell32.SHAppBarMessage(ABMsg.ABM_REMOVE, PAPPBARDATA(abd))
info.isRegistered = False
_RestoreWindow(appbarWindow)
elif not info.isRegistered:
info.isRegistered = True
info.callbackId = win32api.RegisterWindowMessage("AppBarMessage")
shell32.SHAppBarMessage(ABMsg.ABM_NEW, PAPPBARDATA(abd))
appbarWindow.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
_ABSetPos(info.edge, appbarWindow, barSize)
def RemoveAppBar(appbarWindow):
print('REMOVING')
info = _GetRegisterInfo(appbarWindow)
abd = APPBARDATA()
shell32.SHAppBarMessage(ABMsg.ABM_REMOVE, PAPPBARDATA(abd))
info.isRegistered = False
#_RestoreWindow(appbarWindow)
def _DoResize(appBarWindow, rect):
appBarWindow.resize(rect.Width, rect.Height)
appBarWindow.move(rect.Left, rect.Top)
def _ABSetPos(edge, appbarWindow, barSize):
barData = APPBARDATA()
barData.cbSize = wintypes.DWORD(sizeof(barData))
barData.hWnd = appbarWindow.winId().__int__()
barData.uEdge = edge
screen = QDesktopWidget().screenGeometry()
deskW = screen.width()
deskH = screen.height()
if barData.uEdge == ABEdge.Left or barData.uEdge == ABEdge.Right:
winW = barSize
winH = deskH
barData.rc.top = 0
barData.rc.bottom = deskH
if barData.uEdge == ABEdge.Left:
barData.rc.left = 0
barData.rc.right = winW
else:
barData.rc.right = deskW
barData.rc.left = deskW - winW
else:
winW = deskW
winH = barSize
barData.rc.left = 0
barData.rc.right = deskW
if barData.uEdge == ABEdge.Top:
barData.rc.top = 0
barData.rc.bottom = winH
else:
barData.rc.bottom = deskH
barData.rc.top = deskH - winH
oldLeft = deepcopy(barData.rc.left) # deepcopy because after reopen x from 0
become w -> 222 and window isnt visible.
shell32.SHAppBarMessage(ABMsg.ABM_QUERYPOS, PAPPBARDATA(barData))
shell32.SHAppBarMessage(ABMsg.ABM_SETPOS, PAPPBARDATA(barData))
barData.rc.left = deepcopy(oldLeft)
def _resize():
x = barData.rc.left
y = barData.rc.top
w = barData.rc.right - barData.rc.left
h = barData.rc.bottom - barData.rc.top
appbarWindow.resize(w, h)
appbarWindow.move(x, y)
# This is done async, because windows will send a resize after a new appbar is
added.
# if we size right away, the windows resize comes last and overrides us.
QTimer.singleShot(300, _resize)
if __name__=="__main__":
# DPI scaling
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
# QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = QtWidgets.QApplication(sys.argv)
win = QWidget()
win.setWindowFlags(Qt.FramelessWindowHint |
Qt.WindowStaysOnTopHint |
Qt.CustomizeWindowHint |
Qt.Tool
)
btn = QtWidgets.QPushButton('Click-to-close', win)
btn.clicked.connect(QtWidgets.qApp.quit)
barSize = 222
SetAppBar(win, ABEdge.Left, barSize)
win.show()
sys.exit(app.exec_())