2015-07-06 98 views
1

我正在用python編寫一個程序,它將處理大量從某些excel文件中讀取的數據。我使用Tkinter爲這個程序構建了一個GUI。我知道Tkinter是單線程的,因此打開文件並製作一些進程,我使用了一個線程來阻止GUI。其中一個線程任務是填充一個列表(在我的代碼中稱爲columnList),並將其元素用作選項菜單中的選項,因此在線程完成之前,選項菜單是空的,因此我使用join()讓main線程等待工作線程。而且,問題出現了,只要工作線程正在執行,GUI將不會響應(大約7秒),但在此之後它將正常工作。阻塞Tkinter接口,直到線程完成其任務

我想使用一些指示正在加載的圖形指示符,同時阻止GUI窗口,以便用戶無法點擊它。線程停止後,指示器應該消失,並且應該再次啓用GUI。我搜索了這樣一個概念,但我沒有在網上找到這樣的事情,這裏的這個問題,Python Tkinter: loading screen與我的情況非常相似,但它沒有答案。

這是我的代碼的一部分,我需要應用的概念:

__author__ = 'Dania' 
import threading 


from Tkinter import * 
from tkFileDialog import askopenfilename 
import numpy as np 
import xlrd 
global x 
global v 

x = np.ones(5) 
v= np.ones(5) 
global columnList 
columnList="" 


def open_file (file_name): 


    try: 
     workbook = xlrd.open_workbook(file_name) 
     sheet=workbook.sheet_by_index(0) 
     global columns 
     columns = [] #this is a list, in each index we will store a numpy array of a column values. 
     for i in range (0,sheet.ncols-1): 
      columns.append(np.array (sheet.col_values(i,1))) # make a list, each index has a numpy array that represnts a column. 1 means start from row 1 (leave the label) 
      if (i!=0): 
       columns[i]= columns[i].astype(np.float) 
     #Preprocessing columns[0]: 
     m= columns [0] 
     for i in range (m.shape[0]): 
      m[i]= m[i]*2 +1 

     m=m.astype(np.int) 
     columns[0]=m 

     global columnList 
     columnList= np.array(sheet.row_values(0)) #I was using sheet.row(0), but this is better since it doesn't return a 'u' 
     columnList=columnList.astype(np.str) 


     # removing nans: 
     index=input("enter the column index to interpolate: ") #this should be user input 

     n= columns [index] 
     for i in range (n.shape[0]-1, -1, -1): 
      if (np.isnan(n[i])): 
       n=np.delete(n,i) 
       columns[0]=np.delete(columns[0],i) 
       columns [index]= np.delete(columns[index],i) 


    except IOError: 
     print ("The specified file was not found") 

    global x 
    np.resize(x, m.shape[0]) 
    x=columns[0] 

    global v 
    np.resize(v,n.shape[0]) 
    v=columns[index] 

    #return columns [0], columns [index] 




