2017-04-19 6 views
0

다소 큰 Python 3.6 프로젝트를위한 tkinter 창 관리를 테스트 중이며, 올바르게 얻을 수없는 것 또는 상당히 잘 이해할 수있는 것 하나가 있습니다. 다음 코드에서 창은 예상대로 닫히고 닫힙니다 (즉, 빨간색 '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()이 2 차 창을 닫을만큼 충분하지 않아야합니까? ... 객체를 인스턴스화하는 방식에 문제가 있습니까?

+0

문제를 자세히 설명해 주시겠습니까? Windows 컴퓨터에서 오류를 재현 할 수 없습니다. – Dashadower

+0

Mac에서는이 코드를 실행하고 두 번 이상의 보조 창을 열려면 두 번 이상 버튼을 클릭하고 backgroung에있는 창 중 하나를 클릭하면 현재 사용하고 있습니다 (예를 들어 첫 번째 보조 창), 키보드 바로 가기가 예상대로 작동하지 않습니다. 다른 창 (마지막으로 열린 창)이 닫힙니다. 그런 다음 전체 응용 프로그램을 종료하지 않으면 첫 번째 보조 창이 닫히지 않습니다. –

답변

1

코드를 여러 번 수정했습니다. 지금은 효과가 있습니다. 기본적으로 사용자의 메서드 app.create_detail_window을 호출 할 때마다 self.newDetailWindow 특성이 다시 할당되므로 'x'단추가 잘못된 창으로 전송됩니다. 나는 귀하의 경우, 코멘트에서 언급 한 바와 같이 당신이

#!/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을 사용하는 것처럼 보입니다. Toplevels 목록 인 App에 목록 클래스 변수를 추가했습니다.

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

를 생성 된 모든 Toplevel의를 저장하는 dict를 사용, 다음 코드는 여전히 작동합니다.

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