问题描述
我编写了一个python GUI ,它应该通过单击一个名为“开始”的按钮来创建和更新一个.csv文件,并且应该通过单击另一个名为“停止”的按钮来停止更新 .csv 的 while 循环。 但是每当我运行 GUI 并单击开始时,它就会冻结。 虽然,我看到.csv文件不断更新,但我无法阻止 .csv 更新新行。 我只是简单地使用 python 2.7 和 ubuntu 终端编写python filename.py来运行代码。 任何人都可以检查我的代码有什么问题吗?
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()
1楼
当使用像 tkinter 这样的 GUI 工具包时,程序的工作方式与普通的 Python 脚本不同。
GUI 依赖于事件循环来更新。 所以你的代码必须以回调或超时函数的形式适应事件循环。 此类回调不应花费太长时间,因为它们是从事件循环中执行的。 如果它们花费的时间足够长,鼠标和键盘事件就会堆积起来。 由于 GUI 无响应,这将很明显。
有几种方法可以解决这个问题。
最简单的方法是将更新过程分成小块,比如一行。
您将当前行的索引保留为全局变量。
在函数中,您将索引行写入文件,增加索引。
该函数注册为超时函数(使用tkinter.Tk
的after
方法)。
除非A1 == 1
否则该函数应该做的最后一件事是再次重新注册自己(使用after
)。
在Start
按钮的回调中,您使用after
安排更新功能。
另外两个选项是使用多线程进行多处理。 然而,这些要复杂得多。 我不会将它们推荐给新手,也不会推荐给这样一个相对简单的任务。
让我们在不同的线程中讨论更新。
这可能很复杂,因为tkinter
不是线程安全的;
你不应该叫tkinter
从第二个线程。
由于两个线程都可以查看和更改相同的全局变量,因此您必须小心处理它们。
您应该使用锁保护可以从两个线程读取或更新的变量(例如互斥锁)。
也就是说,在两个线程中,您都应该在更改变量之前获取锁,并在更改之后释放它。
如果变量是一个可变数据结构,即使在读取它时使用锁也是明智的。
此外,与 Python2 相比,Python3 在不同线程之间划分处理器时间方面要好得多。
因此,如果您使用后者,它可能无法按您的预期工作。
第三种选择是在不同的过程中进行写作。 这意味着你必须使用进程间通信,它也必须顺利地适应事件循环。
下面是我编写的一个示例程序,它after
.
它是 ms-windows 的简单查找和替换实用程序。
原版托管在。
几点说明:
我正在定义一个从
tk.Tk
继承的类作为用户界面。 这样可以更容易地正确封装数据; 所有回调方法都会自动访问对象的属性。 您可以在没有类的情况下执行 tkinter 程序,但它往往会更混乱一些。__init__
方法创建了对象(和必要的属性),但我已经将创建窗口的过程分离到create_window
方法中。replace_step
方法正在完成一项工作。回调方法的名称以
_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()
2楼
使用 Thread 和全局变量时,它对我有用。 没那么复杂,几行就行。 您的代码已被修改。 希望能帮助到你:-
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()