python窗口消息处理_python – Tkinter窗口事件

如果窗口可见性发生变化,我试图获取一个事件.

我发现有一个名为“可见性”的事件.

操作系统是Windows 64bit.

所以我用以下方式实现:

root.bind('', visibilityChanged)

但无论是否有窗户,我总是得到“VisibilityUnobscured”状态.这个事件的正常行为是什么?我该如何实现这样的功能?

示例程序:

import tkinter as tk

class GUI:

def __init__(self, master):

self.master = master

master.title("Test GUI")

self.master.bind('', 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()

解决方法:

What is the normal behaviour of this event?

它在docs中有详细描述:只要可见性改变状态和任何窗口,X服务器就会生成VisibilityNotify事件.

How can I implement a feature like that?

这取决于你愿意走多远,因为这不是一项微不足道的任务.因此,不要将该答案视为完整的解决方案,而应将其视为问题概述和一系列建议.

事件问题

Windows操作系统使用消息传递模型 – 系统通过消息与应用程序窗口进行通信,其中每条消息都是指定特定事件的数字代码.应用程序窗口具有关联的窗口过程 – 一个处理(响应或忽略)所有已发送消息的函数.

最通用的解决方案是设置钩子以捕获某些事件/消息,并且可以通过SetWindowsHookEx或pyHook.

主要问题是获取事件,因为Windows WM没有像VisibilityNotify这样的消息.正如我在评论部分所说 – 我们可以依赖的一个选项是z-order

(只要此窗口按z顺序更改其位置,就有可能检查窗口的可见性.)因此,我们的目标消息是WM_WINDOWPOSCHANGING或WM_WINDOWPOSCHANGED.

一个天真的实现:

import ctypes

import ctypes.wintypes as wintypes

import tkinter as tk

class CWPRETSTRUCT(ctypes.Structure):

''' a class to represent CWPRETSTRUCT structure

https://msdn.microsoft.com/en-us/library/windows/desktop/ms644963(v=vs.85).aspx '''

_fields_ = [('lResult', wintypes.LPARAM),

('lParam', wintypes.LPARAM),

('wParam', wintypes.WPARAM),

('message', wintypes.UINT),

('hwnd', wintypes.HWND)]

class WINDOWPOS(ctypes.Structure):

''' a class to represent WINDOWPOS structure

https://msdn.microsoft.com/en-gb/library/windows/desktop/ms632612(v=vs.85).aspx '''

_fields_ = [('hwnd', wintypes.HWND),

('hwndInsertAfter', wintypes.HWND),

('x', wintypes.INT),

('y', wintypes.INT),

('cx', wintypes.INT),

('cy', wintypes.INT),

('flags', wintypes.UINT)]

class App(tk.Tk):

''' generic tk app with win api interaction '''

wm_windowposschanged = 71

wh_callwndprocret = 12

swp_noownerzorder = 512

set_hook = ctypes.windll.user32.SetWindowsHookExW

call_next_hook = ctypes.windll.user32.CallNextHookEx

un_hook = ctypes.windll.user32.UnhookWindowsHookEx

get_thread = ctypes.windll.kernel32.GetCurrentThreadId

get_error = ctypes.windll.kernel32.GetLastError

get_parent = ctypes.windll.user32.GetParent

wnd_ret_proc = ctypes.WINFUNCTYPE(ctypes.c_long, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)

def __init__(self):

''' generic __init__ '''

super().__init__()

self.minsize(350, 200)

self.hook = self.setup_hook()

self.protocol('WM_DELETE_WINDOW', self.on_closing)

def setup_hook(self):

''' setting up the hook '''

thread = self.get_thread()

hook = self.set_hook(self.wh_callwndprocret, self.call_wnd_ret_proc, wintypes.HINSTANCE(0), thread)

if not hook:

raise ctypes.WinError(self.get_error())

return hook

def on_closing(self):

''' releasing the hook '''

if self.hook:

self.un_hook(self.hook)

self.destroy()

@staticmethod

@wnd_ret_proc

def call_wnd_ret_proc(nCode, wParam, lParam):

''' an implementation of the CallWndRetProc callback

https://msdn.microsoft.com/en-us/library/windows/desktop/ms644976(v=vs.85).aspx'''

# get a message

msg = ctypes.cast(lParam, ctypes.POINTER(CWPRETSTRUCT)).contents

if msg.message == App.wm_windowposschanged and msg.hwnd == App.get_parent(app.winfo_id()):

# if message, which belongs to owner hwnd, is signaling that windows position is changed - check z-order

wnd_pos = ctypes.cast(msg.lParam, ctypes.POINTER(WINDOWPOS)).contents

print('z-order changed: %r' % ((wnd_pos.flags & App.swp_noownerzorder) != App.swp_noownerzorder))

return App.call_next_hook(None, nCode, wParam, lParam)

app = App()

app.mainloop()

如您所见,此实现具有与“损坏的”Visibility事件类似的行为.

这个问题源于这样一个事实:您只能捕获线程指定的消息,因此应用程序不知道堆栈中的更改.这只是我的假设,但我认为可见性破裂的原因是一样的.

当然,我们可以为所有消息设置一个全局钩子,无论是一个线程,但这种方法需要DLL注入,这是另一个肯定的故事.

可见性问题

逻辑很简单:

>将窗口(以及每个可见窗口,在z顺序中更高)表示为矩形.

>从主矩形中减去每个矩形并存储结果.

如果最终的几何减法是:

> …一个空的矩形 – 返回’VisibilityFullyObscured’

> …一组矩形 – 返回’VisibilityPartiallyObscured’

> …一个矩形:

>如果结果和原始矩形之间的几何差异是:

> …一个空的矩形 – 返回’VisibilityUnobscured’

> …一个矩形 – 返回’VisibilityPartiallyObscured’

一个天真的实现(具有自调度循环):

import ctypes

import ctypes.wintypes as wintypes

import tkinter as tk

class App(tk.Tk):

''' generic tk app with win api interaction '''

enum_windows = ctypes.windll.user32.EnumWindows

is_window_visible = ctypes.windll.user32.IsWindowVisible

get_window_rect = ctypes.windll.user32.GetWindowRect

create_rect_rgn = ctypes.windll.gdi32.CreateRectRgn

combine_rgn = ctypes.windll.gdi32.CombineRgn

del_rgn = ctypes.windll.gdi32.DeleteObject

get_parent = ctypes.windll.user32.GetParent

enum_windows_proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)

