Tkinter:调度通过按钮点击执行的顺序线程

Tkinter: Scheduling Sequential Threads executed via button clicks

我有一个带有多个按钮的 Tkinter GUI,每个按钮使用线程调用不同的函数。我必须按顺序自动点击按钮。所以我使用了一个 START 按钮,它将点击第一个按钮,等待相应功能的执行完成,然后点击下一步按钮,依此类推。

我使用线程是因为我需要保持 Progressbar 运行ning 而任何函数都是 运行ning。

我还将按钮文本的颜色从红色(尚未 运行)更改为蓝色(运行)再更改为绿色(已完成执行)。我知道我需要在某处使用 join(),但它不起作用。

当前代码 运行 一次包含所有按钮 invoke() 方法,而不是按顺序。

import tkinter as tk
from tkinter import ttk

from threading import Thread
def sample_function():
    for i in range(1,10000) :
        print(i)

def run_function(name, func,btn_variable):
    # Disable all buttons
    btn_variable.configure(style = 'blue.TButton')
    processing_bar.start(interval=10)
    print(name, 'started')
    func()
    processing_bar.stop()
    print(name, 'stopped')
    btn_variable.configure(style = 'green.TButton')

def run_thread(name, func,btn_variable):
    Thread(target=run_function, args=(name, func,btn_variable)).start()


def prepare_clicked():
    run_thread('prepare', sample_function,prepare_btn)
    prepare_btn.configure(style = 'green.TButton')


def social_clicked():
    run_thread('social', sample_function,social_btn)
    social_btn.configure(style = 'green.TButton')


def anomaly_clicked():
    run_thread('anomaly', sample_function,anomaly_btn)
    anomaly_btn.configure(style = 'green.TButton')

def scoring_clicked():
    run_thread('scoring', sample_function,scoring_btn)
    scoring_btn.configure(style = 'green.TButton')

def dashboard_clicked():
    run_thread('dashboard', sample_function,dashboard_btn)
    dashboard_btn.configure(style = 'green.TButton')


def start_all():
    prepare_btn.invoke()
    anomaly_btn.invoke()
    social_btn.invoke()
    scoring_btn.invoke()
    dashboard_btn.invoke()



window = tk.Tk()
#window = tk.Toplevel()

topFrame = tk.Frame(window)
bottomFrame = tk.Frame(window)

# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
bottomFrame.pack(fill=tk.BOTH, expand=1)

# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(5):
    topFrame.rowconfigure(i, weight=1)

bottomFrame.rowconfigure(0, weight=1)
for i in range(3):
    bottomFrame.columnconfigure(i, weight=1)

ttk.Style().configure('blue.TButton', foreground='blue')
ttk.Style().configure('green.TButton', foreground='green')
ttk.Style().configure('red.TButton', foreground='red')


prepare_btn = ttk.Button(topFrame, command=prepare_clicked, text='Button 1',style = 'red.TButton')
anomaly_btn = ttk.Button(topFrame,command=anomaly_clicked, text='Button 2',style = 'red.TButton')
social_btn = ttk.Button(topFrame, command=social_clicked, text='Button 3',style = 'red.TButton')
scoring_btn = ttk.Button(topFrame, command=scoring_clicked, text='Button 4',style = 'red.TButton')
dashboard_btn = ttk.Button(topFrame, command=dashboard_clicked, text='Button 5',style = 'red.TButton')
commentary = ttk.Button(bottomFrame,text='START',width=10,command = start_all)
commentarylabel = ttk.Label(bottomFrame,text=' Commentary ',width=25)
processing_bar = ttk.Progressbar(bottomFrame, orient='horizontal', mode='indeterminate')

buttons = [prepare_btn, anomaly_btn, social_btn,scoring_btn,dashboard_btn]


prepare_btn.grid(row=0, column=0, columnspan=1, sticky='EWNS')
anomaly_btn.grid(row=1, column=0, columnspan=1, sticky='EWNS')
social_btn.grid(row=2, column=0, columnspan=1, sticky='EWNS')
scoring_btn.grid(row=3, column=0, columnspan=1, sticky='EWNS')
dashboard_btn.grid(row=4, column=0, columnspan=1, sticky='EWNS')
commentary.grid(row=0, column=0, columnspan=1, sticky='EWNS')
commentarylabel.grid(row=0,column = 1, columnspan=2, sticky='EWNS')
processing_bar.grid(row=0, column=3,columnspan=1, sticky='EWNS')

window.mainloop()

这里有一些东西可以演示如何做你想做的事。它之所以有效,是因为主线程以外的线程中的代码 运行ning 不会对其进行任何 tkinter 调用。为了让线程一个接一个地 运行,它使用一个 FIFO Queue 条目代表每个线程,并在最后一个线程完成时启动新线程 运行ning。

到"schedule"一系列步骤运行按照一定的顺序,就像在start_all()函数中所做的那样,所有需要做的就是put() 有关每个信息的信息,按照它们应在此 job_queue.

