Python 绑定回调以最小化和最大化顶级窗口中的事件

Python 绑定回调以最小化和最大化顶级窗口中的事件,python,linux,tkinter,Python,Linux,Tkinter,我已经通读了相关的答案,似乎可以接受的方法是将回调绑定到顶级小部件中的和事件。我尝试了以下方法,但没有效果: from Tkinter import * tk = Tk() def visible(event): print 'visible' def invisible(event): print 'invisible' tk.bind('<Map>', visible) tk.bind('<Unmap>', invisible) tk.mai

我已经通读了相关的答案,似乎可以接受的方法是将回调绑定到顶级小部件中的
事件。我尝试了以下方法,但没有效果:

from Tkinter import *

tk = Tk()

def visible(event):
    print 'visible'

def invisible(event):
    print 'invisible'

tk.bind('<Map>', visible)
tk.bind('<Unmap>', invisible)

tk.mainloop()
从Tkinter导入*
tk=tk()
def可见(事件):
打印“可见”
def不可见(事件):
打印“不可见”
tk.bind(“”,可见)
tk.bind(“”,不可见)
tk.mainloop()
我正在Linux上运行python 2.7。这可能与不同操作系统中的窗口管理器代码有关吗


tk.mainloop()
之前调用
tk.iconify()
也没有效果。事实上,产生正确行为的唯一命令是
tk.draw()
,这与最小化窗口肯定不是一回事。另外,如果调用
事件是通过调用
pack()
grid()
place()
触发的,那么为什么在Windows和/或Mac上最小化应用程序窗口时会触发
,如和答案所示。为什么在Linux上调用
draw()
deiconify()
时会触发它们呢?

对于我来说,在Win10上,您的代码工作得很好,但需要注意的是,中间的框架按钮会产生“可见”,无论是“最大化”还是“还原”。因此,最大化后再进行恢复将导致2个新的“可见”变为可见

我并没有特别预料到这一点,因为我说地图是在

正在映射一个小部件,即使其在应用程序中可见。例如,当您调用小部件的.grid()方法时,就会发生这种情况

顶层不由几何图形管理。权威人士说

地图,取消地图

只要窗口的映射状态发生更改,就会生成映射和取消映射事件

窗口是在未映射状态下创建的。顶级窗口在转换到正常状态时将被映射,而在撤消和图标状态下将被取消映射

尝试在mainloop调用之前添加
tk.iconify()
。这应该与最小化按钮的操作相同。如果它没有导致“不可见”,那么Linux上似乎存在tcl/tk错误。

在Linux上取消映射 术语
Unmap
在Linux上的含义与在Windows上的含义完全不同。在Linux上,取消窗口映射意味着使其(几乎)无法跟踪;它不会出现在应用程序的图标中,也不会再在
wmctrl-l
的输出中列出。我们可以通过以下命令取消映射/映射窗口:

xdotool windowunmap <window_id>
无论是否最小化,线程始终打印:

normal
变通办法 幸运的是,如果您必须能够检测窗口的最小化状态,那么在Linux上,我们有类似
xprop
wmctrl
的替代工具。尽管它非常脏,但它在应用程序内部非常可靠地可以编写脚本

根据注释中的要求,下面是一个使用外部工具创建自己版本的绑定的简化示例

工作原理
  • 当窗口出现时(应用程序启动),我们使用
    wmctrl-lp
    通过检查名称和pid来获取窗口的
    id
    tkinter
    窗口的pid为0)
  • 一旦我们有了
    窗口id
    ,我们就可以检查
    xprop-id
    的输出中是否有字符串
    \u NET\u WM\u STATE\u HIDDEN
    。如果是这样,窗口将最小化
然后我们可以很容易地使用
tkinter
,包括定期检查。在下面的例子中,这些评论应该能说明问题

我们需要什么 我们需要同时安装和。基于Dedian的系统:

sudo apt-get install wmctrl xprop
代码示例
导入子流程
导入时间
从Tkinter进口*
类测试窗口:
定义初始(自我,主):
self.master=master
self.wintitle=“Testwindow”
self.checked=False
self.state=None
按钮=按钮(self.master,text=“按我”)
button.pack()
self.master.after(0,self.get_状态)
self.master.title(self.wintitle)
def get_窗口(自):
"""
通过标题和pid获取窗口(tkinter窗口的pid为0)
"""
在subprocess.check\u输出中为w返回[w.split()(
[“wmctrl”,“-lp”]
).decode(“utf-8”).splitlines()如果self.wintitle在w][1][0]中
def get_状态(自身):
"""
通过检查_NET_WM_state_HIDDEN是否在
xprop-id的输出
"""
尝试:
"""
checked=False是为了防止重复获取窗口id
(在回路中节省燃油)。确定窗口后,将通过进一步检查。
"""
self.match=self.get_window()如果self.checked==False,则self.match
self.checked=True
除索引器外:
通过
其他:
win_data=子进程。检查_输出([“xprop”,“-id”,self.match])。解码(“utf-8”)
如果win\u数据中有“\u NET\u WM\u STATE\u HIDDEN”:
newstate=“最小化”
其他:
newstate=“正常”
#仅当状态发生变化时才采取行动
如果是newstate!=自我状态:
印刷新闻状态
self.state=newstate
#每半秒钟检查一次
self.master.after(500,self.get_状态)
def main():
root=Tk()
app=TestWindow(根)
root.mainloop()
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
main()

