如何在 Tkinter 中为按钮创建新的 Threads/Kill 个活动线程?
How Do I Create New Threads/Kill Alive Threads for a Button in Tkinter?
我目前正在为我的 DJI Tello 开发 Tkinter GUI,我正在努力做到这一点,以便当我命令无人机 takeoff/land 时,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)
好的,今天早上我实际上根据这篇文章找到了解决方案:
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)
我目前正在为我的 DJI Tello 开发 Tkinter GUI,我正在努力做到这一点,以便当我命令无人机 takeoff/land 时,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)
好的,今天早上我实际上根据这篇文章找到了解决方案:
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)