我有一个带有多个窗口的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's 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
窗口中创建一个标准方法,控制菜单(以及稍后可能的按钮)打开窗口的方式。这在逻辑上(在我自己的伪代码中)是:
def open(window)
if window does not exist:
open an instance of window
这是可能的,还是我应该采取更好的窗口管理方法?
修改 我最初忽略了我的操作系统是运行Mavericks的Mac OSX。显然这可能是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()
和
课程设置(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:
在Jacob的回答中添加了关闭窗口的协议。忘记了这一点。这是我要分享的最后一个版本,除非我想出更好的方法。
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()
答案 0 :(得分:1)
tkinter
&#39; s 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()
现在,当您打开设置窗口时,在设置窗口存在时,主窗口不会对按钮单击做出反应。
另见here。
由于Tkinter / OSX中似乎存在一个关于使用grab_set()
的错误,它在Linux上运行正常(Ubuntu 16.04),这里有一些诡计和欺骗。
我编辑了一下你的代码。为了简单起见,我将Toplevel窗口添加到HomePage
- 类中。我标记了更改##
。
概念:
向您的类添加一个变量,表示“设置”窗口存在(或不存在)这一事实:
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()
版本,但更加灵活。