简体   繁体   English

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

[英]Break a while loop with a stop button in tkinter

I wrote a python GUI which should create and update a .csv file by clicking a button called "Start" and should stop the while loop which updates .csv by clicking another button called "Stop" .我编写了一个python GUI ,它应该通过单击一个名为“开始”的按钮来创建和更新一个.csv文件,并且应该通过单击另一个名为“停止”的按钮来停止更新 .csv 的 while 循环。 But whenever I run the GUI and click on start it freezes.但是每当我运行 GUI 并单击开始时,它就会冻结。 Although, I see that the .csv file is continuously updating but I can't stop the .csv from updating new rows.虽然,我看到.csv文件不断更新,但我无法阻止 .csv 更新新行。 I am just simply running the code using python 2.7 & ubuntu terminal writing python filename.py .我只是简单地使用 python 2.7 和 ubuntu 终端编写python filename.py来运行代码。 Can anyone please check what's wrong in my code?任何人都可以检查我的代码有什么问题吗?

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()

When using a GUI toolkit like tkinter, programs work differently from normal python scripts.当使用像 tkinter 这样的 GUI 工具包时,程序的工作方式与普通的 Python 脚本不同。

GUI's depend on an event loop to update. GUI 依赖于事件循环来更新。 So your code must fit into the event loop into the form of callbacks or timeout functions.所以你的代码必须以回调或超时函数的形式适应事件循环 Such callbacks should not take too long because they are executed from the event loop.此类回调不应花费太长时间,因为它们是从事件循环中执行的。 If they take long enough mouse and keyboard events will pile up.如果它们花费的时间足够长,鼠标和键盘事件就会堆积起来。 This will be noticeable as the GUI being unresponsive.由于 GUI 无响应,这将很明显。

There are several ways to solve this.有几种方法可以解决这个问题。

The easiest way is to cut up the update process in small pieces, say one row.最简单的方法是将更新过程分成小块,比如一行。 You keep the index of the current row as a global variable.您将当前行的索引保留为全局变量。 In a function, you write the indexed row to the file, increase the index.在函数中,您将索引行写入文件,增加索引。 The function is registered as a timeout function (using the after method of tkinter.Tk ).该函数注册为超时函数(使用tkinter.Tkafter方法)。 The last thing that the function should do is re-register itself again (using after ) unless A1 == 1 .除非A1 == 1否则该函数应该做的最后一件事是再次重新注册自己(使用after )。 In the callback for the Start button, you schedule the update function with after .Start按钮的回调中,您使用after安排更新功能。

Two other options are to use multithreading to multiprocessing.另外两个选项是使用多线程进行多处理。 However, these are significantly more complicated.然而,这些要复杂得多。 I would not recommend them for a novice, nor for such a relatively easy task.我不会将它们推荐给新手,也不会推荐给这样一个相对简单的任务。

Let's talk about updating in a different thread.让我们在不同的线程中讨论更新。 This can be complicated because tkinter is not thread-safe;这可能很复杂,因为tkinter不是线程安全的; you should not call tkinter from that second thread.应该叫tkinter从第二个线程。 Since both threads can see and alter the same global variables, you have to be careful with them.由于两个线程都可以查看和更改相同的全局变量,因此您必须小心处理它们。 You should guard variables that can be read or updated from both threads with locks (eg mutex).您应该使用锁保护可以从两个线程读取或更新的变量(例如互斥锁)。 That is, in both threads you should acquire the lock before changing a variable and release it after you have made the change.也就是说,在两个线程中,您都应该在更改变量之前获取锁,并在更改之后释放它。 If the variable is a mutable data structure it would be prudent to use the lock even when reading from it.如果变量是一个可变数据结构,即使在读取它时使用锁也是明智的。 Additionally, Python3 is much better at dividing processor time between different threads than Python2.此外,与 Python2 相比,Python3 在不同线程之间划分处理器时间方面要好得多。 So if you use the latter, it might not work as you expect.因此,如果您使用后者,它可能无法按您的预期工作。

The third option is to do the writing in a different process.第三种选择是在不同的过程中进行写作。 This means that you have to use inter-process communication, which has to be fitted smoothly into the event loop as well.这意味着你必须使用进程间通信,它也必须顺利地适应事件循环。

Below is an example program I wrote that uses after .下面是我编写的一个示例程序,它after . It is a simple find and replace utility for ms-windows.它是 ms-windows 的简单查找和替换实用程序。 The original is hosted on github .原版托管在github 上

A couple of remarks:几点说明:

  1. I'm defining a class that inherits from tk.Tk as the user interface.我正在定义一个从tk.Tk继承的类作为用户界面。 This makes it easier to encapsulate data properly;这样可以更容易地正确封装数据; all callback methods automatically have access to the object's attributes.所有回调方法都会自动访问对象的属性。 You can do tkinter programs without a class, but it tends to be a little more messy.您可以在没有类的情况下执行 tkinter 程序,但它往往会更混乱一些。

  2. The __init__ method creates the object (and the neccesary attributes), but I have separated creating the window into the create_window method. __init__方法创建了对象(和必要的属性),但我已经将创建窗口的过程分离到create_window方法中。

  3. The replace_step method is doing one step of the work. replace_step方法正在完成一项工作。

  4. The names of callback methods end in _cb .回调方法的名称以_cb This is just a convention to make them easier to find.这只是使它们更容易找到的约定。

  5. The main function handles command-line arguments before starting the GUI. main函数在启动 GUI 之前处理命令行参数。

Here is the code.这是代码。 I hope you find it useful.希望对你有帮助。

#!/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()

It works for me when using Thread and global variable.使用 Thread 和全局变量时,它对我有用。 Not that complicated, just a few lines.没那么复杂,几行就行。 Your code was modified with those.您的代码已被修改。 Hope it helps:-希望能帮助到你:-

    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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM