经过一番研究,我可以自己回答这个问题:
在 Mac 上这是可能的,因为这是该操作系统的一个常见功能。
在 Windows 和 Linux 上,这可以通过一个丑陋的解决方法来实现。我想知道为什么这些操作系统不支持此功能,因为此功能确实有一些用例。
这是适用于 Windows 的解决方法(也提到了 here):
- 获取系统托盘区:
def FindSysPagerWindow():
hWnd = win32gui.FindWindowEx(win32gui.GetDesktopWindow(), 0, "Shell_TrayWnd", None)
if hWnd:
hWnd = win32gui.FindWindowEx(hWnd, None, "TrayNotifyWnd", None)
if hWnd:
hWnd = win32gui.FindWindowEx(hWnd, None, "SysPager", None)
return hWnd
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, r):
super().__init__()
hSysPager = FindSysPagerWindow()
# Get rectangle of system area
self.region = win32gui.GetWindowRect(hSysPager)
self.frm = None
self.source = None
- 创建一个线程来检测拖动开始事件并在系统托盘上方创建一个透明框架:
def callback(self, hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime):
length = user32.GetWindowTextLengthW(hwnd)
title = ctypes.create_unicode_buffer(length + 1)
user32.GetWindowTextW(hwnd, title, length + 1)
if self.frm is None and (title.value == "Drag"):
self.source = GetProcessFilename(GetProcessId(dwEventThread, hwnd))
self.frm = SystemTrayFrame(self.region, self.onDrop)
def DragDetectThread(self):
ole32.CoInitialize(0)
WinEventProc = WinEventProcType(self.callback)
user32.SetWinEventHook.restype = ctypes.wintypes.HANDLE
hookId = user32.SetWinEventHook(win32con.EVENT_OBJECT_SHOW, win32con.EVENT_OBJECT_SHOW,
0, WinEventProc, 0, 0, win32con.WINEVENT_OUTOFCONTEXT)
msg = ctypes.wintypes.MSG()
while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) != 0:
user32.TranslateMessageW(msg)
user32.DispatchMessageW(msg)
user32.UnhookWinEvent(hookId)
ole32.CoUninitialize()
这里还有一个未解决的问题是正确拖动源的检测不可靠。如果鼠标在拖动开始时移动得太快,则检测到的源可能是错误的。但这只有在这些信息很重要时才会成为问题。
- 使用 pynput 创建鼠标按钮事件的侦听器,以检测鼠标左键向上事件,该事件被解释为拖动结束事件。监听器和 onDrop 方法都会破坏透明框架:
from pynput.mouse import Listener, Button
...
self.listener = Listener(on_click=self.onMouseButtonEvent)
self.listener.start()
def onMouseButtonEvent(self, x, y, button, pressed):
if self.frm is not None and (button == Button.left) and not pressed:
self.frm.Destroy()
self.frm = None
def onDrop(self, x, y, data):
# Do something with the dropped data
if self.frm is not None:
self.frm.Destroy()
self.frm = None
- 透明框架的类如下所示:
class SystemTrayFrame(wx.Frame):
def __init__(self, r, cbDrop):
super().__init__(None, wx.ID_ANY, "TransparentFrame", pos=(r[0], r[1]), size=(r[2] - r[0], r[3] - r[1]),
style=wx.STAY_ON_TOP)
dropTarget = DropTarget(cbDrop)
self.SetDropTarget(dropTarget)
self.SetTransparent(0)
self.Show()
如果可以将整个系统托盘区域作为应用程序的放置目标,那么到此为止一切正常。但是,如果您想将拖放区域限制为系统托盘图标,您现在需要一个丑陋的解决方法:
a) 将您喜欢的系统托盘图标替换为您可以轻松检测到的具有独特颜色的图标。
b) 对系统托盘区域进行截图,将系统托盘图标替换回您喜欢的应用程序图标,然后搜索您唯一颜色图标的位置:
im = ImageGrab.grab(bbox=self.region)
# Search for icon position and size (because of optional scaling by OS)
当操作系统启用缩放时,搜索操作可能会稍微复杂一些。
c) 使用此结果来定位透明框架。
希望这可以帮助遇到同样问题的其他人。