2017-05-04 63 views
1

我有使用某些類的函數的線程,並且這些函數打印了很多東西,我想要在Text()小部件上顯示。主線程和帶線程的文本

所以我試圖使該窗口的類爲類變量和命令:主循環()似乎停止一切從持續....

是否有任何解決方案?

總體思路我想要做的:(控制檯轉換爲圖形用戶界面。)

from tkinter import * 


root = Tk() 
textbox = Text(root) 
textbox.pack() 

def redirector(inputStr): 
    textbox.insert(INSERT, inputStr) 

sys.stdout.write = redirector 
root.mainloop() 

整個代碼:

import threading 
from queue import Queue 
from Spider import Spider 
from domain import * 
from general import * 
from tkinter import * 



def mmm(answer1,answer2,master): # answer1,answer2 are user inputs from the first GUI that gets info, master is the root so i can close it 

    master.destroy() 
    PROJECT_NAME = answer1 
    HOMEPAGE = answer2 
    DOMAIN_NAME = get_domain_name(HOMEPAGE) 
    QUEUE_FILE = PROJECT_NAME + '/queue.txt' 
    CRAWLED_FILE = PROJECT_NAME + '/crawled.txt' 
    NUMBER_OF_THREADS = 8 

    queue = Queue() # thread queue 
    Spider(PROJECT_NAME, HOMEPAGE, DOMAIN_NAME) # a class where the prints happen and some other functions. 

    root = Tk() 
    textbox = Text(root) 
    textbox.pack() 

    def redirector(inputStr): 
     textbox.insert(INSERT, inputStr) 

    sys.stdout.write = redirector 
    root.mainloop() 
    # create threads (will die when exit) 
    def create_threads(): 
     for x in range(NUMBER_OF_THREADS): 
      t = threading.Thread(target=work) 
      t.daemon = True 
      t.start() 


    # do the next link in the queue 
    def work(): 
     while True: 
      url = queue.get() 
      Spider.crawl_page(threading.current_thread().name, url) 
      queue.task_done() 


    # each link is a new job 
    def create_jobs(): 
     for link in file_to_set(QUEUE_FILE): 
      queue.put(link) # put the link in the thread queue 
     queue.join() # block until all processed 
     crawl() 


    # if there are items in the queue, crawl them 
    def crawl(): 
     queued_links = file_to_set(QUEUE_FILE) 
     if len(queued_links) > 0: 
      print(str(len(queued_links)) + ' links in the queue') 
      create_jobs() 


    create_threads() 
    crawl() 

回答

1

一旦你開始mainloop(),你會得到一個事件驅動的應用程序,在循環中運行。任何放在root.mainloop()行之後的代碼只會在GUI終止後才能運行。預計你的GUI或多或少是自包含的。你用tkinter小部件填充它,這些小部件將綁定一些事件,每個事件都有其適當的回調函數。

但請注意,tkinter不是線程安全的。例如,您需要非常好地分隔引導代碼,確保它不調用任何GUI小部件。在this page中,您可以找到關於如何使用tkinter進行線程處理的Python2示例。

但也許你甚至不需要線程。例如,您可以安排一個函數,每012秒運行一個函數,可以讀取更新的日誌文件或從數據庫獲取更新的值,並相應地更新GUI。你可以在this page找到一些例子和解釋。

+0

咋,所以我得到了這個錯誤,它需要在主線程中...這是什麼意思? –

+0

我無法用您提供的代碼示例在這裏重現您的問題......它運行並在tkinter窗口中顯示一個空文本小部件。我相信它的行爲與你編碼的一樣。現在,如果你想要的是有一個文本小部件實時顯示無論在控制檯中顯示的任何內容,並且還對用戶輸入作出反應,它將是一個更復雜的任務。我不確定是否有可能,但可能有一些限制。 –

+0

是我的壞我沒有鏈接我的主代碼....這只是例子虐待編輯它1秒 –

1

@Victor Domingos的提及在你的案例中非常有用,但是你真正的問題 - 你自己的代碼!首先 - 看看你的應用程序的結構和理解,它很弱,沒有進攻(你甚至通過masterdestroy函數)。所以我建議你閱讀關於Python中的類和繼承(如果你還沒有),然後看看here

下一站 - 您的重定向器。你重新分配sys.stdout.write,但你永遠不會保留它 - 所以它是另一個弱點。好吧,讓我們說,現在你保存它,但如果我們保持面向對象的方法 - 我寧願this選項。

另外,是否真的有必要destroymaster?對於輸出,如果您爲了避免兩個mainloop而銷燬master,則可以使用Toplevel小部件。 You can even hide root while Toplevel is active。奇妙,不是嗎?

最後,回答你關於解決方案的問題。沒有直接的解決方案,但只有一個:閱讀並嘗試。你已經回答了爲什麼mainloop停止了一切,但你的問題是非常廣泛的。

我試圖重現完整的程序(2窗口的應用程序,第一個用戶輸入,2日 - 控制檯類和一些例如打印螺紋任務),這裏是代碼:

