为什么 "wm_protocol" 在 Python3/tkinter 中破坏正常的 window 管理?
Why does "wm_protocol" break normal window management in Python3/tkinter?
我正在为一个较大的 Python 3.6 项目测试 tkinter window 管理,有一件事我似乎无法正确理解,甚至无法很好地理解。在下面的代码中,windows 按预期打开和关闭(我的意思是,通过单击红色 'x' 按钮或在 OS X 中按 Command-W)。但是当我尝试为辅助 window 关闭事件添加回调时,事情变得一团糟。例如,如果我有多个辅助 window,键盘快捷键甚至按钮并不总是关闭活动 window。知道这里出了什么问题吗?
这是我当前的测试代码:
#!/usr/bin/env python3.6
# encoding: utf-8
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
def create_detail_window(self, *event, number=None):
self.newDetailsWindow = tk.Toplevel(self.master)
self.newDetailsWindow.geometry('900x600+80+130')
self.newDetailsWindow.title(f'Detail: {number}')
self.newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", lambda: self.close_detail_window()) # This line breaks window management!...
self.detail_window = detailWindow(self.newDetailsWindow, 0)
self.newDetailsWindow.focus()
def close_detail_window(self, *event):
""" will test for some condition before closing, save if necessary and
then call destroy()
"""
self.newDetailsWindow.destroy() # Shouldn't this be enough to close the secondary window?...
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
似乎当我取消注释行 self.newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", lambda: self.close_detail_window())
时 window 管理被破坏了。 self.newDetailsWindow.destroy()
行不应该足以简单地关闭辅助 window 吗?...我在实例化对象的方式上做错了什么吗?
看起来使用 self.newDetailsWindow
使新的顶层垃圾收集现有的顶层。我在 App
中添加了一个列表 class 变量,它是 Toplevels 的列表。
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
self.windows = [] #This is a list of the created windows instances.
def create_detail_window(self, *event, number=None):
newDetailsWindow = tk.Toplevel(self.master)
self.windows.append(newDetailsWindow)
newDetailsWindow.geometry('900x600+80+130')
newDetailsWindow.title(f'Detail: {number}')
newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", lambda:
self.close_detail_window(newDetailsWindow)) # This line breaks window management!...
detail_window = detailWindow(newDetailsWindow, 0)
newDetailsWindow.focus()
def close_detail_window(self, window):
""" will test for some condition before closing, save if necessary and
then call destroy()
"""
self.windows.remove(window)
window.destroy() # destroy the specific instance in self.windows
我对你的代码做了几处调整。它现在应该可以工作了。基本上,你的方法 app.create_detail_window
每次调用它时都会重新分配属性 self.newDetailWindow
,这就是为什么 'x' 按钮会被发送到错误的 window。我使用 dict
来存储你创建的所有 Toplevel
s
#!/usr/bin/env python3.6
# encoding: utf-8
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
self.newDetailsWindow = {}
self.windows_count=0
def create_detail_window(self, *event, number=None):
self.windows_count+=1
self.newDetailsWindow[self.windows_count]=tk.Toplevel(self.master)
self.newDetailsWindow[self.windows_count].geometry('900x600+80+130')
self.newDetailsWindow[self.windows_count].title(f'Detail: {self.windows_count}')
self.newDetailsWindow[self.windows_count].wm_protocol("WM_DELETE_WINDOW", self.newDetailsWindow[self.windows_count].destroy)
#self.newDetailsWindow[self.windows_count].bind("Command-w", lambda event: self.newDetailsWindow[-1].destroy())
self.detail_window = detailWindow(self.newDetailsWindow[self.windows_count], self.windows_count)
self.newDetailsWindow[self.windows_count].focus()
print(self.newDetailsWindow)
def close_detail_window(self, *event):
""" will test for some condition before closing, save if necessary and
then call destroy()
"""
pass
#self.newDetailsWindow.destroy() # Shouldn't this be enough to close the secondary window?...
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
如评论中所述,在您的情况下,以下代码仍然有效。
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self))
self.btn.pack()
self.windows_count=0
def create_detail_window(self, *event):
self.windows_count+=1
newDetailsWindow=tk.Toplevel()
newDetailsWindow.geometry('900x600+80+130')
newDetailsWindow.title(f'Detail: {self.windows_count}')
newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", newDetailsWindow.destroy)
self.detail_window = detailWindow(newDetailsWindow, self.windows_count)
newDetailsWindow.focus()
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
我正在为一个较大的 Python 3.6 项目测试 tkinter window 管理,有一件事我似乎无法正确理解,甚至无法很好地理解。在下面的代码中,windows 按预期打开和关闭(我的意思是,通过单击红色 'x' 按钮或在 OS X 中按 Command-W)。但是当我尝试为辅助 window 关闭事件添加回调时,事情变得一团糟。例如,如果我有多个辅助 window,键盘快捷键甚至按钮并不总是关闭活动 window。知道这里出了什么问题吗?
这是我当前的测试代码:
#!/usr/bin/env python3.6
# encoding: utf-8
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
def create_detail_window(self, *event, number=None):
self.newDetailsWindow = tk.Toplevel(self.master)
self.newDetailsWindow.geometry('900x600+80+130')
self.newDetailsWindow.title(f'Detail: {number}')
self.newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", lambda: self.close_detail_window()) # This line breaks window management!...
self.detail_window = detailWindow(self.newDetailsWindow, 0)
self.newDetailsWindow.focus()
def close_detail_window(self, *event):
""" will test for some condition before closing, save if necessary and
then call destroy()
"""
self.newDetailsWindow.destroy() # Shouldn't this be enough to close the secondary window?...
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
似乎当我取消注释行 self.newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", lambda: self.close_detail_window())
时 window 管理被破坏了。 self.newDetailsWindow.destroy()
行不应该足以简单地关闭辅助 window 吗?...我在实例化对象的方式上做错了什么吗?
看起来使用 self.newDetailsWindow
使新的顶层垃圾收集现有的顶层。我在 App
中添加了一个列表 class 变量,它是 Toplevels 的列表。
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
self.windows = [] #This is a list of the created windows instances.
def create_detail_window(self, *event, number=None):
newDetailsWindow = tk.Toplevel(self.master)
self.windows.append(newDetailsWindow)
newDetailsWindow.geometry('900x600+80+130')
newDetailsWindow.title(f'Detail: {number}')
newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", lambda:
self.close_detail_window(newDetailsWindow)) # This line breaks window management!...
detail_window = detailWindow(newDetailsWindow, 0)
newDetailsWindow.focus()
def close_detail_window(self, window):
""" will test for some condition before closing, save if necessary and
then call destroy()
"""
self.windows.remove(window)
window.destroy() # destroy the specific instance in self.windows
我对你的代码做了几处调整。它现在应该可以工作了。基本上,你的方法 app.create_detail_window
每次调用它时都会重新分配属性 self.newDetailWindow
,这就是为什么 'x' 按钮会被发送到错误的 window。我使用 dict
来存储你创建的所有 Toplevel
s
#!/usr/bin/env python3.6
# encoding: utf-8
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self, number=0))
self.btn.pack()
self.newDetailsWindow = {}
self.windows_count=0
def create_detail_window(self, *event, number=None):
self.windows_count+=1
self.newDetailsWindow[self.windows_count]=tk.Toplevel(self.master)
self.newDetailsWindow[self.windows_count].geometry('900x600+80+130')
self.newDetailsWindow[self.windows_count].title(f'Detail: {self.windows_count}')
self.newDetailsWindow[self.windows_count].wm_protocol("WM_DELETE_WINDOW", self.newDetailsWindow[self.windows_count].destroy)
#self.newDetailsWindow[self.windows_count].bind("Command-w", lambda event: self.newDetailsWindow[-1].destroy())
self.detail_window = detailWindow(self.newDetailsWindow[self.windows_count], self.windows_count)
self.newDetailsWindow[self.windows_count].focus()
print(self.newDetailsWindow)
def close_detail_window(self, *event):
""" will test for some condition before closing, save if necessary and
then call destroy()
"""
pass
#self.newDetailsWindow.destroy() # Shouldn't this be enough to close the secondary window?...
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()
如评论中所述,在您的情况下,以下代码仍然有效。
import tkinter as tk
import tkinter.font
from tkinter import ttk
class baseApp(ttk.Frame):
"""
Parent classe for main app window (will include some aditional methods and properties).
"""
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
class App(baseApp):
""" Base class for the main application window """
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.master = master
self.lbl_text = ttk.Label(self.mainframe, text="This is the Main Window")
self.lbl_text.pack()
self.btn = ttk.Button(self.mainframe, text="Open Second window",
command=lambda: self.create_detail_window(self))
self.btn.pack()
self.windows_count=0
def create_detail_window(self, *event):
self.windows_count+=1
newDetailsWindow=tk.Toplevel()
newDetailsWindow.geometry('900x600+80+130')
newDetailsWindow.title(f'Detail: {self.windows_count}')
newDetailsWindow.wm_protocol("WM_DELETE_WINDOW", newDetailsWindow.destroy)
self.detail_window = detailWindow(newDetailsWindow, self.windows_count)
newDetailsWindow.focus()
class detailWindow(ttk.Frame):
""" Base class for secondary windows """
def __init__(self, master, rep_num, *args,**kwargs):
super().__init__(master,*args,**kwargs)
self.num_rep = rep_num
self.master.minsize(900, 600)
self.master.maxsize(900, 600)
print(f"Showing details about nr. {self.num_rep}")
self.mainframe = ttk.Frame(master)
self.mainframe.pack()
self.lbl_text = ttk.Label(self.mainframe,
text=f"Showing details about nr. {self.num_rep}")
self.lbl_text.pack()
if __name__ == "__main__":
root = tk.Tk()
janela_principal = App(root)
root.title('Main Window')
root.bind_all("<Mod2-q>", exit)
root.mainloop()