带螺纹的Mainloop和Text

时间:2017-05-04 11:14:35

标签: python python-3.x user-interface tkinter tk

我有使用某些类函数的线程,这些函数会打印很多东西,我想在Text()小部件上显示。

所以我试着让类中的窗口作为类变量而命令:mainloop()似乎会阻止所有内容继续....

有没有解决方案?

我想做的一般想法:(将控制台转换为GUI ..)

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()

2 个答案:

答案 0 :(得分:1)

一旦启动mainloop(),您就会获得一个循环运行的事件驱动应用程序。在root.mainloop()之后放置的任何代码只有在GUI终止后才会运行。预计您的GUI或多或少是自包含的。您使用tkinter小部件填充它,这些小部件将绑定一些事件,每个事件都有适当的回调函数。

但请注意,tkinter不是线程安全的。例如,您需要非常好地分离主题代码,确保它不会调用任何GUI小部件。在this page中,您可以找到有关如何使用tkinter进行线程化的Python2示例。

但也许你甚至不需要线程。例如,您可以使用after()计划每X秒运行一次的函数,它可以读取更新的日志文件或从数据库获取更新的值,并相应地更新GUI。您可以在this page中找到一些示例和说明。

答案 1 :(得分:1)

@Victor Domingos的提及在你的情况下非常有用,但你真正的问题 - 你自己的代码!首先 - 看看你的应用程序的结构并理解它是弱的,没有冒犯的(你甚至将master传递给函数destroy它。所以我建议你阅读Python中的类和继承(如果你还没有),然后看看here

下一站 - 你的重定向器。你重新分配sys.stdout.write,但你永远不会保留它 - 所以这是另一个弱点。好吧,让我们说现在你保留它,但如果我们保持面向对象的方法 - 我更喜欢this选项。

此外,是否真的有必要destroy master?对于输出,如果您销毁Toplevel只是为了避免两个master,则可以使用mainloop窗口小部件。 You can even hide root while Toplevel is active。奇妙,不是吗?

最后,回答有关解决方案的问题。没有直接的解决方案,但只有一个:阅读并尝试。您已经回答为什么 mainloop停止了所有内容,但您的问题非常广泛。

我试图重现你的完整程序(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”(感谢继承)和按钮单击事件。当然,这只是许多人的一种方法,但我希望你现在的问题更加明确。