Tkinter: разрешить только один экземпляр окна Toplevel

Вопрос: У меня есть программа tkinter с несколькими окнами. Вот полный код, если он нужен. import tkinter as tk import tkinter.scrolledtext as tkst from tkinter import ttk import logging import time def popupmsg(msg): popup = tk.Toplevel() popup.wm_title("!") label = ttk.Label(popup, text=msg) label.pack(side="top", fill="x", pady=10) b1 = ttk.Button(popup, text="Okay", command=popup.destroy) b1.pack() popup.mainloop() def test1(): root.logger.error("Test") def

Вопрос:

У меня есть программа tkinter с несколькими окнами. Вот полный код, если он нужен.

import tkinter as tk import tkinter.scrolledtext as tkst from tkinter import ttk import logging import time def popupmsg(msg): popup = tk.Toplevel() popup.wm_title(«!») label = ttk.Label(popup, text=msg) label.pack(side=»top», fill=»x», pady=10) b1 = ttk.Button(popup, text=»Okay», command=popup.destroy) b1.pack() popup.mainloop() def test1(): root.logger.error(«Test») def toggle(self): t_btn = self.t_btn if t_btn.config(‘text’)[-1] == ‘Start’: t_btn.config(text=’Stop’) def startloop(): if root.flag: now = time.strftime(«%c») root.logger.error(now) root.after(30000, startloop) else: root.flag = True return startloop() else: t_btn.config(text=’Start’) root.logger.error(«Loop stopped») root.flag = False class TextHandler(logging.Handler): def __init__(self, text): # run the regular Handler __init__ logging.Handler.__init__(self) # Store a reference to the Text it will log to self.text = text def emit(self, record): msg = self.format(record) def append(): self.text.configure(state=’normal’) self.text.insert(tk.END, msg + ‘n’) self.text.configure(state=’disabled’) # Autoscroll to the bottom self.text.yview(tk.END) # This is necessary because we can’t modify the Text from other threads self.text.after(0, append) def create(self): # Create textLogger topframe = tk.Frame(root) topframe.pack(side=tk.TOP) st = tkst.ScrolledText(topframe, bg=»#00A09E», fg=»white», state=’disabled’) st.configure(font=’TkFixedFont’) st.pack() self.text_handler = TextHandler(st) # Add the handler to logger root.logger = logging.getLogger() root.logger.addHandler(self.text_handler) def stop(self): root.flag = False def start(self): if root.flag: root.logger.error(«error») root.after(1000, self.start) else: root.logger.error(«Loop stopped») root.flag = True return def loop(self): self.start() class HomePage(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) container = tk.Frame(self) container.pack(side=»top», fill=»both», expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) self.menubar = tk.Menu(container) # Create taskbar/menu file = tk.Menu(self.menubar) file.add_command(label=»Run», command=lambda: test1()) file.add_command(label=»Stop», command=lambda: test1()) file.add_separator() file.add_command(label=»Settings», command=lambda: Settings()) file.add_separator() file.add_command(label=»Quit», command=quit) self.menubar.add_cascade(label=»File», menu=file) self.master.config(menu=self.menubar) #logger and main loop th = TextHandler(«none») th.create() root.flag = True root.logger.error(«Welcome to ShiptScraper!») bottomframe = tk.Frame(self) bottomframe.pack(side=tk.BOTTOM) topframe = tk.Frame(self) topframe.pack(side=tk.TOP) self.t_btn = tk.Button(text=»Start», highlightbackground=»#56B426″, command=lambda: toggle(self)) self.t_btn.pack(pady=5) self.exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) self.exitButton.pack() root.setting = False class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) self.wm_title(«Settings») print(Settings.state(self)) exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=self.destroy) exitButton.pack() class Help(tk.Toplevel): def __init__(self, parent): tk.Toplevel.__init__(self, parent) self.wm_title(«Help») exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) exitButton.pack() if __name__ == «__main__»: root = tk.Tk() root.configure(background=»#56B426″) root.wm_title(«ShiptScraper») app = HomePage(root) app.mainloop()

В основном моя проблема заключается в том, что при нажатии команды ” Settings из меню открывается каждое окно Settings каждый раз, когда он щелкнет. Я не могу понять, как сделать так, чтобы он мог обнаружить, что один экземпляр окна уже открыт или нет. Я попытался использовать state() в качестве проверки метода в классе HomePage например

#in it respective place as shown above file.add_command(label=»Settings», command=lambda: self.open(Settings)) #outside the init as a method def open(self, window): if window.state(self) != ‘normal’: window()

