#python #tkinter
Вопрос:
Я пытаюсь реализовать отображение потока данных с помощью tkinter. У меня есть программа, которая вычисляет некоторые данные, и я хочу запрограммировать отображение данных в виде фрагментов в окне по мере их появления (он же поток данных).
Рассмотрим этот примитивный пример:
Я хочу отобразить msg1 и подождать 12 секунд (полностью искусственная задержка, представляющая ожидаемую задержку моих вычислений, которая является переменной), прежде чем отображать msg2. Что делает программа, так это ждет 12 секунд, а затем отображает два сообщения вместе.
from tkinter import *
import time
window = Tk()
msg1 = Label(window, text="Display Part 1")
msg1.pack()
time.sleep(12)
msg2 = Label(window, text="Display Part 2")
msg2.pack()
window.geometry("600x600")
window.mainloop()
Мы высоко ценим ваш вклад!
Комментарии:
1. вам нужно будет рассчитать его в отдельной таблице.
Ответ №1:
Графические интерфейсы не работают так, как input()
— Label
не создает виджет сразу, но он сообщает, какой виджет отображать, mainloop
создает окно и отображает его. Так что вы sleep
еще до того, как он создаст окно.
Другая проблема может заключаться в том, что mainloop
нужно все время запускать, чтобы получить событие ключа/мыши из системы, отправлять события виджетам и обновлять/перерисовывать виджет в окне. Если вы используете sleep
, то он остановится mainloop
, и окно замерзнет.
Вы можете использовать root.after(millisecond, function)
для выполнения функции после задержки.
import tkinter as tk # PEP8: `import *` is not preferred
import time
# --- functions ---
def add_label():
msg2 = tk.Label(window, text="Display Part 2")
msg2.pack()
# --- main ---
window = tk.Tk()
window.geometry("600x600")
msg1 = tk.Label(window, text="Display Part 1")
msg1.pack()
window.after(12000, add_label) # 12000ms = 12s
window.mainloop()
PEP 8 — Руководство по стилю для кода на Python
кстати:
Если вы хотите выполнить некоторые вычисления одновременно, вам придется делать это в отдельном потоке. Но вы не можете использовать виджеты в другом потоке, поэтому вам может потребоваться отправить текст из другого потока queue
, а основной поток нужно after
будет периодически проверять queue
и добавлять новый текст в графический интерфейс.
import tkinter as tk # PEP8: `import *` is not preferred
import time
import threading
import queue
import datetime
import random
# --- functions ---
def calculations(q):
while running:
# some calculations
time.sleep(random.randint(1, 10))
# create message
text = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# send message using queue
q.put(text)
def check_queue():
# check if there is new message in queue
if not q.empty():
# get new message
text = q.get()
# append in `Text`
textbox.insert('end', text 'n')
# repeate after 0.1s
window.after(100, check_queue) # 100ms = 0.1s
# --- main ---
running = True
q = queue.Queue()
t = threading.Thread(target=calculations, args=(q,))
t.start()
# - GUI -
window = tk.Tk()
window.geometry("600x600")
textbox = tk.Text(window, bg='gray')
textbox.pack()
button = tk.Button(window, text='Exit', command=window.destroy)
button.pack()
window.after(100, check_queue) # 100ms = 0.1s
window.mainloop()
# - end -
running = False # stop loop in thread
t.join() # wait for the end of thread
Комментарии:
1. «Вы можете использовать root.after(миллисекунда, функция) для выполнения функции после задержки». Спасибо за ответ. Однако задержка является искусственной, я просто использовал задержку в своем примере, чтобы заменить ожидаемую задержку моего вычисления, которая будет переменной. Поэтому я не хочу устанавливать там постоянную задержку, я хочу отображать данные, как только они станут доступны.
2. вторая версия использует
calculations
для имитации задержки (я мог бы использоватьrandom
для имитации случайной задержки), и она используетqueue
иafter
для получения сообщения с любой задержкой. Используя0.1s
вafter
нем, вы можете получить сообщение как можно скорее.3. Я добавил
random
,calculations
так что теперь у вас есть пример со случайными задержками, и он получает сообщение как можно скорее.
Ответ №2:
Лучше запускать трудоемкую задачу в потоке, чтобы она не мешала tkinter mainloop
обрабатывать ожидающие события и обновления.
И трудоемкая задача может уведомить основную задачу о завершении задачи с помощью виртуального события tkinter.
Ниже приведен пример:
from tkinter import *
import time
import threading
def calculation_task():
print("started")
# simulate calculation
time.sleep(12)
print("completed")
# task completed, notify main task via virtual event
window.event_generate("<<Complete>>", when="tail")
def on_complete(event):
msg2 = Label(window, text="Display Part 2")
msg2.pack()
window = Tk()
window.geometry("600x600")
msg1 = Label(window, text="Display Part 1")
msg1.pack()
# bind virtal event
window.bind("<<Complete>>", on_complete)
# start calculation task in other thread
threading.Thread(target=calculation_task).start()
window.mainloop()