我有使用某些类函数的线程,这些函数会打印很多东西,我想在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()
答案 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”(感谢继承)和按钮单击事件。当然,这只是许多人的一种方法,但我希望你现在的问题更加明确。