【问题标题】:How Do I Create New Threads/Kill Alive Threads for a Button in Tkinter?如何在 Tkinter 中为按钮创建新线程/杀死活动线程?
【发布时间】:2022-01-03 03:05:36
【问题描述】:

我目前正在为我的 DJI Tello 开发 Tkinter GUI,我正在努力做到这一点,以便当我命令无人机起飞/降落时,GUI 上的流式视频不会冻结。我对多线程不太熟悉,但我查了一下这个问题,似乎我不是唯一遇到这个问题的人。所以我使用了我发现的关于线程和启动线程的内容,并最终得到了这一行(或多或少):

forward_button = Button(root, text="Takeoff/Land", font=("Verdana", 18), bg="#95dff3", command=threading.Thread(target=lambda: takeoff_land(flydo)).start)

现在,当我按下按钮时,无人机起飞,视频不再冻结。但是,当我再次单击它时,代码会抛出错误:

RuntimeError: threads can only be started once

但我希望我的按钮能够让无人机在着陆时起飞,然后在飞行时着陆。有什么办法可以做到吗?

这是我目前所拥有的(在 takeoff_land() 函数中,我设置了一些测试代码来代替实际的命令。基本上,我希望它能够开始打印 0、1、2...之后,即使它已经在打印了。)大部分只是 GUI 的东西,但我不想省略任何会破坏代码的东西。

import cv2
import threading
from djitellopy import tello
from tkinter import *
from PIL import Image, ImageTk

import time

def takeoff_land(flydo):
    '''Flydo takes off if not flying, lands if flying.'''
    global flying
    if flying:
        for i in range(10):
            print(i)
            time.sleep(1)
        # flydo.land()
        flying = False
    else:
        for i in range(10):
            print(i)
            time.sleep(1)
        # flydo.takeoff()
        flying = True
    

def run_app(HEIGHT=800, WIDTH=800):
    root = Tk()
    
    flydo = tello.Tello()
    flydo.connect()
    flydo.streamon()

    global flying
    flying = False # To toggle between takeoff and landing for button

    canvas = Canvas(root, height=HEIGHT, width=WIDTH)
    
    # For background image
    bg_dir = "C:\\Users\\charl\\Desktop\\flydo\\Tacit.jpg"
    img = Image.open(bg_dir).resize((WIDTH, HEIGHT))
    bg_label = Label(root)
    bg_label.img = ImageTk.PhotoImage(img)
    bg_label["image"] = bg_label.img
    bg_label.place(x=0, y=0, relwidth=1, relheight=1)

    # Display current battery
    battery = Label(text=f"Battery: {int(flydo.get_battery())}%", font=("Verdana", 18), bg="#95dff3")
    bat_width = 200
    bat_height = 50
    battery.config(width=bat_width, height=bat_height)
    battery.place(x=(WIDTH - bat_width - 0.1*HEIGHT + bat_height), rely=0.9, relwidth=bat_width/WIDTH, relheight=bat_height/HEIGHT)

    # Takeoff/Land button
    forward_button = Button(root, text="Takeoff/Land", font=("Verdana", 18), bg="#95dff3", command=threading.Thread(target=lambda: takeoff_land(flydo)).start)
    if threading.Thread(target=lambda: takeoff_land(flydo)).is_alive():
        threading.Thread(target=lambda: takeoff_land(flydo)).join() # This doesn't kill the thread the way I want it to...
    fb_width = 200
    fb_height = 100
    forward_button.config(width=fb_width, height=fb_height)
    forward_button.place(x=(WIDTH/2 - fb_width/2), rely=0.61, relwidth=fb_width/WIDTH, relheight=fb_height/HEIGHT)

    cap_label = Label(root)
    cap_label.pack()
    
    def video_stream():
        h = 480
        w = 720
        frame = flydo.get_frame_read().frame
        frame = cv2.resize(frame, (w, h))
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        cap_label.place(x=WIDTH/2 - w/2, y=0)
        cap_label.imgtk = imgtk
        cap_label.configure(image=imgtk)
        cap_label.after(5, video_stream)

    video_stream()
    canvas.pack()
    root.mainloop()