Это возвращает эту ошибку

Exception in Tkinter callback Traceback (most recent call last): File «/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py», line 1550, in __call__ return self.func(*args) File «/Users/user/pythonProjects/ShiptScraper/ShiptScraperGUI.py», line 112, in <lambda> file.add_command(label=»Settings», command=lambda: self.open(Settings)) File «/Users/user/pythonProjects/ShiptScraper/ShiptScraperGUI.py», line 139, in open if window.state(self) != ‘normal’: File «/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py», line 1826, in wm_state return self.tk.call(‘wm’, ‘state’, self._w, newstate) _tkinter.TclError: window «.4319455216» isn’t a top-level window

Я пробовал использовать метод winfo_exists(), но, похоже, если я уже не уничтожил окно (которого у меня нет, если он еще не был открыт), это не принесет мне никакой пользы. Тем не менее здесь одна из тех комбо, которую я пробовал

def open(self, window): if window.winfo_exists(self) != 1: window()

Это, конечно, ничего не делает. Я не собираюсь проходить через все другие неправильные комбинации. Я пробовал, потому что честно в этот момент, я не могу вспомнить их всех.

Я также попытался определить эти open методы как функции за пределами любого класса, и они не работают и там, как правило, из – за путаницы self ключевых слов не определенных вне класса, но необходимости быть параметром winfo_exists() и state().

Я также думаю, что моя проблема в использовании этих функций как методов в классе HomePage заключается в том, что всякий раз, когда я ссылаюсь на self, он проверяет HomePage, а не какое-то окно, которое я передаю в качестве аргумента в методе. Хотя я не уверен, поэтому я здесь.

Действительно, то, что я пытаюсь сделать, это просто создать стандартный метод в моем окне HomePage который контролирует, как меню (и, возможно, кнопки позже) открывает окно. Это логично (в моем собственном psuedocode):

def open(window) if window does not exist: open an instance of window

Возможно ли это, или есть лучший подход к управлению окнами, который я должен принимать?

Edit: Я изначально пренебрегал упоминанием о том, что моя ОС Mac OSX работает с Mavericks. По-видимому, это может быть проблема OSX. Кроме того, если вы собираетесь уменьшить этот вопрос, по крайней мере, комментарий и сказать мне, почему/как я могу его пересмотреть, чтобы сделать его лучше.

Я сейчас пробовал эти комбинации

class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) self.wm_title(«Settings») # added grab_set() self.grab_set() # print(Settings.state(self)) exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=self.destroy) exitButton.pack()

а также

class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) self.wm_title(«Settings») # added grab_set() self.grab_set() self.focus() # print(Settings.state(self)) exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=self.destroy) exitButton.pack()

а также

class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) self.wm_title(«Settings») # added grab_set() self.attributes(«-topmost», True) # print(Settings.state(self)) exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=self.destroy) exitButton.pack()

а также

class Settings (tk.Toplevel):

def __init__(self, master=None): tk.Toplevel.__init__(self, master) self.wm_title(«Settings») # added grab_set() self.after(1, lambda: self.focus_force()) # print(Settings.state(self)) exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=self.destroy) exitButton.pack()

Редактировать # 2:

Я придумал обходной путь… Я ненавижу его. Но это работает, по крайней мере на данный момент. Я определенно все еще надеюсь на лучшее решение.

