在以下示例中,我无法写入除默认打开的Log选项卡之外的其他选项卡:
#!/usr/bin/python
import os, sys
import threading
import time
from itertools import count
import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk
TCL_DONT_WAIT = 1<<1
TCL_WINDOW_EVENTS = 1<<2
TCL_FILE_EVENTS = 1<<3
TCL_TIMER_EVENTS = 1<<4
TCL_IDLE_EVENTS = 1<<5
TCL_ALL_EVENTS = 0
class GUI(threading.Thread):
def __init__(self):
""" thread init
defines some vars and starts stuff when the class is called (gui=GUI())
"""
self.root=tkinter.tix.Tk()
z = self.root.winfo_toplevel()
z.wm_title('minimal example')
if z.winfo_screenwidth() <= 800:
z.geometry('790x590+10+10')
else:
z.geometry('890x640+10+10')
frame1 = self.MkMainNotebook()
frame2 = self.MkMainStatus()
frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
frame2.pack(side=BOTTOM, fill=X)
z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
threading.Thread.__init__(self)
def run(self):
""" thread start
kick starts the main loop when the thread start()
"""
self.root.mainloop()
def stop(self):
""" escape plan
Exits gui thread
"""
self._stop()
raise SystemExit
def MkMainStatus(self):
""" status bar
"""
top = self.root
w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
self.exitbutton.grid(row=0, column=0, sticky=E, padx=3, pady=3)
return w
def MkMainNotebook(self):
""" the tabs frame
defines the tabs
"""
top = self.root
w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
tagPadX 6
tagPadY 4
borderWidth 2
""")
top['bg'] = w['bg']
w.add('log', label='Log', underline=0,
createcmd=lambda w=w, name='log': self.MkLog(w, name))
w.add('pro', label='Progress', underline=0,
createcmd=lambda w=w, name='pro': self.MkProgress(w, name))
w.add('set', label='Settings', underline=0,
createcmd=lambda w=w, name='set': self.MkSettings(w, name))
return w
def MkSettings(self, nb, name):
""" TODO: settings tab
"""
w = nb.page(name)
options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.SettingsFrame = settings_scr_win.window
def MkProgress(self, nb, name):
""" TODO: progress tab
"""
w = nb.page(name)
options = "label.padX 4"
progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.ProgressFrame = progress_scr_win.window
def MkLog(self, nb, name):
""" log
"""
w = nb.page(name)
options = "label.padX 4"
log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.LogFrame = log_scr_win.window
def main(argv):
""" main function
Keyword arguments:
args[0] --
args[1] --
Returns:
None
"""
#GUI
gui=GUI()
gui.start()
time.sleep(1) # give it a sec to draw the gui...
tix.Label(gui.LogFrame, text=("log")).pack()
tix.Label(gui.SettingsFrame, text=("settings")).pack()
tix.Label(gui.ProgressFrame, text=("progress")).pack()
return None
if __name__ == "__main__":
sys.exit(main(sys.argv))
控制台回溯:
Traceback (most recent call last):
File "C:minimal example.py", line 132, in <module>
sys.exit(main(sys.argv))
File "C:minimal example.py", line 126, in main
tix.Label(gui.SettingsFrame, text=("settings")).pack()
AttributeError: 'GUI' object has no attribute 'SettingsFrame'
每当我尝试在gui中创建小部件时,都会发生这种情况。设置帧或gui.ProgressFrame。如果我在main()中增加time.sleep(1),然后点击标题前的选项卡。标签部分开始,代码工作,因为现在调用了tabs命令。
那么,我如何声明gui。设置框架和gui。进度框架提前?我能在到达终点之前通过代码中的标签吗?main()中的标签?
注意:我需要它是线程的,它是一个更大的程序的一部分,它做了十几件不同的事情,并且是多线程的,有几个进程。
感谢编辑1:我可以给类添加方法,而不是引用框架:
def print_log(self, text):
""" log printer
"""
tix.Label(self.LogFrame, text=(text)).pack()
def print_progress(self, text):
""" log printer
"""
tix.Label(self.ProgressFrame, text=(text)).pack()
def print_settings(self, text):
""" log printer
"""
tix.Label(self.SettingsFrame, text=(text)).pack()
并在main中调用它们:
#tix.Label(gui.LogFrame, text=("log")).pack()
gui.print_log("log")
#tix.Label(gui.SettingsFrame, text=("settings")).pack()
gui.print_settings("settings")
#tix.Label(gui.ProgressFrame, text=("progress")).pack()
gui.print_progress("progress")
,但结果是一样的:
Traceback (most recent call last):
File "C:minimal example.py", line 150, in <module>
sys.exit(main(sys.argv))
File "C:minimal example.py", line 143, in main
gui.print_settings("settings")
File "C:minimal example.py", line 124, in print_settings
tix.Label(self.SettingsFrame, text=(text)).pack()
AttributeError: 'GUI' object has no attribute 'SettingsFrame'
编辑2:一个非常好的快速和简单的修复由Bryan Oakley:
#!/usr/bin/python
import os, sys
import threading
import time
from itertools import count
import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk
TCL_DONT_WAIT = 1<<1
TCL_WINDOW_EVENTS = 1<<2
TCL_FILE_EVENTS = 1<<3
TCL_TIMER_EVENTS = 1<<4
TCL_IDLE_EVENTS = 1<<5
TCL_ALL_EVENTS = 0
class GUI(threading.Thread):
def __init__(self):
""" thread init
defines some vars and starts stuff when the class is called (gui=GUI())
"""
self.root=tkinter.tix.Tk()
z = self.root.winfo_toplevel()
z.wm_title('minimal example')
if z.winfo_screenwidth() <= 800:
z.geometry('790x590+10+10')
else:
z.geometry('890x640+10+10')
frame1 = self.MkMainNotebook()
frame2 = self.MkMainStatus()
frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
frame2.pack(side=BOTTOM, fill=X)
z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
threading.Thread.__init__(self)
def run(self):
""" thread start
kick starts the main loop when the thread start()
"""
self.root.mainloop()
def stop(self):
""" escape plan
Exits gui thread
"""
self._stop()
raise SystemExit
def MkMainStatus(self):
""" status bar
"""
top = self.root
w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
self.exitbutton.grid(row=0, column=0, sticky=E, padx=3, pady=3)
return w
def MkMainNotebook(self):
""" the tabs frame
defines the tabs
"""
top = self.root
w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
tagPadX 6
tagPadY 4
borderWidth 2
""")
top['bg'] = w['bg']
w.add('log', label='Log', underline=0)
#createcmd=lambda w=w, name='log': self.MkLog(w, name))
w.add('pro', label='Progress', underline=0)
#createcmd=lambda w=w, name='pro': self.MkProgress(w, name))
w.add('set', label='Settings', underline=0)
#createcmd=lambda w=w, name='set': self.MkSettings(w, name))
#log_it = w.subwidget('log')
#pro_it = w.subwidget('pro')
#set_it = w.subwidget('set')
self.MkLog(w, 'log')
self.MkProgress(w, 'pro')
self.MkSettings(w, 'set')
return w
def MkSettings(self, nb, name):
""" TODO: settings tab
"""
w = nb.page(name)
options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.SettingsFrame = settings_scr_win.window
def MkProgress(self, nb, name):
""" TODO: progress tab
"""
w = nb.page(name)
options = "label.padX 4"
progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.ProgressFrame = progress_scr_win.window
def MkLog(self, nb, name):
""" log
"""
w = nb.page(name)
options = "label.padX 4"
log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.LogFrame = log_scr_win.window
def print_log(self, text):
""" log printer
"""
tix.Label(self.LogFrame, text=(text)).pack()
def print_progress(self, text):
""" log printer
"""
tix.Label(self.ProgressFrame, text=(text)).pack()
def print_settings(self, text):
""" log printer
"""
tix.Label(self.SettingsFrame, text=(text)).pack()
def main(argv):
""" main function
Keyword arguments:
args[0] --
args[1] --
Returns:
None
"""
#GUI
gui=GUI()
gui.start()
time.sleep(1) # give it a sec to draw the gui...
tix.Label(gui.LogFrame, text=("log")).pack()
#gui.print_log("log")
tix.Label(gui.SettingsFrame, text=("settings")).pack()
#gui.print_settings("settings")
tix.Label(gui.ProgressFrame, text=("progress")).pack()
#gui.print_progress("progress")
return None
if __name__ == "__main__":
sys.exit(main(sys.argv))
谢谢!
Edit 3:下面是一个线程安全的实现。我添加了一个额外的计时器:
#!/usr/bin/python
import os, sys
import threading
import queue
from queue import Empty
import time
from itertools import count
import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk
TCL_DONT_WAIT = 1<<1
TCL_WINDOW_EVENTS = 1<<2
TCL_FILE_EVENTS = 1<<3
TCL_TIMER_EVENTS = 1<<4
TCL_IDLE_EVENTS = 1<<5
TCL_ALL_EVENTS = 0
class GUI(threading.Thread):
def __init__(self):
""" thread init
defines some vars and starts stuff when the class is called (gui=GUI())
"""
self.root=tkinter.tix.Tk()
z = self.root.winfo_toplevel()
z.wm_title('minimal example')
if z.winfo_screenwidth() <= 800:
z.geometry('790x590+10+10')
else:
z.geometry('890x640+10+10')
frame1 = self.MkMainNotebook()
frame2 = self.MkMainStatus()
frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
frame2.pack(side=BOTTOM, fill=X)
z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
threading.Thread.__init__(self)
def run(self):
""" thread start
kick starts the main loop when the thread start()
"""
self.root.mainloop()
def stop(self):
""" escape plan
Exits gui thread
"""
self._stop()
raise SystemExit
def MkMainStatus(self):
""" status bar
"""
top = self.root
w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
self.status = tkinter.tix.Label(w, anchor=E, bd=1)
self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
self.print_queue=queue.Queue()
self.print_label()
self.status.grid(row=0, column=0, sticky=W, padx=3, pady=3)
self.exitbutton.grid(row=0, column=1, sticky=E, padx=3, pady=3)
return w
def print_label(self):
""" listner
listner
"""
rate=0.5 # seconds to re-read queue; 0.5=half a second, 1=a full second
counter = count(0, rate)
def update_func():
secs= str(counter.__next__())
try:
self.status.config(text=str("%s(secs): Processing queue..." % (secs.split('.'))[0]), fg=str("red"))
a = tix.Label(self.LogFrame, text=(self.print_queue.get(False)))
except Empty:
self.status.config(text=str("%s(secs): Waiting for queue..." % (secs.split('.'))[0]), fg=str("black"))
self.status.after(int(rate*1000), update_func)
else:
a.pack()
a.after(int(rate*1000), update_func)
update_func()
def MkMainNotebook(self):
""" the tabs frame
defines the tabs
"""
top = self.root
w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
tagPadX 6
tagPadY 4
borderWidth 2
""")
top['bg'] = w['bg']
w.add('log', label='Log', underline=0)
self.MkLog(w, 'log')
w.add('pro', label='Progress', underline=0)
self.MkProgress(w, 'pro')
w.add('set', label='Settings', underline=0)
self.MkSettings(w, 'set')
return w
def MkSettings(self, nb, name):
""" TODO: settings tab
"""
w = nb.page(name)
options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.SettingsFrame = settings_scr_win.window
def MkProgress(self, nb, name):
""" TODO: progress tab
"""
w = nb.page(name)
options = "label.padX 4"
progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.ProgressFrame = progress_scr_win.window
def MkLog(self, nb, name):
""" log
"""
w = nb.page(name)
options = "label.padX 4"
log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.LogFrame = log_scr_win.window
def main(argv):
""" main function
Keyword arguments:
args[0] --
args[1] --
Returns:
None
"""
#GUI
gui=GUI()
gui.start()
gui.print_queue.put("log")
time.sleep(10) # timed release test
gui.print_queue.put("timed release test")
return None
if __name__ == "__main__":
sys.exit(main(sys.argv))
这可能是在控制台应用程序中实现线程式图形用户界面的最简单方法。gui.print_queue.put()取代了print(), gui更新的速率可以在print_label(self)中通过调整速率变量来修改。
享受吧!
首先,Tkinter不是线程安全的。您不能从多个线程访问tk小部件。这样做会产生不可预测的行为(尽管通常可以预测它将导致崩溃)。
至于另一个问题——如何提前声明gui.SettingsFrame
——我不知道如何回答,除非陈述显而易见的事情。要"声明"它,你必须创建它,要创建它,你必须调用创建它的方法。你必须在使用gui.SettingsFrame
作为其他方法的参数之前这样做。
为什么要在创建框架的方法之外创建一个标签?我认为解决这个问题的方法是将"设置"标签的创建从Main
移动到MkSettings
(同样适用于"进度"one_answers"日志"标签)。