我想让应用程序从 FTP 服务器读取日志文件 - 当用户单击按钮时,它会连接到 FTP,更改目录并每 1 秒读取数据。如果数据发生更改(出现新日志),则会将其添加到文本小部件。 它工作得很酷,直到用户多次按下同一按钮 - 然后出现错误“运行时错误:线程只能启动一次”。因此,在下面这段代码的不同版本中,我为每个 USB 按钮添加了附加按钮“显示日志”。此外,每个 USB 按钮都有自己的文本小部件。 因此,当用户首先按下 USB 按钮时,他可以通过“显示日志”按钮在文本小部件之间切换 - 但它变得混乱,主要是因为我在告诉我的 func readdata() 哪个文本小部件忘记(隐藏)以及哪个渲染时遇到问题(再次.pack)-例如,我想随后 pack_forget Text widget1 和 .pack Text widget2,但我不知道如何让应用程序记住活动上下文。此外,滚动在“切换到”文本小部件中不起作用(仅在“活动”文本中起作用),有时无法读取数据文件和许多其他问题。 这是更优雅的方式吗?我的意思是,只需单击“USB0”和“USB1”按钮即可在日志之间切换,而不需要为每个 USB 按钮使用额外的“显示日志”按钮?
import tkinter as tk
import pysftp
import time
import threading
root = tk.Tk()
root.geometry("1200x700")
frame = tk.Frame(root)
frame.place(x=15, y=10)
scroll = tk.Scrollbar(frame)
t = tk.Text(frame, width=183, height=45, yscrollcommand=scroll.set)
scroll.config(command=t.yview)
scroll.pack(side='right', fill='y')
def clears():
t.delete(1.0, tk.END)
def readdata(text,dir,logfile):
clears()
t.insert(tk.END, text+"LOADING LOGS\n\n")
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
sftp = pysftp.Connection('ip', username='user', password='pass', cnopts=cnopts)
sftp.chdir('uart-logs')
sftp.chdir(dir)
active_log = logfile
with sftp.open(active_log, mode="r") as file:
old_text = file.read().decode('ASCII')
t.insert(tk.END, old_text)
t.see('end')
while True:
with sftp.open(active_log, mode="r") as file:
new_text = file.read().decode('ASCII')
if new_text != old_text:
t.insert(tk.END, new_text[len(old_text):])
root.update()
t.see('end')
old_text = new_text
time.sleep(1)
buttons = tk.Frame()
b0 = tk.Button(buttons, text = "ttyUSB0", command =threading.Thread(target=lambda:readdata('USB0: ','USB0',"USB0-active.log")).start)
b1 = tk.Button(buttons, text = "ttyUSB1", command =threading.Thread(target=lambda:readdata('USB1: ','USB1',"USB1-active.log")).start)
clear = tk.Button(buttons, text="Clear", command=lambda:clears())
chkbox = tk.Checkbutton(buttons, text="Auto-scroll")
buttons.pack()
b0.pack(in_=buttons, side=tk.LEFT, padx=10)
b1.pack(in_=buttons, side=tk.LEFT, padx=10)
clear.pack(in_=buttons, side=tk.LEFT, padx=10)
chkbox.pack(in_=buttons, side=tk.RIGHT, padx=40)
frame.pack(fill='both',expand=True)
t.pack(fill='both',expand=True)
tk.mainloop()
它工作得很酷,直到用户多次按下同一按钮 - 然后出现错误“运行时错误:线程只能启动一次”。
只需更改即可解决问题
command=threading.Thread(target=lambda:readdata('USB0: ','USB0',"USB0-active.log")).start
(将单个线程的
start
方法绑定到按钮)
到
command=lambda: threading.Thread(target=lambda:readdata('USB0: ','USB0',"USB0-active.log")).start()
这将创建一个新线程并在每次单击它时启动它, 但您还需要能够阻止前一个线程同时尝试更新该字段。
你可以用类似的东西来做到这一点
current_read_thread = None
current_stop_event = None
def readdata(text, dir, logfile, stop_event):
...
while not stop_event.is_set(): # (not `while True`)
...
def start_thread(text, dir, logfile):
global current_read_thread
global current_stop_event
if current_read_thread:
current_stop_event.set()
# Wait for the other thread to stop
current_read_thread.join()
current_read_thread = None
current_stop_event = threading.Event()
current_read_thread = threading.Thread(target=readdata, args=(text, dir, logfile, current_stop_event))
current_read_thread.start()
# ...
b0 = tk.Button(buttons, text="ttyUSB0", command=lambda: start_thread("USB0: ", "USB0", "USB0-active.log"))