import tkinter as tk import tkinter.scrolledtext as tkst from tkinter import ttk import logging import time def popupmsg(msg): popup = tk.Toplevel() popup.wm_title(«!») label = ttk.Label(popup, text=msg) label.pack(side=»top», fill=»x», pady=10) b1 = ttk.Button(popup, text=»Okay», command=popup.destroy) b1.pack() popup.mainloop() def test1(): root.logger.error(«Test») def toggle(self): t_btn = self.t_btn if t_btn.config(‘text’)[-1] == ‘Start’: t_btn.config(text=’Stop’) def startloop(): if root.flag: now = time.strftime(«%c») root.logger.error(now) root.after(30000, startloop) else: root.flag = True return startloop() else: t_btn.config(text=’Start’) root.logger.error(«Loop stopped») root.flag = False class TextHandler(logging.Handler): def __init__(self, text): # run the regular Handler __init__ logging.Handler.__init__(self) # Store a reference to the Text it will log to self.text = text def emit(self, record): msg = self.format(record) def append(): self.text.configure(state=’normal’) self.text.insert(tk.END, msg + ‘n’) self.text.configure(state=’disabled’) # Autoscroll to the bottom self.text.yview(tk.END) # This is necessary because we can’t modify the Text from other threads self.text.after(0, append) def create(self): # Create textLogger topframe = tk.Frame(root) topframe.pack(side=tk.TOP) st = tkst.ScrolledText(topframe, bg=»#00A09E», fg=»white», state=’disabled’) st.configure(font=’TkFixedFont’) st.pack() self.text_handler = TextHandler(st) # Add the handler to logger root.logger = logging.getLogger() root.logger.addHandler(self.text_handler) def stop(self): root.flag = False def start(self): if root.flag: root.logger.error(«error») root.after(1000, self.start) else: root.logger.error(«Loop stopped») root.flag = True return def loop(self): self.start() class HomePage(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) container = tk.Frame(self) container.pack(side=»top», fill=»both», expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) # NEW added a flag for the Settings window root.settings = False self.menubar = tk.Menu(container) # Create taskbar/menu file = tk.Menu(self.menubar) file.add_command(label=»Run», command=lambda: test1()) file.add_command(label=»Stop», command=lambda: test1()) file.add_separator() # NEW now calling a method from Settings instead of Settings itself file.add_command(label=»Settings», command=lambda: Settings().open()) file.add_separator() file.add_command(label=»Quit», command=quit) self.menubar.add_cascade(label=»File», menu=file) self.master.config(menu=self.menubar) #logger and main loop th = TextHandler(«none») th.create() root.flag = True root.logger.error(«Welcome to ShiptScraper!») bottomframe = tk.Frame(self) bottomframe.pack(side=tk.BOTTOM) topframe = tk.Frame(self) topframe.pack(side=tk.TOP) self.t_btn = tk.Button(text=»Start», highlightbackground=»#56B426″, command=lambda: toggle(self)) self.t_btn.pack(pady=5) self.exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) self.exitButton.pack() root.setting = False class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) # NEW ‘open’ method which is being called. This checks the root.setting flag added in the HomePage class def open(self): #NEW if root setting is false, continue creation of of Settings window if not root.setting: self.wm_title(«Settings») # added grab_set() Settings.grab_set(self) #NEW edited the exitButton command, see close function below exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=lambda: close()) exitButton.pack() root.setting = True #NEW if the root.settings flag is TRUE this cancels window creation else: self.destroy() #NEW when close() is called it resets the root.setting flag to false, then destroys the window def close(): root.setting = False self.destroy() class Help(tk.Toplevel): def __init__(self, parent): tk.Toplevel.__init__(self, parent) self.wm_title(«Help») exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) exitButton.pack() if __name__ == «__main__»: root = tk.Tk() root.configure(background=»#56B426″) root.wm_title(«ShiptScraper») app = HomePage(root) app.mainloop()

Это похоже на полный и полный взлом, я чувствую себя грязным, глядя на него и даже более грязным для создания этой мерзости… но он работает, на данный момент, по крайней мере

ИЗМЕНИТЬ 3:

Добавлен протокол закрытия окна в ответ Джейкоба. Забыл объяснить это. Это последняя версия, которую я поделюсь, пока не приду к лучшему подходу.