class Interface: 
    def __init__(self, master): 

     self.title= Label(master,text="Kriging Missing data Imputation", fg="blue", font=("Helvetica", 18)) 
     self.select_file= Label (master, text="Select the file that contains the data (must be an excel file): ", font=("Helvetica", 12)) 


     self.title.grid (row=1, column=5, columnspan= 4, pady= (20,0)) 
     self.select_file.grid (row=3, column=1, sticky=W, pady=(20,0), padx=(5,2)) 
     self.browse_button= Button (master, text="Browse", command=self.browser, font=("Helvetica", 12), width=12) 
     self.browse_button.grid (row=3, column=3, pady=(20,0)) 


     self.varLoc= StringVar(master) 
     self.varLoc.set("status") 

     self.varColumn= StringVar(master) 
     self.varColumn.set("") 

     self.locationColumn= Label(master,text="Select a column as a location indicator", font=("Helvetica", 12)) 
     self.columnLabel= Label(master,text="Select a column to process", font=("Helvetica", 12)) 

     global locationOption 
     global columnOption 
     columnOption= OptionMenu (master, self.varColumn,"",*columnList) 
     locationOption= OptionMenu (master, self.varLoc,"",*columnList) 

     self.locationColumn.grid (row=5, column=1, pady=(20,0), sticky=W, padx=(5,0)) 
     locationOption.grid (row=5, column=3, pady=(20,0)) 

     self.columnLabel.grid (row=7, column=1, pady=(20,0), sticky=W, padx=(5,0)) 
     columnOption.grid(row=7, column= 3, pady= (20,0)) 


     self.missing_label= Label(master, text="Select missing data indicator: ", font=("Helvetica", 12)) 
     self.var = StringVar (master) 
     self.var.set("nan") 
     self.menu= OptionMenu (master, self.var,"nan", "?", "*") 

     self.missing_label.grid (row=9, column=1, padx=(5,2), pady= (20,0), sticky=W) 
     self.menu.grid(row=9, column=3, pady= (20,0)) 

     self.extrapolate= Label (master, text="Select a range for extrapolation (max=800): ", font=("Helvetica", 12)) 
     self.max_extra= Entry (master) 

     self.extrapolate.grid (row=11, column=1, padx=(5,2), pady= (20,0), sticky=W) 
     self.max_extra.grid (row=11, column=3, pady=(20,0)) 

     self.a_label= Label (master, text="enter the value of a (range): ", font=("Helvetica", 12)) 
     self.a_value= Entry (master) 

     self.a_label.grid (row=13, column=1, padx=(5,2), pady=(20,0), sticky=W) 
     self.a_value.grid (row=13, column=3, pady=(20,0)) 


     self.start_button= Button (master, text="Start", font=("Helvetica", 12), width=12) 
     self.pause_button= Button (master, text= "Pause", font=("Helvetica", 12),width=12) 
     self.stop_button= Button (master, text="stop", font=("Helvetica", 12),width=12) 

     self.start_button.grid (row=15, column=1, pady=(30,0)) 
     self.pause_button.grid (row=15, column=2, pady=(30,0)) 
     self.stop_button.grid (row=15, column=3, pady=(30,0)) 



    def browser (self): 
      filename = askopenfilename() 
      #indicator should start here. 
      t=threading.Thread (target=open_file, args=(filename,)) 

      t.start() 
      t.join() #I use join because if I didn't,next lines will execute before open_file is completed, this will make columnList empty and the code will not execute. 
      #indicator should end here. 
      opt=columnOption.children ['menu'] 
      optLoc= locationOption.children ['menu'] 
      optLoc.entryconfig (0,label= columnList [0], command=self.justamethod) 
      opt.entryconfig (0, label= columnList [0], command=self.justamethod) 
      for i in range(1,len (columnList)): 
       opt.add_command (label=columnList[i], command=self.justamethod) 
       optLoc.add_command (label=columnList[i], command=self.justamethod) 

    def justamethod (self): 
     print("method is called") 
     print(self.varLoc.get()) 




window= Tk() #main window. 
starter= Interface (window) 


window.mainloop() #keep the window open until the user decides to close it. 

我已經嘗試添加這樣的方法瀏覽器裏面的一些進度條,

def browser (self): 
      filename = askopenfilename() 
      progressbar = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate') 
      progressbar.pack(side="bottom") 
      progressbar.start() 
      t=threading.Thread (target=open_file, args=(filename,)) 
      t.start() 
      t.join() #I use join because if I didn't,next lines will execute before open_file is completed, this will make columnList empty and the code will not execute. 
      progressbar.stop() 
      opt=columnOption.children ['menu'] 
      opt.entryconfig (0, label= columnList [0], command=self.justamethod) 

      for i in range(1,len (columnList)): 
       opt.add_command (label=columnList[i], command=self.justamethod) 
       optLoc.add_command (label=columnList[i], command=self.justamethod) 

def justamethod (self): 
     print("method is called") 



window= Tk() #main window. 
starter= Interface (window) 


window.mainloop() #keep the window open until the user decides to close it. 

但,上面的代碼甚至沒有顯示進度條,這不是我真正需要的。

編輯:代碼是用固定的空格編輯的。工作代碼示例是第一個代碼片段中的代碼示例。

任何人都可以請告訴我如何做到這一點?

任何幫助表示讚賞。

謝謝。

+1

請問修復縮進問題,因爲Python中的縮進數很重要? – nbro

+0

@ Christopher Wallace是的,實際上我並沒有把所有的代碼放在一起,因此縮進的組織不好,我會編輯這個問題。謝謝 – Dania

+0

@克里斯托弗華萊士我編輯了問題 – Dania

回答

1

使用後臺線程讀取文件的優點之一是當前線程不會阻塞並可以繼續運行。通過在t.start之後直接調用t.join(),可以阻止GUI,因爲如果您剛剛在當前線程中執行了讀取操作,則不會有任何區別。

取而代之,您只需將光標更改爲wait光標,然後再進行操作?我已經簡化了你的代碼,但這樣的事情:

from tkinter import * 
import time 