# imports: 
try: 
    import tkinter as tk 
except ImportError: 
    import Tkinter as tk 

import sys 
import string 
import random 
import threading 


# classes: 
class ReStdout: 
    # common stdout-redirector 
    def __init__(self, target_widget, start_redirection=True): 
     self.text_console = target_widget 
     if start_redirection: 
      self.start_redirection() 

    def __del__(self): 
     self.stop_redirection() 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     self.stop_redirection() 

    def __enter__(self): 
     pass 

    def flush(self): 
     pass 

    def write(self, stdout_line): 
     self.text_console.insert('1.0', stdout_line) 

    def start_redirection(self): 
     sys.stdout = self 

    @staticmethod 
    def stop_redirection(): 
     sys.stdout = sys.__stdout__ 


class App(tk.Tk): 
    # common tk app 
    def __init__(self): 
     tk.Tk.__init__(self) 
     self.resizable(width=True, height=False) 
     self.minsize(width=250, height=25) 
     self.some_entry = tk.Entry(self) 
     self.some_entry.insert(0, 'You can pass something to Spawner!') 
     self.some_entry.pack(expand=True, fill='x') 
     self.start_spawner_button = tk.Button(self, text='Start Spawner', command=self.spawn_spawner) 
     self.start_spawner_button.pack(expand=True, fill='x') 

    def spawn_spawner(self): 
     Spawner(self, self.some_entry.get()) 

    def close_app(self): 
     self.destroy() 


class Spawner(tk.Toplevel): 
    # common tk app - task spawner 
    def __init__(self, master, entry_string): 
     tk.Toplevel.__init__(self, master) 
     self.resizable(width=False, height=False) 
     self.preserved_count = threading.active_count() 
     self.master = master 
     self.master.withdraw() 

     self.spawn_task_button = tk.Button(self, text='Spawn Task', command=spawn_task) 
     self.spawn_task_button.pack(expand=True, fill='x') 

     self.quit_button = tk.Button(self, text='Quit', command=self.close_app) 
     self.quit_button.pack(expand=True, fill='x') 

     self.text = tk.Text(self, bg='black', fg='white') 
     self.text.pack(expand=True, fill='both') 
     self.stdout = ReStdout(self.text) 
     self.protocol('WM_DELETE_WINDOW', self.close_app) 

     # print what have been passed 
     print('Users Input: %s' % entry_string) 

     # let's spawn something right now 
     # after here just for example 
     # try to use it w/o after 
     self.after(500, multi_spawn) 

    def close_app(self): 
     if threading.active_count() == self.preserved_count: 
      self.stdout.stop_redirection() 
      self.master.deiconify() 
      self.destroy() 
     else: 
      # code to handle threads 
      print('\n**** Cant quit right now! ****\n') 


# non - class functions: 
def multi_spawn(count=1): 
    for _ in range(count): 
     spawn_task() 


def spawn_task(): 
    task_count = threading.active_count() 
    task = threading.Thread(target=lambda: common_task(comment='%d printing task' % task_count, 
                 iteration_count=random.randint(1, 10))) 
    task.start() 


def common_task(comment, iteration_count=1): 
    # example task wait + print 
    task_numb = comment.split(None, 1)[0] 
    print('\nTask spawned:\n%s\nIteration count: %d\n' % (comment, iteration_count)) 

    for _ in range(iteration_count): 
     threading.Event().wait(1) 
     print('Task: %s \t Iteration: %d \t Generated: %s' % (task_numb, _ + 1, generate_smth())) 

    print('\nTask %s completed!' % task_numb) 


def generate_smth(size=6, chars=string.ascii_uppercase + string.digits): 
    # generate random 
    return ''.join(random.choice(chars) for _ in range(size)) 

# entry-point: 
print('Just another example from SO') 
app = App() 
app.mainloop() 
print('Beep') 

正如你看到的 - 我從來沒有在mainloop中被困住(當我不需要它時),因爲我創建了事件的線程:__init__「Spawner」(感謝繼承)和一個按鈕點擊事件。當然,這只是衆多方法之一,但我希望現在你的問題對你更清楚。

+0

非常感謝評論:) TopLevel應用程序是什麼意思?什麼是類的使用? ,我真的不明白爲什麼..和我destory掌握,所以我關閉... –

+0

'Toplevel'只是另一個tkinter部件像按鈕或條目。 [一般來說tkinter有很好的材料](http://effbot.org/tkinterbook/)。你關於課程的問題也非常廣泛。但是這裏有一個[另一個鏈接](https://jeffknupp.com/blog/2014/06/18/improve-your-python-python-classes-and-object-oriented-programming/)。如果它看起來像你想要的,隨時重寫我的代碼! – CommonSense

+0

我理解你的代碼,但它真的很難使用它,因爲它不是真的接近我在做什麼......你可以告訴我要刪除什麼以及在哪裏放置我自己的代碼?因爲我喜歡在這個代碼中丟失 –