Tkinter、Matplotlib与线程

0 投票
1 回答
928 浏览
提问于 2025-06-18 04:09

在主循环中,创建了一个2 x 2的tkinter网格,第一行每个单元格里都有一个标签。第二行则创建了两个Matplotlib图形,并且有一个子图。两个函数负责动态刷新这个网格,它们各自运行在一个线程中。第一行(两个标签)通过这两个函数刷新得很好。但是,第二行什么都没有……没有图表!

from tkinter import *
import threading
import time
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style

def read_api1():
    n = 0
    while 1:
        n = n + 1
        texte1.config(text="fig1 " + str(n))
        ax1.plot([1,2], [12,14])
        time.sleep(2)
        ax1.cla()

def read_api2():
    m = 0
    while 1:
        m = m + 1
        texte2.config(text="fig2 " + str(m))
        ax2.plot([.1,.2,.3], [2,4,3])
        time.sleep(1)

main = Tk()
style.use("ggplot")

texte1 = Label(main, text="fig1")
texte1.grid(row=0,column=0)
fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph = FigureCanvasTkAgg(fig1, master=main)
canvas = graph.get_tk_widget()
canvas.grid(row=1, column=0)

texte2 = Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph = FigureCanvasTkAgg(fig2, master=main)
canvas = graph.get_tk_widget()
canvas.grid(row=1, column=1)

t = threading.Thread(target=read_api1)
t.start()
t = threading.Thread(target=read_api2)
t.start()

main.mainloop()

任何帮助都非常感谢 :)


编辑:

更多细节 @furas:

  • read_api2是用来在特定时间从WEB API获取数据的。所以你推荐的after方法应该是有效的。

  • read_api1是用来从串口(GPIO UART)获取数据的。所以这个线程会一直等待数据可以读取。

在这种情况下,我不太明白怎么使用after方法。

换句话说,问题是:如何在tkinter环境中根据异步输入刷新Matplotlib图表?异步串口数据读取不能放在主循环里,所以我把它放在了线程中,但即使使用graph.draw()也不行。有什么建议吗?

相关问题:

  • 暂无相关问题
暂无标签

1 个回答

0

这里有两个问题:

  1. 需要用 graph.draw() 来更新或重新绘制图表。

  2. 通常图形用户界面(GUI)不喜欢在线程中运行,而 graph.draw() 似乎在线程中无法工作(至少在我的Linux系统上是这样)。

你可能需要使用 main.after(1000, main_api1) 来在 1000毫秒(即 1秒)后运行同一个函数,这样就不需要使用 thread,也不会阻塞 mainloop()


import tkinter as tk # PEP8: `import *` is not preferred
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
import random

# --- functions ---

def read_api1():
    global n

    n = n + 1
    texte1.config(text="fig1 " + str(n))

    ax1.cla()
    ax1.plot([1,2], [random.randint(0,10),random.randint(0,10)])
    graph1.draw()

    main.after(1000, read_api1)

def read_api2():
    global m

    m = m + 1
    texte2.config(text="fig2 " + str(m))

    ax2.cla()
    ax2.plot([.1,.2,.3], [random.randint(0,10),random.randint(0,10),random.randint(0,10)])
    graph2.draw()

    main.after(1000, read_api2)

# --- main ---

m = 0
n = 0

main = tk.Tk()
style.use("ggplot")

texte1 = tk.Label(main, text="fig1")
texte1.grid(row=0, column=0)

fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph1 = FigureCanvasTkAgg(fig1, master=main)
canvas1 = graph1.get_tk_widget()
canvas1.grid(row=1, column=0)
#graph1.draw()

texte2 = tk.Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph2 = FigureCanvasTkAgg(fig2, master=main)
canvas2 = graph2.get_tk_widget()
canvas2.grid(row=1, column=1)
#graph2.draw()

read_api1()
read_api2()

main.mainloop()

编辑:这是一个示例,展示了如何运行两个线程。每个线程以不同的速度生成数据,并使用两个 queue 将数据发送到主线程。主线程使用两个 after() 来检查这两个队列,并更新两个图表。

import tkinter as tk # PEP8: `import *` is not preferred
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import style
import random

import threading
import queue
import time

# --- functions ---

def WEB_API(queue):
    # it will run in thread

    print('WEB_API: start')

    while web_api_running:
        value = random.randint(1, 3)
        time.sleep(.1)
        print('WEB_API:', value)
        queue.put(value)


def GPIO_API(queue):
    # it will run in thread

    print('GPIO_API: start')

    while gpio_api_running:
        value = random.randint(1, 3)
        time.sleep(value)
        print('GPIO_API:', value)
        queue.put(value)


def read_api1():
    global n
    global data1

    if not queue1.empty():
        value = queue1.get()

        # remove first item and add new item at the the end
        data1 = data1[1:] + [value]

        n += 1
        texte1.config(text="fig1 " + str(n))

        ax1.cla()
        ax1.plot(range(10), data1)
        graph1.draw()

    main.after(100, read_api1)

def read_api2():
    global m
    global data2

    if not queue2.empty():
        value = queue2.get()

        # remove first item and add new item at the the end
        data2 = data2[1:] + [value]

        m = m + 1
        texte2.config(text="fig2 " + str(m))

        ax2.cla()
        ax2.plot([.1,.2,.3], data2)
        graph2.draw()

    main.after(100, read_api2)

# --- before GUI ---

# default data at start (to add new value at the end and remove first value)
data1 = [0,0,0,0,0,0,0,0,0,0]
data2 = [0,0,0]

m = 0
n = 0

# queues to communicate with threads
queue1 = queue.Queue()
queue2 = queue.Queue()

# global variables to control loops in thread    
web_api_running = True 
gpio_api_running = True

# start threads and send queues as arguments
thread1 = threading.Thread(target=WEB_API,  args=(queue1,))
thread1.start()

thread2 = threading.Thread(target=GPIO_API, args=(queue2,))
thread2.start()

# --- GUI ---

main = tk.Tk()
style.use("ggplot")

texte1 = tk.Label(main, text="fig1")
texte1.grid(row=0, column=0)

fig1 = Figure(figsize=(2, 2), dpi=112)
ax1 = fig1.add_subplot()
fig1.set_tight_layout(True)
graph1 = FigureCanvasTkAgg(fig1, master=main)
canvas1 = graph1.get_tk_widget()
canvas1.grid(row=1, column=0)

texte2 = tk.Label(main, text="fig2")
texte2.grid(row=0,column=1)
fig2 = Figure(figsize=(2, 2), dpi=112)
ax2 = fig2.add_subplot()
fig2.set_tight_layout(True)
graph2 = FigureCanvasTkAgg(fig2, master=main)
canvas2 = graph2.get_tk_widget()
canvas2.grid(row=1, column=1)

# draw plots first time
ax1.plot(range(10),  data1)
ax2.plot([.1,.2,.3], data2)

# run after which will update data and redraw plots
read_api1()
read_api2()

main.mainloop()

# --- after GUI ---

# stop loops in threads
web_api_running = False
gpio_api_running = False

# wait for end of theads
thread1.join()
thread2.join()

撰写回答