Python 在tkinter中使用停止按钮中断while循环
我编写了一个python GUI,它应该通过单击一个名为的按钮“开始”来创建和更新一个.csv文件,并且应该通过单击另一个名为的按钮“停止”来停止更新.csv的while循环。但每当我运行GUI并单击start时,它就会冻结。虽然我看到.csv文件在不断更新,但我无法阻止.csv更新新行。我只是简单地使用Python2.7运行代码&ubuntu终端编写python filename.py。 谁能检查一下我的代码有什么问题吗Python 在tkinter中使用停止按钮中断while循环,python,tkinter,Python,Tkinter,我编写了一个python GUI,它应该通过单击一个名为的按钮“开始”来创建和更新一个.csv文件,并且应该通过单击另一个名为的按钮“停止”来停止更新.csv的while循环。但每当我运行GUI并单击start时,它就会冻结。虽然我看到.csv文件在不断更新,但我无法阻止.csv更新新行。我只是简单地使用Python2.7运行代码&ubuntu终端编写python filename.py。 谁能检查一下我的代码有什么问题吗 from Tkinter import * import datetim
from Tkinter import *
import datetime
import sys
import time
import csv
import math
A1 = 0
def csv_write(label):
global A1
A1 = 0
A = str(datetime.datetime.now()) + ".csv"
start = time.time()
elapsed = 0
with open(A, 'wt') as filename:
csv_writer = csv.writer(filename, delimiter=',')
csv_writer.writerow(('IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z'))
while (A1==0):
elapsed = str(time.time() - start)
label['text']=elapsed
csv_writer.writerow((1, 1, 2, 3,
4, 5, 6,
7,8, 9,
1, 2, 3,
4, 5, 6,
7, 8,
9, 1, 2,
3, 4, 5,
6, 7, 8,
9, 0, 1,
2, 3, 4,
5, 6, 7,
8, 9, 0,
1, 2,
3, 4, 5,
6, 7, 8,
9, 0, 1,
2, 3,
4, 5, 6,
7, 8, 9,
0, 1, 2,
3, 4,
5, 6, 7))
def stop():
global A1
A1 = 1
root = Tk()
frame = Frame(root)
frame.pack()
root.title("connect and get sensor data")
root.geometry("500x500") # You want the size of the app to be 500x500
root.resizable(0, 0) # Don't allow resizing in the x or y direction
label = Label(root, text="Welcome!", fg="black", font="Verdana 15 bold")
label.pack(side=TOP, padx=5 )
button = Button(root, text='Start', width=25, command=lambda: csv_write(label))
button1 = Button(root, text='Stop', width=25, command=lambda: stop())
button1.pack(side=BOTTOM, pady=10)
button.pack(side=BOTTOM, pady=10)
root.mainloop()
当使用像tkinter这样的GUI工具包时,程序的工作方式与普通python脚本不同 GUI依赖于要更新的事件循环。因此,您的代码必须以回调或超时函数的形式适应事件循环。这样的回调不应该花费太长时间,因为它们是从事件循环执行的。如果它们花费足够长的时间,鼠标和键盘事件就会堆积起来。由于GUI没有响应,这一点很明显 有几种方法可以解决这个问题 最简单的方法是将更新过程分成小块,比如说一行。将当前行的索引保留为全局变量。 在函数中,将索引行写入文件,增加索引。该函数被注册为超时函数(使用
tkinter.Tk
的after
方法)。函数应该做的最后一件事是重新注册自身(在之后使用),除非A1==1
。在Start
按钮的回调中,使用after
调度更新功能
另外两个选项是使用多线程到多处理。然而,这些问题要复杂得多。我不推荐新手使用,也不推荐这么简单的任务
让我们讨论一下在另一个线程中进行更新。这可能很复杂,因为tkinter
不是线程安全的;您不应该从第二个线程调用tkinter
。由于两个线程都可以查看和更改相同的全局变量,因此必须小心使用它们。您应该使用锁(例如互斥锁)保护可以从两个线程读取或更新的变量。也就是说,在两个线程中,您都应该在更改变量之前获取锁,并在进行更改之后释放它。如果变量是可变数据结构,那么即使在读取时也要谨慎使用锁。此外,Python3在不同线程之间分配处理器时间方面比Python2好得多。因此,如果您使用后者,它可能不会像您预期的那样工作
第三种选择是在不同的过程中进行写作。这意味着您必须使用进程间通信,它也必须平滑地安装到事件循环中
下面是我编写的一个示例程序,它在
之后使用。这是一个用于ms windows的简单查找和替换实用程序。原始版本的主机位于
几句话:
我正在定义一个继承自tk.tk
的类作为用户界面。这使得正确封装数据变得更容易;所有回调方法都会自动访问对象的属性。您可以不用类来编写tkinter程序,但它往往有点混乱
\uuuu init\uuuu
方法创建对象(以及必要的属性),但我已将创建窗口分为create\u window
方法
replace_step
方法正在执行工作的一个步骤
回调方法的名称以\u cb
结尾。这只是一个惯例,让他们更容易找到
main
函数在启动GUI之前处理命令行参数
这是代码。我希望你觉得它有用
#!/usr/bin/env python3
# file: far.py
# vim:fileencoding=utf-8:fdm=marker:ft=python
#
# Copyright © 2018 R.F. Smith <rsmith@xs4all.nl>.
# SPDX-License-Identifier: MIT
# Created: 2018-02-27T23:38:17+0100
# Last modified: 2018-04-17T00:11:57+0200
from tkinter import filedialog
from tkinter import ttk
from tkinter.font import nametofont
import argparse
import os
import shutil
import sys
import tkinter as tk
__version__ = '0.1'
class FarUI(tk.Tk):
def __init__(self, rootdir='', findname='', replacement=''):
tk.Tk.__init__(self, None)
self.running = False
self.finditer = None
self.create_window()
self.tree['text'] = rootdir
self.find.insert(0, findname)
self.replace['text'] = replacement
def create_window(self):
"""Create the GUI"""
# Set the font.
default_font = nametofont("TkDefaultFont")
default_font.configure(size=12)
self.option_add("*Font", default_font)
# General commands and bindings
self.bind_all('q', self.quit_cb)
self.wm_title('Find and Replace v' + __version__)
self.columnconfigure(4, weight=1)
self.rowconfigure(4, weight=1)
# First row
ftxt = ttk.Label(self, text='Find:')
ftxt.grid(row=0, column=0, sticky='w')
fe = ttk.Entry(self, justify='left')
fe.grid(row=0, column=1, columnspan=4, sticky='ew')
self.find = fe
# Second row
treetxt = ttk.Label(self, text='In tree:')
treetxt.grid(row=1, column=0, sticky='w')
te = ttk.Label(self, justify='left')
te.grid(row=1, column=1, columnspan=4, sticky='ew')
tb = ttk.Button(self, text="browse...", command=self.tree_cb)
tb.grid(row=1, column=5, columnspan=2, sticky='ew')
self.tree = te
# Third row
reptxt = ttk.Label(self, text='Replace with:')
reptxt.grid(row=2, column=0, sticky='w')
re = ttk.Label(self, justify='left')
re.grid(row=2, column=1, columnspan=4, sticky='ew')
rb = ttk.Button(self, text="browse...", command=self.replace_cb)
rb.grid(row=2, column=5, columnspan=2, sticky='ew')
self.replace = re
# Fourth row
run = ttk.Button(self, text="run", command=self.start_replace_cb)
run.grid(row=3, column=0, sticky='ew')
stop = ttk.Button(self, text="stop", command=self.stop_replace_cb, state=tk.DISABLED)
stop.grid(row=3, column=1, sticky='w')
self.runbutton = run
self.stopbutton = stop
qb = ttk.Button(self, text="quit", command=self.destroy)
qb.grid(row=3, column=2, sticky='w')
ttk.Label(self, justify='left', text='Progress: ').grid(row=3, column=3, sticky='w')
progress = ttk.Label(self, justify='left', text='None')
progress.grid(row=3, column=4, columnspan=2, sticky='ew')
self.progress = progress
# Fifth row
message = tk.Text(self, height=4)
message.grid(row=4, column=0, columnspan=6, sticky='nsew')
s = ttk.Scrollbar(self, command=message.yview)
s.grid(row=4, column=6, sticky='nse')
message['yscrollcommand'] = s.set
self.message = message
def quit_cb(self, event):
"""
Callback to handle quitting.
This is necessary since the quit method does not take arguments.
"""
self.running = False
self.quit()
def tree_cb(self):
rootdir = filedialog.askdirectory(
parent=self, title='Directory where to start looking', mustexist=True
)
self.tree['text'] = rootdir
def replace_cb(self):
replacement = filedialog.askopenfilename(parent=self, title='Replacement file')
self.replace['text'] = replacement
def start_replace_cb(self):
rootdir = self.tree['text']
filename = self.find.get()
replacement = self.replace['text']
if self.running or not rootdir or not filename or not replacement:
self.message.delete('1.0', tk.END)
self.message.insert(tk.END, 'Missing data!')
return
self.running = True
self.message.delete('1.0', tk.END)
self.message.insert(tk.END, 'Starting replacement\n')
self.runbutton['state'] = tk.DISABLED
self.stopbutton['state'] = tk.NORMAL
self.finditer = os.walk(rootdir)
self.after(1, self.replace_step)
def replace_step(self):
if not self.running:
return
try:
path, _, files = self.finditer.send(None)
rootlen = len(self.tree['text']) + 1
# Skip known revision control systems directories.
for skip in ('.git', '.hg', '.svn', '.cvs', '.rcs'):
if skip in path:
self.progress['text'] = 'skipping ' + path[rootlen:]
return
if len(path) > rootlen and path[rootlen] != '.':
self.progress['text'] = 'processing ' + path[rootlen:]
filename = self.find.get()
if filename in files:
original = path + os.sep + filename
replacement = self.replace['text']
repfile = os.path.basename(replacement)
dest = path + os.sep + repfile
self.message.insert(tk.END, "Removing '{}'\n".format(original))
os.remove(original)
self.message.insert(tk.END, "Copying '{}' to '{}'\n".format(replacement, dest))
shutil.copy2(replacement, dest)
self.after(1, self.replace_step)
except StopIteration:
self.stop()
self.message.insert(tk.END, 'Finished replacement.\n')
def stop(self):
self.running = False
self.finditer = None
self.runbutton['state'] = tk.NORMAL
self.stopbutton['state'] = tk.DISABLED
self.progress['text'] = 'None'
def stop_replace_cb(self):
self.stop()
self.message.insert(tk.END, 'Replacement stopped by user.\n')
def main():
"""Main entry point for far.py"""
# Parse the arguments.
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'-d', '--rootdir', type=str, default=os.getcwd(), help='Directory to start looking in.'
)
parser.add_argument('-f', '--findname', type=str, default='', help='Name of the file to find.')
parser.add_argument(
'-r', '--replacement', type=str, default='', help='Path of the replacement file.'
)
parser.add_argument('-v', '--version', action='version', version=__version__)
args = parser.parse_args(sys.argv[1:])
if not args.rootdir.startswith(os.sep):
args.rootdir = os.getcwd() + os.sep + args.rootdir
# Create the UI.
root = FarUI(args.rootdir, args.findname, args.replacement)
root.mainloop()
if __name__ == '__main__':
# Detach from the terminal on POSIX systems.
if os.name == 'posix':
if os.fork():
sys.exit()
# Run the program.
main()
#/usr/bin/env蟒蛇3
#文件:far.py
#vim:fileencoding=utf-8:fdm=marker:ft=python
#
#版权所有©2018 R.F.Smith。
#SPDX许可证标识符:MIT
#创建时间:2018-02-27T23:38:17+0100
#最后修改:2018-04-17T00:11:57+0200
从tkinter导入文件对话框
从tkinter导入ttk
从tkinter.font导入名称到字体
导入argparse
导入操作系统
进口舒蒂尔
导入系统
将tkinter作为tk导入
__版本='0.1'
FarUI类(tk.tk):
定义初始化(self,rootdir='',findname='',replacement='':
tk.tk.\uuuu初始化(self,None)
self.running=False
self.finditer=None
self.create_window()
self.tree['text']=rootdir
self.find.insert(0,findname)
self.replace['text']=替换
def创建_窗口(自):
“”“创建GUI”“”
#设置字体。
默认字体=名称字体(“TkDefaultFont”)
默认字体配置(大小=12)
self.option\u add(“*字体”,默认字体)
#通用命令和绑定
self.bind\u all('q',self.quit\u cb)
self.wm_title('Find and Replace v'+_version_;)
self.columnconfigure(4,权重=1)
self.rowconfigure(4,权重=1)
#第一排
ftxt=ttk.Label(self,text='Find:')
ftxt.grid(行=0,列=0,粘性=w')
fe=ttk.Entry(self,justify='left')
fe.grid(行=0,列=1,列span=4,粘性=ew')
self.find=fe
#第二排
treetxt=ttk.Label(self,text='在树中:')
grid(行=1,列=0,粘性=w')
te=ttk.Label(self,justify='left')
网格(行=1,列=1,列span=4,粘性=ew')
tb=ttk.Button(self,text=“browse…”,command=self.tree\u cb)
tb.grid(行=1,列=5,列span=2,sticky='ew')
self.tree=te
#第三排
reptxt=ttk.Label(self,text='Replace为:')
grid(行=2,列=0,粘性=w')
re=ttk.Label(self,justify='left')
关于网格(行=2,列=1,列span=4,粘性=ew')
rb=ttk.Bu
from tkinter import *
import datetime
import sys
import time
import csv
import math
from threading import Thread
def start_thread(label):
global A1
A1 = 0
# Create and launch a thread
t = Thread(target = csv_write, args = (label, ))
t.start()
def csv_write(label):
global A1
A1 = 0
A = str(datetime.datetime.now()) + ".csv"
start = time.time()
elapsed = 0
with open(A, 'wt') as filename:
csv_writer = csv.writer(filename, delimiter=',')
csv_writer.writerow(('IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z',
'IMU', 'Time', 'A.Sensor.X', 'A.Sensor.Y', 'A.Sensor.Z', 'G.Sensor.X', 'G.Sensor.Y',
'G.Sensor.Z', 'M.Sensor.X', 'M.Sensor.Y', 'M.Sensor.Z'))
while (A1==0):
elapsed = str(time.time() - start)
label['text']=elapsed
csv_writer.writerow((1, 1, 2, 3,
4, 5, 6,
7,8, 9,
1, 2, 3,
4, 5, 6,
7, 8,
9, 1, 2,
3, 4, 5,
6, 7, 8,
9, 0, 1,
2, 3, 4,
5, 6, 7,
8, 9, 0,
1, 2,
3, 4, 5,
6, 7, 8,
9, 0, 1,
2, 3,
4, 5, 6,
7, 8, 9,
0, 1, 2,
3, 4,
5, 6, 7))
def stop():
global A1
A1 = 1
root = Tk()
frame = Frame(root)
frame.pack()
root.title("connect and get sensor data")
root.geometry("500x500") # You want the size of the app to be 500x500
root.resizable(0, 0) # Don't allow resizing in the x or y direction
label = Label(root, text="Welcome!", fg="black", font="Verdana 15 bold")
label.pack(side=TOP, padx=5 )
button = Button(root, text='Start', width=25, command=lambda: start_thread(label))
button1 = Button(root, text='Stop', width=25, command=lambda: stop())
button1.pack(side=BOTTOM, pady=10)
button.pack(side=BOTTOM, pady=10)
root.mainloop()