def __init__(self):

''' generic __init__ '''

super().__init__()

self.minsize(350, 200)

self.status_label = tk.Label(self)

self.status_label.pack()

self.after(100, self.continuous_check)

self.state = ''

def continuous_check(self):

''' continuous (self-scheduled) check '''

state = self.determine_obscuration()

if self.state != state:

# mimic the event - fire only when state changes

print(state)

self.status_label.config(text=state)

self.state = state

self.after(100, self.continuous_check)

def enumerate_higher_windows(self, self_hwnd):

''' enumerate window, which has a higher position in z-order '''

@self.enum_windows_proc

def enum_func(hwnd, lParam):

''' clojure-callback for enumeration '''

rect = wintypes.RECT()

if hwnd == lParam:

# stop enumeration if hwnd is equal to lParam (self_hwnd)

return False

else:

# continue enumeration

if self.is_window_visible(hwnd):

self.get_window_rect(hwnd, ctypes.byref(rect))

rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)

# append region

rgns.append(rgn)

return True

rgns = []

self.enum_windows(enum_func, self_hwnd)

return rgns

def determine_obscuration(self):

''' determine obscuration via CombineRgn '''

hwnd = self.get_parent(self.winfo_id())

results = {1: 'VisibilityFullyObscured', 2: 'VisibilityUnobscured', 3: 'VisibilityPartiallyObscured'}

rgns = self.enumerate_higher_windows(hwnd)

result = 2

if len(rgns):

rect = wintypes.RECT()

self.get_window_rect(hwnd, ctypes.byref(rect))

# region of tk-window

reference_rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)

# temp region for storing diff and xor rgn-results

rgn = self.create_rect_rgn(0, 0, 0, 0)

# iterate over stored results

for _ in range(len(rgns)):

_rgn = rgn if _ != 0 else reference_rgn

result = self.combine_rgn(rgn, _rgn, rgns[_], 4)

self.del_rgn(rgns[_])

if result != 2:

# if result isn't a single rectangle

# (NULLREGION - 'VisibilityFullyObscured' or COMPLEXREGION - 'VisibilityPartiallyObscured')

pass

elif self.combine_rgn(rgn, reference_rgn, rgn, 3) == 1:

# if result of XOR is NULLREGION - 'VisibilityUnobscured'

result = 2

else:

# 'VisibilityPartiallyObscured'

result = 3

# clear up regions to prevent memory leaking

self.del_rgn(rgn)

self.del_rgn(reference_rgn)

return results[result]

app = App()

app.mainloop()

不幸的是,这种方法远非一种可行的解决方案,但从视角来看它是可调整的.

标签:python,visibility,tkinter

来源: https://codeday.me/bug/20190701/1347933.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值