import tkinter as tk import tkinter.scrolledtext as tkst from tkinter import ttk import logging import time def popupmsg(msg): popup = tk.Toplevel() popup.wm_title(«!») label = ttk.Label(popup, text=msg) label.pack(side=»top», fill=»x», pady=10) b1 = ttk.Button(popup, text=»Okay», command=popup.destroy) b1.pack() popup.mainloop() def test1(): root.logger.error(«Test») def toggle(self): t_btn = self.t_btn if t_btn.config(‘text’)[-1] == ‘Start’: t_btn.config(text=’Stop’) def startloop(): if root.flag: now = time.strftime(«%c») root.logger.error(now) root.after(30000, startloop) else: root.flag = True return startloop() else: t_btn.config(text=’Start’) root.logger.error(«Loop stopped») root.flag = False class TextHandler(logging.Handler): def __init__(self, text): # run the regular Handler __init__ logging.Handler.__init__(self) # Store a reference to the Text it will log to self.text = text def emit(self, record): msg = self.format(record) def append(): self.text.configure(state=’normal’) self.text.insert(tk.END, msg + ‘n’) self.text.configure(state=’disabled’) # Autoscroll to the bottom self.text.yview(tk.END) # This is necessary because we can’t modify the Text from other threads self.text.after(0, append) def create(self): # Create textLogger topframe = tk.Frame(root) topframe.pack(side=tk.TOP) st = tkst.ScrolledText(topframe, bg=»#00A09E», fg=»white», state=’disabled’) st.configure(font=’TkFixedFont’) st.pack() self.text_handler = TextHandler(st) # Add the handler to logger root.logger = logging.getLogger() root.logger.addHandler(self.text_handler) def stop(self): root.flag = False def start(self): if root.flag: root.logger.error(«error») root.after(1000, self.start) else: root.logger.error(«Loop stopped») root.flag = True return def loop(self): self.start() class HomePage(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) container = tk.Frame(self) container.pack(side=»top», fill=»both», expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) # NEW added a flag for the Settings window root.setting = True self.menubar = tk.Menu(container) # Create taskbar/menu file = tk.Menu(self.menubar) file.add_command(label=»Run», command=lambda: test1()) file.add_command(label=»Stop», command=lambda: test1()) file.add_separator() # NEW now calling a method from Settings instead of Settings itself file.add_command(label=»Settings», command=lambda: Settings().open()) file.add_separator() file.add_command(label=»Quit», command=quit) self.menubar.add_cascade(label=»File», menu=file) self.master.config(menu=self.menubar) #logger and main loop th = TextHandler(«none») th.create() root.flag = True root.logger.error(«Welcome to ShiptScraper!») bottomframe = tk.Frame(self) bottomframe.pack(side=tk.BOTTOM) topframe = tk.Frame(self) topframe.pack(side=tk.TOP) self.t_btn = tk.Button(text=»Start», highlightbackground=»#56B426″, command=lambda: toggle(self)) self.t_btn.pack(pady=5) self.exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) self.exitButton.pack() class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) # NEW ‘open’ method which is being called. This checks the root.setting flag added in the HomePage class def open(self): #NEW when close() is called it resets the root.setting flag to false, then destroys the window def close_TopLevel(): root.setting = True self.destroy() #NEW if root setting is false, continue creation of of Settings window if root.setting: self.wm_title(«Settings») #NEW adjust window close protocol and change root.setting to FALSE self.protocol(‘WM_DELETE_WINDOW’, close_TopLevel) root.setting = False #NEW edited the exitButton command, see close function below exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=lambda: close_TopLevel()) exitButton.pack() #NEW if the root.settings flag is TRUE this cancels window creation else: print(‘shit’) self.destroy() class Help(tk.Toplevel): def __init__(self, parent): tk.Toplevel.__init__(self, parent) self.wm_title(«Help») exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) exitButton.pack() if __name__ == «__main__»: root = tk.Tk() root.configure(background=»#56B426″) root.wm_title(«ShiptScraper») app = HomePage(root) app.mainloop() Лучший ответ:

tkinter grab_set() tkinter grab_set().

Измените раздел кода ниже:

class Settings(tk.Toplevel): def __init__(self, master=None): tk.Toplevel.__init__(self, master) self.wm_title(«Settings») # added grab_set() self.grab_set() # print(Settings.state(self)) exitButton = tk.Button(self, text=»Exit», highlightbackground=»#56B426″, command=self.destroy) exitButton.pack()

Теперь, когда вы открываете окно настроек, главное окно не будет реагировать на нажатия кнопок, пока существует окно настроек.

См. Также здесь.

Содержание

  1. РЕДАКТИРОВАТЬ
  2. Обман и обман
  3. Измененный код:
  4. Заметка

РЕДАКТИРОВАТЬ

Обман и обман

Так как в Tkinter/OSX есть ошибка, связанная с использованием grab_set() которая отлично работает на Linux (Ubuntu 16.04), здесь есть некоторые обман и обман.

Я немного изменил ваш код. Для простоты причин, например, я добавил Toplevel окно в HomePage -class. Я отметил изменения ##.

Концепт:

  • Добавьте переменную в ваш класс, представляя, что существует окно настроек (или нет):

    self.check = False

  • Если вызывается окно настроек, значение изменяется:

    self.check = True

  • Функция вызова окна настроек теперь пассивна. Никаких дополнительных окон настроек:

    def call_settings(self): if self.check == False: self.settings_window()

  • Мы добавляем протокол в окно “Настройки”, чтобы запустить команду, если окно перестает существовать:

    self.settingswin.protocol(‘WM_DELETE_WINDOW’, self.close_Toplevel)

  • Затем вызываемая функция сбросит self.check:

    def close_Toplevel(self): self.check = False self.settingswin.destroy()

    Это будет работать независимо от того, как окно настроек было закрыто.