if __name__ == "__main__":
    HEIGHT = 800
    WIDTH = 800

    run_app(HEIGHT, WIDTH)

【问题讨论】:

  • 啊,好吧,我还是 Stackoverflow 的新手,所以谢谢!

标签: python multithreading tkinter tello-drone


【解决方案1】:

好吧,今天早上我实际上设法根据这篇文章找到了解决方案:

https://bhaveshsingh0124.medium.com/multi-threading-on-python-tkinter-button-f0d9f759ad3e

基本上,我要做的是将 Tello 命令线程化到我使用按钮调用的函数中,而不是该函数本身。由于无人机只能着陆或起飞,因此每次调用这两个命令之一时,它都可以创建一个新线程。这是固定的代码:

import cv2
import threading
from djitellopy import tello
from tkinter import *
from PIL import Image, ImageTk # You have to import this last or else Image.open throws an error
import time

def dummy_tello_fn():
    for i in range(3):
        print(i)
        time.sleep(1)


def takeoff_land(flydo):
    '''Flydo takes off if not flying, lands if flying.'''
    global flying
    if flying:
        # threading.Thread(target=lambda: dummy_tello_fn()).start()
        threading.Thread(target=lambda: flydo.land()).start()
        flying = False
    else:
        # threading.Thread(target=lambda: dummy_tello_fn()).start()
        threading.Thread(target=lambda: flydo.takeoff()).start()
        flying = True
    

def run_app(HEIGHT=800, WIDTH=800):
    root = Tk()
    
    flydo = tello.Tello()
    flydo.connect()
    flydo.streamon()

    global flying
    flying = False # To toggle between takeoff and landing for button

    canvas = Canvas(root, height=HEIGHT, width=WIDTH)
    
    # For background image
    bg_dir = "C:\\Users\\charl\\Desktop\\flydo\\Tacit.jpg"
    img = Image.open(bg_dir).resize((WIDTH, HEIGHT))
    bg_label = Label(root)
    bg_label.img = ImageTk.PhotoImage(img)
    bg_label["image"] = bg_label.img
    bg_label.place(x=0, y=0, relwidth=1, relheight=1)

    # Display current battery
    battery = Label(text=f"Battery: {int(flydo.get_battery())}%", font=("Verdana", 18), bg="#95dff3")
    bat_width = 200
    bat_height = 50
    battery.config(width=bat_width, height=bat_height)
    battery.place(x=(WIDTH - bat_width - 0.1*HEIGHT + bat_height), rely=0.9, relwidth=bat_width/WIDTH, relheight=bat_height/HEIGHT)

    # Takeoff/Land button
    forward_button = Button(root, text="Takeoff/Land", font=("Verdana", 18), bg="#95dff3", command=lambda: takeoff_land(flydo))
    fb_width = 200
    fb_height = 100
    forward_button.config(width=fb_width, height=fb_height)
    forward_button.place(x=(WIDTH/2 - fb_width/2), rely=0.61, relwidth=fb_width/WIDTH, relheight=fb_height/HEIGHT)

    cap_label = Label(root)
    cap_label.pack()
    
    def video_stream():
        h = 480
        w = 720
        frame = flydo.get_frame_read().frame
        frame = cv2.resize(frame, (w, h))
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        cap_label.place(x=WIDTH/2 - w/2, y=0)
        cap_label.imgtk = imgtk
        cap_label.configure(image=imgtk)
        cap_label.after(5, video_stream)

    video_stream()
    canvas.pack()
    root.mainloop()


if __name__ == "__main__":
    HEIGHT = 800
    WIDTH = 800

    run_app(HEIGHT, WIDTH)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多