python 线程和 tkinter.tix.NoteBook 选项卡无法在第一个打开的选项卡之外访问
在下面的示例中,除了默认打开的“日志”选项卡之外,我无法写入其他选项卡:
#!/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.SettingsFrame 或 gui.ProgressFrame 中创建小部件时,就会发生这种情况。 如果我增加 main() 中的 time.sleep(1) 并在 tix.Label 部分启动之前单击选项卡,则代码从现在调用选项卡命令起就可以工作。
那么,如何提前声明 gui.SettingsFrame 和 gui.ProgressFrame 呢?在到达 main() 中的 tix.Label 之前,我可以通过代码中的选项卡吗?
注意:我需要它是线程化的,它是一个更大的程序的一部分,该程序执行许多不同的操作,并且是多线程的并且有多个进程。
谢谢
编辑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))
谢谢!
编辑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))
这可能是在控制台应用程序中实现线程 tkinter gui 的最简单方法。 gui.print_queue.put() 取代了 print(),并且可以通过调整速率变量在 print_label(self) 中修改 gui 更新的速率。
享受 !
In the following example I'm unable to write to the other tabs aside from the open-by-default Log tab:
#!/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))
The console traceback:
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'
This happens whenever I try to create widgets in gui.SettingsFrame or gui.ProgressFrame.
If I increase the time.sleep(1) in main() and click through the tabs before the tix.Label part starts, the code works since now the tabs command were invoked.
So, how do I declare the gui.SettingsFrame and gui.ProgressFrame in advance ? can I pass through the tabs in the code before I reach the tix.Label in main() ?
Note: I need it threaded, it's part of a larger program that does a dozen different things and is both multi threaded and has several processes.
Thanks
Edit 1:
I could add methods to the class instead of referring to the frames:
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()
and call them in 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")
but the results are the same:
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'
Edit 2: a very nice quick and easy fix by 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))
Thanks !
Edit 3: The following is a thread safe implementation. I added a bonus timer:
#!/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))
It's probably the simplest method for implementing a threaded tkinter gui to a console application. The gui.print_queue.put() replaces the print() and the rate the gui updates can be modified in print_label(self) by adjusting the rate variable.
Enjoy !
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
其一,Tkinter 不是线程安全的。您无法从多个线程访问 tk 小部件。这样做会产生不可预测的行为(尽管人们通常可以预测它会导致崩溃)。
至于另一个问题——如何提前声明
gui.SettingsFrame
——我不知道如何在不说明显而易见的情况下回答它。要“声明”它,您必须创建它,并且要创建它,您必须调用创建它的方法。您必须在使用 gui.SettingsFrame 作为其他方法的参数之前执行此操作。为什么要从创建框架的方法外部在框架中创建标签?我认为解决该问题的方法是将“设置”标签的创建从
Main
移动到MkSettings
(对于“进度”和“日志”标签也是如此) 。For one, Tkinter is not thread safe. You can't access the tk widgets from more than one thread. Doing so yields unpredictable behavior (though one can usually predict that it will cause crashes).
As for the other question -- how to declare
gui.SettingsFrame
in advance -- I'm not sure how to answer it without stating the obvious. To "declare" it you must create it, and to create it you must call the method that creates it. And you must do this before usinggui.SettingsFrame
as an argument to some other method.Why are you creating a label in a frame from outside of the method where the frame is created? I think the solution to that problem is to move the creation of the "settings" label from
Main
toMkSettings
(and likewise for the "progress" and "log" labels).