Измененный код:

import tkinter as tk import tkinter.scrolledtext as tkst from tkinter import ttk import logging import time def popupmsg(msg): popup = tk.Toplevel() popup.wm_title(«!») label = ttk.Label(popup, text=msg) label.pack(side=»top», fill=»x», pady=10) b1 = ttk.Button(popup, text=»Okay», command=popup.destroy) b1.pack() popup.mainloop() def test1(): root.logger.error(«Test») def toggle(self): t_btn = self.t_btn if t_btn.config(‘text’)[-1] == ‘Start’: t_btn.config(text=’Stop’) def startloop(): if root.flag: now = time.strftime(«%c») root.logger.error(now) root.after(30000, startloop) else: root.flag = True return startloop() else: t_btn.config(text=’Start’) root.logger.error(«Loop stopped») root.flag = False class TextHandler(logging.Handler): def __init__(self, text): # run the regular Handler __init__ logging.Handler.__init__(self) # Store a reference to the Text it will log to self.text = text def emit(self, record): msg = self.format(record) def append(): self.text.configure(state=’normal’) self.text.insert(tk.END, msg + ‘n’) self.text.configure(state=’disabled’) # Autoscroll to the bottom self.text.yview(tk.END) # This is necessary because we can’t modify the Text from other threads self.text.after(0, append) def create(self): # Create textLogger topframe = tk.Frame(root) topframe.pack(side=tk.TOP) st = tkst.ScrolledText(topframe, bg=»#00A09E», fg=»white», state=’disabled’) st.configure(font=’TkFixedFont’) st.pack() self.text_handler = TextHandler(st) # Add the handler to logger root.logger = logging.getLogger() root.logger.addHandler(self.text_handler) def stop(self): root.flag = False def start(self): if root.flag: root.logger.error(«error») root.after(1000, self.start) else: root.logger.error(«Loop stopped») root.flag = True return def loop(self): self.start() class HomePage(tk.Frame): def __init__(self, parent): tk.Frame.__init__(self, parent) container = tk.Frame(self) container.pack(side=»top», fill=»both», expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) self.menubar = tk.Menu(container) self.check = False ### new # Create taskbar/menu file = tk.Menu(self.menubar) file.add_command(label=»Run», command=lambda: test1()) file.add_command(label=»Stop», command=lambda: test1()) file.add_separator() file.add_command(label=»Settings», command=self.call_settings) #### new, changed command to run the function file.add_separator() file.add_command(label=»Quit», command=quit) self.menubar.add_cascade(label=»File», menu=file) self.master.config(menu=self.menubar) #logger and main loop th = TextHandler(«none») th.create() root.flag = True root.logger.error(«Welcome to ShiptScraper!») bottomframe = tk.Frame(self) bottomframe.pack(side=tk.BOTTOM) topframe = tk.Frame(self) topframe.pack(side=tk.TOP) self.t_btn = tk.Button(text=»Start», highlightbackground=»#56B426″, command=lambda: toggle(self)) self.t_btn.pack(pady=5) self.exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) self.exitButton.pack() root.setting = False ########## changed def call_settings(self): if self.check == False: self.settings_window() ########## def settings_window(self): self.check = True self.settingswin = tk.Toplevel() self.settingswin.wm_title(«Settings») self.settingswin.protocol(‘WM_DELETE_WINDOW’, self.close_Toplevel) ##### new exitButton = tk.Button(self.settingswin, text=»Exit», highlightbackground=»#56B426″, command=self.close_Toplevel) exitButton.pack() def close_Toplevel(self): # New, this runs when the Toplevel window closes, either by button or else self.check = False self.settingswin.destroy() class Help(tk.Toplevel): def __init__(self, parent): tk.Toplevel.__init__(self, parent) self.wm_title(«Help») exitButton = tk.Button(text=»Exit», highlightbackground=»#56B426″, command=quit) exitButton.pack() if __name__ == «__main__»: root = tk.Tk() root.configure(background=»#56B426″) root.wm_title(«ShiptScraper») app = HomePage(root) app.mainloop()

Заметка

Как только мы активировали существование окна настроек, мы можем сделать гораздо больше, конечно, отключить все кнопки на главном окнах, например. Таким образом, мы создали собственную версию grab_set() но еще больше flexibel.

Оцените статью
Добавить комментарий