中执行的顺序

这一切都是通过重复使用通用 after() 方法定期 运行 一个 "polling" 函数(名为 poll)来完成的,除其他外,该函数检查查看另一个线程当前是否正在执行并做出相应的反应。

在代码中,每个线程所做的处理称为 "step" 或 "job"。

from queue import Queue, Empty
import random
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox as tkMessageBox
from threading import Thread
from time import sleep

random.seed(42)  # Generate repeatable sequence for testing.

ITERATIONS = 100
POLLING_RATE = 100  # millisecs.

# Global variables
cur_thread = None  # Current running thread.
cur_button = None
cur_name = None
job_queue = Queue()  # FIFO queue.

def sample_function():
    for i in range(1, ITERATIONS):
        print(i)
        sleep(0.01)  # Simulate slow I/O.

def start_thread(name, func, btn_variable):
    global cur_thread, cur_button

    if cur_thread is not None:
        tkMessageBox.showerror('Error', "You can't start a step when there"
                                        " are some already running.")
        return

    cur_thread = Thread(target=func)
    cur_button = btn_variable

    btn_variable.configure(style='blue.TButton')
    cur_thread.start()

def poll(window, processing_bar):
    global cur_thread, cur_button

    if cur_thread is not None:
        if cur_thread.is_alive():
            processing_bar.step()
        else:
            cur_thread.join() # Should be immediate.
            cur_thread = None
            processing_bar.stop()
            cur_button.configure(style='green.TButton')
            window.update()
    elif not job_queue.empty():  # More to do?
        try:
            job_info = job_queue.get_nowait()  # Non-blocking.
            start_thread(*job_info)
            processing_bar.start()
            window.update()
        except Empty:  # Just in case (shouldn't happen).
            pass

    window.after(POLLING_RATE, poll, window, processing_bar)

# Button commands.
def prepare_clicked():
    start_thread('prepare', sample_function, prepare_btn)

def social_clicked():
    start_thread('social', sample_function, social_btn)

def anomaly_clicked():
    start_thread('anomaly', sample_function, anomaly_btn)

def scoring_clicked():
    start_thread('scoring', sample_function, scoring_btn)

def dashboard_clicked():
    start_thread('dashboard', sample_function, dashboard_btn)

def start_all():
    global job_queue

    # Put info for each step in the job queue to be run.
    for job_info in (('prepare', sample_function, prepare_btn),
                     ('social', sample_function, social_btn),
                     ('anomaly', sample_function, anomaly_btn),
                     ('scoring', sample_function, scoring_btn),
                     ('dashboard', sample_function, dashboard_btn)):
        job_queue.put(job_info)
    # Start the polling.
    window.after(POLLING_RATE, poll, window, processing_bar)

####
window = tk.Tk()
#window = tk.Toplevel()

topFrame = tk.Frame(window)
bottomFrame = tk.Frame(window)

# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
bottomFrame.pack(fill=tk.BOTH, expand=1)

# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(5):
    topFrame.rowconfigure(i, weight=1)

bottomFrame.rowconfigure(0, weight=1)
for i in range(3):
    bottomFrame.columnconfigure(i, weight=1)

ttk.Style().configure('blue.TButton', foreground='blue')
ttk.Style().configure('green.TButton', foreground='green')
ttk.Style().configure('red.TButton', foreground='red')

prepare_btn     = ttk.Button(topFrame, command=prepare_clicked, text='Button 1', style='red.TButton')
anomaly_btn     = ttk.Button(topFrame, command=anomaly_clicked, text='Button 2', style='red.TButton')
social_btn      = ttk.Button(topFrame, command=social_clicked, text='Button 3', style='red.TButton')
scoring_btn     = ttk.Button(topFrame, command=scoring_clicked, text='Button 4', style='red.TButton')
dashboard_btn   = ttk.Button(topFrame, command=dashboard_clicked, text='Button 5', style='red.TButton')

commentary      = ttk.Button(bottomFrame, text='START', width=10, command=start_all)
commentarylabel = ttk.Label(bottomFrame, text=' Commentary ', width=25)
processing_bar  = ttk.Progressbar(bottomFrame, orient='horizontal', mode='indeterminate')

prepare_btn.grid    (row=0, column=0, columnspan=1, sticky='EWNS')
anomaly_btn.grid    (row=1, column=0, columnspan=1, sticky='EWNS')
social_btn.grid     (row=2, column=0, columnspan=1, sticky='EWNS')
scoring_btn.grid    (row=3, column=0, columnspan=1, sticky='EWNS')
dashboard_btn.grid  (row=4, column=0, columnspan=1, sticky='EWNS')

commentary.grid     (row=0, column=0, columnspan=1, sticky='EWNS')
commentarylabel.grid(row=0, column=1, columnspan=2, sticky='EWNS')
processing_bar.grid (row=0, column=3,columnspan=1, sticky='EWNS')

window.after(POLLING_RATE, poll, window, processing_bar)
window.mainloop()