Hol*_*urm 5 python visibility tkinter
如果Windows可见性已更改,我尝试获取一个事件。我发现有一个名为“可见性”的事件。操作系统是Windows 64位。因此,我以以下方式实现:
root.bind('<Visibility>', visibilityChanged)
Run Code Online (Sandbox Code Playgroud)
但是,无论是否有窗口,我总是处于“ VisibilityUnobscured”状态。此事件的正常行为是什么?如何实现这样的功能?
示例编:
import tkinter as tk
class GUI:
def __init__(self, master):
self.master = master
master.title("Test GUI")
self.master.bind('<Visibility>', self.visibilityChanged)
self.label = tk.Label(master, text="GUI")
self.label.pack()
self.close_button = tk.Button(master, text="Close", command=master.quit)
self.close_button.pack()
def visibilityChanged(self, event):
if (str(event.type) == "Visibility"):
print(event.state)
root = tk.Tk()
my_gui = GUI(root)
root.mainloop()
Run Code Online (Sandbox Code Playgroud)
\n\n\n此事件的正常行为是什么?
\n
文档中对此进行了很好的描述:每当可见性更改状态以及任何窗口时,
服务器X都会生成事件。VisibilityNotify
\n\n\n我怎样才能实现这样的功能?
\n
这取决于您要实现自己的愿望有多远,因为这不是一项微不足道的任务。因此,不要将该答案视为完整的解决方案,而是将其视为问题概述和一组建议。
\n\nWindows OS使用消息传递模型- 系统通过消息与应用程序窗口进行通信,其中每条消息都是指定特定事件的数字代码。应用程序窗口有一个关联的窗口过程\xe2\x80\x94,该函数处理(响应或忽略)所有发送的消息。
最通用的解决方案是设置一个钩子来捕获某些事件/消息,可以通过SetWindowsHookEx或pyHook来实现。
\n\n主要问题是获取事件,因为Windows WM没有诸如 之类的消息VisibilityNotify。正如我在评论部分中所说 - 我们可以依赖的一个选项是z-order\n (每当此窗口更改其在 中的位置时,都可以检查窗口的可见性z-order)。
因此,我们的目标消息是WM_WINDOWPOSCHANGING或WM_WINDOWPOSCHANGED。
一个幼稚的实现:
\n\nimport ctypes\nimport ctypes.wintypes as wintypes\nimport tkinter as tk\n\n\nclass CWPRETSTRUCT(ctypes.Structure):\n \'\'\' a class to represent CWPRETSTRUCT structure\n https://msdn.microsoft.com/en-us/library/windows/desktop/ms644963(v=vs.85).aspx \'\'\'\n\n _fields_ = [(\'lResult\', wintypes.LPARAM),\n (\'lParam\', wintypes.LPARAM),\n (\'wParam\', wintypes.WPARAM),\n (\'message\', wintypes.UINT),\n (\'hwnd\', wintypes.HWND)]\n\n\nclass WINDOWPOS(ctypes.Structure):\n \'\'\' a class to represent WINDOWPOS structure\n https://msdn.microsoft.com/en-gb/library/windows/desktop/ms632612(v=vs.85).aspx \'\'\'\n\n _fields_ = [(\'hwnd\', wintypes.HWND),\n (\'hwndInsertAfter\', wintypes.HWND),\n (\'x\', wintypes.INT),\n (\'y\', wintypes.INT),\n (\'cx\', wintypes.INT),\n (\'cy\', wintypes.INT),\n (\'flags\', wintypes.UINT)]\n\n\nclass App(tk.Tk):\n \'\'\' generic tk app with win api interaction \'\'\'\n\n wm_windowposschanged = 71\n wh_callwndprocret = 12\n swp_noownerzorder = 512\n set_hook = ctypes.windll.user32.SetWindowsHookExW\n call_next_hook = ctypes.windll.user32.CallNextHookEx\n un_hook = ctypes.windll.user32.UnhookWindowsHookEx\n get_thread = ctypes.windll.kernel32.GetCurrentThreadId\n get_error = ctypes.windll.kernel32.GetLastError\n get_parent = ctypes.windll.user32.GetParent\n wnd_ret_proc = ctypes.WINFUNCTYPE(ctypes.c_long, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)\n\n def __init__(self):\n \'\'\' generic __init__ \'\'\'\n\n super().__init__()\n self.minsize(350, 200)\n self.hook = self.setup_hook()\n self.protocol(\'WM_DELETE_WINDOW\', self.on_closing)\n\n def setup_hook(self):\n \'\'\' setting up the hook \'\'\'\n\n thread = self.get_thread()\n hook = self.set_hook(self.wh_callwndprocret, self.call_wnd_ret_proc, wintypes.HINSTANCE(0), thread)\n\n if not hook:\n raise ctypes.WinError(self.get_error())\n\n return hook\n\n def on_closing(self):\n \'\'\' releasing the hook \'\'\'\n if self.hook:\n self.un_hook(self.hook)\n self.destroy()\n\n @staticmethod\n @wnd_ret_proc\n def call_wnd_ret_proc(nCode, wParam, lParam):\n \'\'\' an implementation of the CallWndRetProc callback\n https://msdn.microsoft.com/en-us/library/windows/desktop/ms644976(v=vs.85).aspx\'\'\'\n\n # get a message\n msg = ctypes.cast(lParam, ctypes.POINTER(CWPRETSTRUCT)).contents\n if msg.message == App.wm_windowposschanged and msg.hwnd == App.get_parent(app.winfo_id()):\n # if message, which belongs to owner hwnd, is signaling that windows position is changed - check z-order\n wnd_pos = ctypes.cast(msg.lParam, ctypes.POINTER(WINDOWPOS)).contents\n print(\'z-order changed: %r\' % ((wnd_pos.flags & App.swp_noownerzorder) != App.swp_noownerzorder))\n\n return App.call_next_hook(None, nCode, wParam, lParam)\n\n\napp = App()\napp.mainloop()\nRun Code Online (Sandbox Code Playgroud)\n\n正如您所看到的,此实现具有与“损坏”事件类似的行为Visibility。
这个问题源于这样一个事实,即您只能捕获线程指定的消息,因此应用程序不知道堆栈中的更改。这只是我的假设,但我认为损坏的原因Visibility是相同的。
当然,我们可以为所有消息设置一个全局钩子,无论是哪个线程,但这种方法需要 DLL 注入,这肯定是另一回事了。
\n\n确定窗口的遮挡不是问题,因为我们可以依赖图形设备接口。
\n\n逻辑很简单:
\n\nz-order)表示为矩形。如果最终的几何减法是:
\n\nreturn \'VisibilityFullyObscured\'return \'VisibilityPartiallyObscured\'return \'VisibilityUnobscured\'return \'VisibilityPartiallyObscured\'一个简单的实现(带有自调度循环):
\n\nimport ctypes\nimport ctypes.wintypes as wintypes\nimport tkinter as tk\n\n\nclass App(tk.Tk):\n \'\'\' generic tk app with win api interaction \'\'\'\n enum_windows = ctypes.windll.user32.EnumWindows\n is_window_visible = ctypes.windll.user32.IsWindowVisible\n get_window_rect = ctypes.windll.user32.GetWindowRect\n create_rect_rgn = ctypes.windll.gdi32.CreateRectRgn\n combine_rgn = ctypes.windll.gdi32.CombineRgn\n del_rgn = ctypes.windll.gdi32.DeleteObject\n get_parent = ctypes.windll.user32.GetParent\n enum_windows_proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)\n\n def __init__(self):\n \'\'\' generic __init__ \'\'\'\n super().__init__()\n self.minsize(350, 200)\n\n self.status_label = tk.Label(self)\n self.status_label.pack()\n\n self.after(100, self.continuous_check)\n self.state = \'\'\n\n def continuous_check(self):\n \'\'\' continuous (self-scheduled) check \'\'\'\n state = self.determine_obscuration()\n\n if self.state != state:\n # mimic the event - fire only when state changes\n print(state)\n self.status_label.config(text=state)\n self.state = state\n self.after(100, self.continuous_check)\n\n def enumerate_higher_windows(self, self_hwnd):\n \'\'\' enumerate window, which has a higher position in z-order \'\'\'\n\n @self.enum_windows_proc\n def enum_func(hwnd, lParam):\n \'\'\' clojure-callback for enumeration \'\'\'\n rect = wintypes.RECT()\n if hwnd == lParam:\n # stop enumeration if hwnd is equal to lParam (self_hwnd)\n return False\n else:\n # continue enumeration\n if self.is_window_visible(hwnd):\n self.get_window_rect(hwnd, ctypes.byref(rect))\n rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)\n # append region\n rgns.append(rgn)\n return True\n\n rgns = []\n self.enum_windows(enum_func, self_hwnd)\n\n return rgns\n\n def determine_obscuration(self):\n \'\'\' determine obscuration via CombineRgn \'\'\'\n hwnd = self.get_parent(self.winfo_id())\n results = {1: \'VisibilityFullyObscured\', 2: \'VisibilityUnobscured\', 3: \'VisibilityPartiallyObscured\'}\n rgns = self.enumerate_higher_windows(hwnd)\n result = 2\n\n if len(rgns):\n rect = wintypes.RECT()\n self.get_window_rect(hwnd, ctypes.byref(rect))\n\n # region of tk-window\n reference_rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)\n # temp region for storing diff and xor rgn-results\n rgn = self.create_rect_rgn(0, 0, 0, 0)\n\n # iterate over stored results\n for _ in range(len(rgns)):\n _rgn = rgn if _ != 0 else reference_rgn\n result = self.combine_rgn(rgn, _rgn, rgns[_], 4)\n self.del_rgn(rgns[_])\n\n if result != 2:\n # if result isn\'t a single rectangle\n # (NULLREGION - \'VisibilityFullyObscured\' or COMPLEXREGION - \'VisibilityPartiallyObscured\')\n pass\n elif self.combine_rgn(rgn, reference_rgn, rgn, 3) == 1:\n # if result of XOR is NULLREGION - \'VisibilityUnobscured\'\n result = 2\n else:\n # \'VisibilityPartiallyObscured\'\n result = 3\n\n # clear up regions to prevent memory leaking\n self.del_rgn(rgn)\n self.del_rgn(reference_rgn)\n\n return results[result]\n\napp = App()\napp.mainloop()\nRun Code Online (Sandbox Code Playgroud)\n\n不幸的是,这种方法远不是一个有效的解决方案,但从某种角度来看它是可以调整的。
\n