2017-04-19 149 views
0

我正在测试tkinter窗口管理的一个有点大的Python 3.6项目,有一件事情我似乎无法正确理解,甚至无法理解。在下面的代码中,窗口按预期打开和关闭(我的意思是,通过单击红色的'x'按钮或通过在OS X中按Command-W)。但是,当我尝试为次级窗口关闭事件广告回调时,事情变得混乱。例如,如果我有多个辅助窗口,则键盘快捷方式或按钮并不总是关闭活动窗口。任何关于这里有什么错误的想法?为什么“wm_protocol”会破坏Python3/tkinter中的正常窗口管理?

这是我目前的测试代码:

#!/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())窗口管理得到打破。不应该行self.newDetailsWindow.destroy()足以简单地关闭辅助窗口?...我是否在实例化对象时做了任何错误?

+0

您能详细说明是什么问题?我无法在Windows电脑上重现您的错误。 – Dashadower

+0

在我目前使用的Mac上,如果我运行此代码并且多次单击该按钮以打开两个或多个辅助窗口,并且如果在后台窗口中单击其中一个窗口(我的意思是例如第一个辅助窗口),键盘快捷方式不能按预期方式工作。我得到另一个窗口(最后一个窗口)关闭。然后,第一个辅助窗口不会关闭,除非我退出整个应用程序。 –

回答

1

我对你的代码做了一些调整。它应该现在工作。基本上,每次调用时,您的方法app.create_detail_window都重新分配了属性self.newDetailWindow,这就是为什么'x'按钮将被发送到错误的窗口。我用了一个dict来存储所有的Toplevel是你创建

#!/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() 
+0

它解决了这个问题。我真的需要深入了解这是如何工作:) –

+0

在你的答案中,关闭事件回调已被丢弃。但如果我理解正确,我现在可以安全地移动'self.newDetailsWindow [self.windows_count] .destroy '声明到该方法(当然加上括号)并用调用我的方法替换'wm_protocol'调用? –

+0

此外,它似乎字典正在增长,但从来没有得到它的项目删除。也许我们应该像@Dashadower建议的那样做,删除引用并在窗口关闭方法中相应地调整窗口计数。或者'destroy()'方法还确保在字典本身中进行清理? –

1

它看起来像使用self.newDetailsWindow使新的顶层垃圾收集现有的顶层。我在App中添加了一个列表类变量,它是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 
+0

它解决了这个问题。我真的需要非常好地看待这个工作:) –

0

正如在评论中你提到的情况下,下面的代码仍然有效。

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() 
相关问题