我自己实现了Jacob建议的

from Tkinter import Tk, Toplevel
import subprocess


class CustomWindow(Toplevel):

    class State(object):
        NORMAL = 'normal'
        MINIMIZED = 'minimized'

    def __init__(self, parent, **kwargs):
        Toplevel.__init__(self, parent, **kwargs)
        self._state = CustomWindow.State.NORMAL
        self.protocol('WM_DELETE_WINDOW', self.quit)
        self.after(50, self._poll_window_state)

    def _poll_window_state(self):
        id = self.winfo_id() + 1
        winfo = subprocess.check_output(
            ['xprop', '-id', str(id)]).decode('utf-8')

        if '_NET_WM_STATE_HIDDEN' in winfo:
            state = CustomWindow.State.MINIMIZED
        else:
            state = CustomWindow.State.NORMAL

        if state != self._state:
            sequence = {
                CustomWindow.State.NORMAL: '<<Restore>>',
                CustomWindow.State.MINIMIZED: '<<Minimize>>'
            }[state]
            self.event_generate(sequence)
            self._state = state

        self.after(50, self._poll_window_state)


if __name__ == '__main__':
    root = Tk()
    root.withdraw()
    window = CustomWindow(root)

    def on_restore(event):
        print 'restore'

    def on_minimize(event):
        print 'minimize'

    window.bind('<<Restore>>', on_restore)
    window.bind('<<Minimize>>', on_minimize)

    root.mainloop()
从Tkinter导入Tk,顶级
导入子流程
类自定义窗口(顶级):
类状态(对象):
正常=‘正常’
最小化='MINIMIZED'
定义初始值(自、父、**kwargs):
顶层.uuuu初始化(自、父、**kwargs)
self.\u state=CustomWindow.state.NORMAL
self.protocol('WM\u DELETE\u WINDOW',self.quit)
自我后(50,自我调查,窗口,状态)
def_poll_w
sudo apt-get install wmctrl xprop
import subprocess
import time
from Tkinter import *

class TestWindow:

    def __init__(self, master):
        self.master = master
        self.wintitle = "Testwindow"
        self.checked = False
        self.state = None
        button = Button(self.master, text = "Press me")
        button.pack()
        self.master.after(0, self.get_state)
        self.master.title(self.wintitle)

    def get_window(self):
        """
        get the window by title and pid (tkinter windows have pid 0)
        """
        return [w.split() for w in subprocess.check_output(
            ["wmctrl", "-lp"]
            ).decode("utf-8").splitlines() if self.wintitle in w][-1][0]

    def get_state(self):
        """
        get the window state by checking if _NET_WM_STATE_HIDDEN is in the
        output of xprop -id <window_id>
        """
        try:
            """
            checked = False is to prevent repeatedly fetching the window id
            (saving fuel in the loop). after window is determined, it passes further checks.
            """
            self.match = self.get_window() if self.checked == False else self.match
            self.checked = True
        except IndexError:
            pass
        else:
            win_data = subprocess.check_output(["xprop", "-id", self.match]).decode("utf-8")
            if "_NET_WM_STATE_HIDDEN" in win_data:
                newstate = "minimized"
            else:
                newstate = "normal"
            # only take action if state changes
            if newstate != self.state:
                print newstate
                self.state = newstate
        # check once per half a second
        self.master.after(500, self.get_state)

def main(): 
    root = Tk()
    app = TestWindow(root)
    root.mainloop()

if __name__ == '__main__':
    main()
from Tkinter import Tk, Toplevel
import subprocess


class CustomWindow(Toplevel):

    class State(object):
        NORMAL = 'normal'
        MINIMIZED = 'minimized'

    def __init__(self, parent, **kwargs):
        Toplevel.__init__(self, parent, **kwargs)
        self._state = CustomWindow.State.NORMAL
        self.protocol('WM_DELETE_WINDOW', self.quit)
        self.after(50, self._poll_window_state)

    def _poll_window_state(self):
        id = self.winfo_id() + 1
        winfo = subprocess.check_output(
            ['xprop', '-id', str(id)]).decode('utf-8')

        if '_NET_WM_STATE_HIDDEN' in winfo:
            state = CustomWindow.State.MINIMIZED
        else:
            state = CustomWindow.State.NORMAL

        if state != self._state:
            sequence = {
                CustomWindow.State.NORMAL: '<<Restore>>',
                CustomWindow.State.MINIMIZED: '<<Minimize>>'
            }[state]
            self.event_generate(sequence)
            self._state = state

        self.after(50, self._poll_window_state)


if __name__ == '__main__':
    root = Tk()
    root.withdraw()
    window = CustomWindow(root)

    def on_restore(event):
        print 'restore'

    def on_minimize(event):
        print 'minimize'

    window.bind('<<Restore>>', on_restore)
    window.bind('<<Minimize>>', on_minimize)

    root.mainloop()