class Interface: 
    def __init__(self, master): 
     self.master = master 
     self.browse_button= Button (master, text="Browse", command=self.browser) 
     self.browse_button.pack() 

    def browser (self): 
     self.master.config(cursor="wait") 
     self.master.update() 
     self.read_file("filename") 
     self.master.config(cursor="") 

    def read_file (self, filename): 
     time.sleep(5) # actually do the read file operation here 

window = Tk() 
starter = Interface(window) 
window.mainloop() 

編輯:好吧,我想我更好地瞭解是什麼問題。我的操作系統沒有說沒有響應,所以不能真正測試這個問題,但試着用ThreadProgressbar

from tkinter import * 
from tkinter.ttk import * 
import time 
import threading 

class Interface: 
    def __init__(self, master): 
     self.master = master 
     self.browse_button= Button (master, text="Browse", command=self.browser) 
     self.browse_button.pack() 
     self.progressbar = Progressbar(mode="determinate", maximum=75) 

    def browser (self): 
     t = threading.Thread(target=self.read_file, args=("filename",)) 
     self.progressbar.pack() 
     self.browse_button.config(state="disabled") 
     self.master.config(cursor="wait") 
     self.master.update() 

     t.start() 
     while t.is_alive(): 
      self.progressbar.step(1) 
      self.master.update_idletasks() # or try self.master.update() 
      t.join(0.1) 

     self.progressbar.config(value="0") 
     self.progressbar.pack_forget() 
     self.browse_button.config(state="enabled") 
     self.master.config(cursor="") 

    def read_file (self, filename): 
     time.sleep(7) # actually do the read here 

window = Tk() 
starter = Interface(window) 
window.mainloop() 

注:我沒有做很多的GUI編碼,這可能不是最好的解決方案,只是通過並試圖幫助! :)

編輯2:多想了一會兒。由於您不確定讀取的時間需要多長時間,因此您可以使用此方法在進度條的末端之間來回跳動指示器。

from tkinter import * 
from tkinter.ttk import * 
import time 
import threading 

class Interface: 
    def __init__(self, master): 
     self.master = master 
     self.browse_button= Button (master, text="Browse", command=self.browser) 
     self.browse_button.pack() 
     # Create an indeterminate progressbar here but don't pack it. 
     # Change the maximum to change speed. Smaller == faster. 
     self.progressbar = Progressbar(mode="indeterminate", maximum=20) 

    def browser (self): 
     # set up thread to do work in 
     self.thread = threading.Thread(target=self.read_file, args=("filename",)) 
     # disable the button 
     self.browse_button.config(state="disabled") 
     # show the progress bar 
     self.progressbar.pack() 
     # change the cursor 
     self.master.config(cursor="wait") 
     # force Tk to update 
     self.master.update() 

     # start the thread and progress bar 
     self.thread.start() 
     self.progressbar.start() 
     # check in 50 milliseconds if the thread has finished 
     self.master.after(50, self.check_completed) 

    def check_completed(self): 
     if self.thread.is_alive(): 
      # if the thread is still alive check again in 50 milliseconds 
      self.master.after(50, self.check_completed) 
     else: 
      # if thread has finished stop and reset everything 
      self.progressbar.stop() 
      self.progressbar.pack_forget() 
      self.browse_button.config(state="enabled") 
      self.master.config(cursor="") 
      self.master.update() 

      # Call method to do rest of work, like displaying the info. 
      self.display_file() 

    def read_file (self, filename): 
     time.sleep(7) # actually do the read here 

    def display_file(self): 
     pass # actually display the info here 

window = Tk() 
starter = Interface(window) 
window.mainloop() 
+0

謝謝JSE,但我已經故意使用連接來阻塞GUI,這是因爲工作線程填充了空變量columnList,所以如果我不阻塞主線程,直到工作線程完成,此行optLoc.entryconfig (0,label = columnList [0],command = self.justamethod)將在工作線程完成之前執行,並且由於仍未填充columnList,我將收到一個錯誤。 – Dania

+0

@JSEI嘗試了你的方法,但我得到這個錯誤,AttributeError:接口實例沒有屬性'主'。你能告訴我如何解決這個問題嗎? – Dania

+1

@Dania我明白你爲什麼使用'join()',但是因爲你並沒有在開始線程和調用join()之間做任何事情,所以不需要創建一個單獨的線程,你可以在當前線程中執行讀取操作。 您是否確定在'Interface'的'__init__'方法中包含'self.master = master'這一行?我發佈的代碼適用於Python 3.4.2中的IDLE。 – JSE

相關問題