Python 在tkinter中使用停止按钮中断while循环

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

我编写了一个python GUI,它应该通过单击一个名为的按钮“开始”来创建和更新一个.csv文件,并且应该通过单击另一个名为的按钮“停止”来停止更新.csv的while循环。但每当我运行GUI并单击start时,它就会冻结。虽然我看到.csv文件在不断更新,但我无法阻止.csv更新新行。我只是简单地使用Python2.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()

当使用像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()