Отображение потока данных с помощью Python tkinter

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