我在使用Tkinter(ttk模块)并在后台运行函数并将该函数的print
(sys.stdout
)消息接收到GUI文本小部件self.constext
时遇到了严重问题。函数本身包含执行空间查询的类,大约需要15分钟,其间有打印语句来检查计算状态。在编辑这篇文章之前,我确实尝试了线程,队列实现ThreadingClient或QueueClient,但启动GUI总是导致程序崩溃
所以,到目前为止,这是我的代码
import tkFileDialog
import tkMessageBox
import ttk
import Tkinter
import Queue
import os
import time
import sys
import threading
import multiprocessing
import gemeindesteckbrief__SupportTools__
class SystemInfoSupport():
#def __init__(self, master,factshHW, factshHwGeb,factshHwSch, factshGem,
factshGeol, factshWLV) :
def __init__(self,master):
#Actual Window
self.sysInf =ttk.Frame (master)
self.sysInf.grid()
self.sysInf.grab_set()
self.incrVal = 0
#self.__calcFacthw = factshHW
#self.__calcFacthwGeb = factshHwGeb
#self.__calcFactHwSch = factshHwSch
#self.__calcFactGeol = factshGeol
#self.__calcFactWLV = factshWLV
#self.__calcFactGem = factshGem
#print self.__calcFacthwGeb
self.style= ttk.Style()
self.style.configure("Head.TLabel",foreground="#20B2AA", background="#E6E6FA", font = "Verdana 12 bold")
self.headLabel= ttk.Label (self.sysInf,text = "Systeminformation- Kalkulation",style = "Head.TLabel")
self.headLabel.grid(row=0, column =0, sticky ="NW",pady = 15, padx =20)
#Process OVerview
self.mainFrame = ttk.LabelFrame (self.sysInf,width=200,height=100)
self.mainFrame.grid(row=2, column =0, sticky = "NW", padx = 15, pady = 5)
self.style.configure("Prog.TLabel", font = "Verdana 10 italic underline")
self.progLabel= ttk.Label (self.mainFrame,text = "Räumliche Analysen-Fortschritt:", style ="Prog.TLabel")
self.progLabel.grid (row =2,column =1, sticky = "NW", padx = 10, pady = 2)
self.progBar= ttk.Progressbar(self.mainFrame,mode='determinate',length = 370, name='progBar1')
self.progBar.grid(row=3, column=0, columnspan=4,sticky ="NW", pady=5, padx=10)
self.style.configure("Scale.TLabel", font = "Verdana 8 bold")
self.scaleBounds = ttk.Label (self.mainFrame,text = "0 %\t\t\t\t\t 100 %")
self.scaleBounds.grid (row =4,column =1, sticky = "NW", padx = 5, pady = 1)
self.textFrame = ttk.LabelFrame (self.mainFrame,width=200,height=100)
self.textFrame.grid(row=5, column =1, sticky = "NW", padx = 5, pady = 10)
self.style.configure("Consol.TLabel", font ="Verdana 8 bold")
self.consLable = ttk.Label (self.textFrame,text = "Log-Console:",style ="Consol.TLabel")
self.consLable.grid (row =6,column =1, sticky = "NW", padx = 5, pady = 1)
self.consText= ttk.Tkinter.Text(self.textFrame, wrap = "word")
self.consText.grid(row =7,column =1, rowspan =4)
self.consText.tag_configure("stderr", foreground="#b22222")
self.scrollText= ttk.Scrollbar(self.textFrame,command = self.consText.yview)
self.scrollText.grid(row =7,column =2,rowspan =4,sticky='NSEW')
self.consText.config(yscrollcommand = self.scrollText.set)
self.cancelButton = ttk.Button (self.mainFrame, text ="Abbrechen",command = self.testProgBar)
self.cancelButton.grid (row =12,column =1)
sys.stdout = TextRedirector(self.consText, "stdout")
sys.stderr= TextRedirector(self.consText, "stderr")
# Create new threads
# run function in background using a ThreadingClient
# self.thread1 = gemeindesteckbrief_SpatialThread.SpatialThread(self.__calcFacthw, self.__calcFacthwGeb)
#run function in a threading.Thread
#self.thread1 = threading.Thread(name ="MyThread", target = self.prozessCalculateFactsheets)
#run function in a threading.Timer
#self.thread1= threading.Timer(2,self.calculateFactsheets)
#Start the thread
#self.thread1.start()
#self.check_thread()
#Check if thread is still executing or not
def check_thread(self):
# Still alive? Check again in half a second
if self.thread1.isAlive():
self.sysInf.after(500,self.check_thread)
# function to test the sys.stdout behaviour and writting to the Tkinter.text widget
def testProgBar (self):
print "hello my friend"
sys.stderr.write("hello my error friend\n")
self.sysInf.grab_release()
# the actual function needed to be executed in background
def calculateFactsheets (self):
# Read the directory to the input data of the init_File and add to a new Factsheet spatial calculation
try:
print "''''Hello from the Calculation Function ()''''"
requireData = gemeindesteckbrief__SupportTools__.ToolSet()
if self.__calcFacthw == 1:
factsheetHochw = gemeindesteckbrief_SpatialCalculFactshHochw.SpatialAnalysis_FactsheetHochwasser(requireData.readData(13),requireData.readData(10),
requireData.readData(20), requireData.readData(21), requireData.readData(22), requireData.readData(23), requireData.readData(24),
requireData.readData(25), requireData.readData(26))
factsheetHochw.verkExpertAnalyseGZPBWV()
factsheetHochw.verkExpertAnalyseHSG()
factsheetHochw.verkExpertAnalyseTotal()
factsheetHochw.verkExpertAnalyseGZPOI()
factsheetHochw.verkExpertAnalyseGZLOI()
factsheetHochw.verkExpertAnalyseHSGPOI()
factsheetHochw.verkExpertAnalysePLOITot()
factsheetHochw.verkExpertAnalyseLandWald()
factsheetHochw.verkExpertAnalyseLandWaldTotal()
if self.__calcFacthwGeb == 1:
print "FACTSHEET HOCHWASSER Gebaeude startet"
factsheetHochwGeb = gemeindesteckbrief_SpatialCalculFactshHochwGebaeude.SpatialAnalysis_FactsheetHochwGebaeude(requireData.readData(13),requireData.readData(10),
requireData.readData(16),requireData.readData(15), requireData.readData(17),requireData.readData(18))
print "Data correct initialized"
factsheetHochwGeb.gebaeudeExpAnalyseGZPBWV()
factsheetHochwGeb.gebaeudeExpAnalyseHSG()
factsheetHochwGeb.gebaeudeExpAnalyseTotal()
tkMessageBox.showinfo("Räumlicher-Analyse Erfolgreich","Die Berechnungen wurden erfolgreich abgeschlossen!")
except:
tkMessageBox.showerror ("FactsheetHochwasser_Gebaeude FEHLER","Bei der Berechnung ist ein Fehler aufgetreten!\n Für Details öffnen Sie das Error-File in der Programmumgebung")
class TextRedirector(object):
def __init__(self,widget, tag):
self.targetwidget = widget
self.targettag = tag
#@Override the sys.stdout & sys.stderr methods to write to the text widget instead of the python console
def write(self, str):
self.targetwidget.configure(state="normal")
self.targetwidget.insert("end", str, (self.targettag,))
self.targetwidget.configure(state="disabled")
root = ttk.Tkinter.Tk()
root.title ("SystemINFO-Menü")
runGUI = SystemInfoSupport (root)
root.mainloop ()
基本上我需要在后台执行函数def calculateFactsheets (self):
并接收打印或错误控制台消息以将它们写入小部件。
任何想法??
答案 0 :(得分:2)
Tkinter不是线程安全的。如果您尝试将数据插入文本小部件,您将获得不可预测的行为(或经常崩溃)。为了让一个单独的线程将数据发送到一个小部件,你需要将数据写入一个线程安全的队列,然后让你的主线程轮询该队列(使用tkinter的after方法)。
也无法从线程调用tkMessagebox(崩溃)
答案 1 :(得分:0)
我有类似的问题,我这样解决了:
class UpdateWindow(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.create_widgets()
self.grid()
self.focus_force()
def create_widgets(self):
self.queue = Queue.Queue()
#...
self.button= Button(self, text='Auto Update', command=lambda: self.spawnthread(function))
self.button.grid(row=1, column=0, sticky=N)
#...
def spawnthread(self, fcn):
self.button.config(state="disabled")
self.thread = ThreadedClient(self.queue, fcn)
self.thread.start()
self.periodiccall()
def periodiccall(self):
if self.thread.is_alive():
self.after(100, self.periodiccall)
self.progressbar.step(500)
else:
self.button.config(state="active")
self.progressbar.stop()
class ThreadedClient(threading.Thread):
def __init__(self, queue, fcn):
threading.Thread.__init__(self)
self.queue = queue
self.fcn = fcn
def run(self):
time.sleep(1)
self.queue.put(self.fcn())
在这个示例中,我有一个进度条,但是您应该在将代码调整到代码时遇到任何问题,主要是通过更改periodiccall
。