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

时间:2016-09-19 23:21:34

标签: python linux tkinter

我已阅读相关答案,似乎可接受的方法是将回调绑定到Toplevel小部件中的<Map><Unmap>事件。我试过以下但没有效果:

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()

我在Linux上运行python 2.7。这可能与不同操作系统中的窗口管理器代码有关吗?

tk.iconify()之前调用tk.mainloop()也无效。实际上,产生正确行为的唯一命令是tk.withdraw(),这与最小化窗口肯定不同。此外,如果通过调用<Map><Unmap>pack()触发grid()place()事件,为什么在应用程序窗口时会触发<Map>在Windows和/或Mac上最小化,thisthis回答建议。为什么在Linux上调用withdraw()deiconify()时会触发它们?

3 个答案:

答案 0 :(得分:2)

在Linux上取消映射

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

xdotool windowunmap <window_id>

xdotool windowmap <window_id>

为了看看我们是否甚至可以让tkinter检测窗口的状态最小化,我在你的基本窗口中添加了一个线程,每秒打印一次窗口的状态,使用:

root.state()

最小化与否,线程始终打印:

normal

解决方法

幸运的是,如果你必须能够检测窗口的最小化状态,那么在Linux上我们有其他工具,如xpropwmctrl。虽然它很脏,但在应用程序中可以很好地编写脚本。

根据评论中的要求,下面是一个简化示例,用于创建您自己的外部工具绑定版本。

enter image description here

如何运作

  • 当窗口出现(应用程序启动)时,我们使用wmctrl -lp通过检查名称 pid 来获取窗口的idtkinter个窗口有pid 0)。
  • 获得window id后,我们可以检查字符串_NET_WM_STATE_HIDDEN是否在xprop -id <window_id>的输出中。如果是这样,窗口就会最小化。

然后我们可以轻松使用tkinter的{​​{3}}来定期检查。在下面的例子中,评论应该说明一切。

我们需要什么

我们需要安装after() methodwmctrl。在基于Dedian的系统上:

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()

答案 1 :(得分:1)

我自己实施雅各布建议的hack

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()

答案 2 :(得分:0)

对我来说,在Win10上,你的代码运行得很完美,但需要注意的是,中间框架按钮会产生“可见”,无论它是“最大化”还是“恢复”。因此,最大化,然后恢复结果,2个新的“可见”变得可见。

我没有特别期待这一点,因为this reference表示地图是在

时生成的
  

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

Toplevels不受几何体管理。权威性更强tk doc

  

地图,取消地图

     

只要窗口的映射状态发生变化,就会生成Map和Unmap事件。

     

Windows以未映射状态创建。顶级窗口在转换到正常状态时会被映射,并在撤回和标志状态下取消映射。

尝试在主循环调用之前添加+。这应该与最小化按钮相同。如果它不会导致“不可见”,那么Linux上似乎存在tcl / tk错误。