当前位置: 代码迷 >> python >> 使用 tkinter 中的停止按钮中断 while 循环
  详细解决方案

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

热度:143   发布时间:2023-06-19 09:17:36.0

我编写了一个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()

当使用像 tkinter 这样的 GUI 工具包时,程序的工作方式与普通的 Python 脚本不同。

GUI 依赖于事件循环来更新。 所以你的代码必须以回调或超时函数的形式适应事件循环 此类回调不应花费太长时间,因为它们是从事件循环中执行的。 如果它们花费的时间足够长,鼠标和键盘事件就会堆积起来。 由于 GUI 无响应,这将很明显。

有几种方法可以解决这个问题。

最简单的方法是将更新过程分成小块,比如一行。 您将当前行的索引保留为全局变量。 在函数中,您将索引行写入文件,增加索引。 该函数注册为超时函数(使用tkinter.Tkafter方法)。 除非A1 == 1否则该函数应该做的最后一件事是再次重新注册自己(使用after )。 Start按钮的回调中,您使用after安排更新功能。

另外两个选项是使用多线程进行多处理。 然而,这些要复杂得多。 我不会将它们推荐给新手,也不会推荐给这样一个相对简单的任务。

让我们在不同的线程中讨论更新。 这可能很复杂,因为tkinter不是线程安全的; 应该叫tkinter从第二个线程。 由于两个线程都可以查看和更改相同的全局变量,因此您必须小心处理它们。 您应该使用锁保护可以从两个线程读取或更新的变量(例如互斥锁)。 也就是说,在两个线程中,您都应该在更改变量之前获取锁,并在更改之后释放它。 如果变量是一个可变数据结构,即使在读取它时使用锁也是明智的。 此外,与 Python2 相比,Python3 在不同线程之间划分处理器时间方面要好得多。 因此,如果您使用后者,它可能无法按您的预期工作。

第三种选择是在不同的过程中进行写作。 这意味着你必须使用进程间通信,它也必须顺利地适应事件循环。

下面是我编写的一个示例程序,它after . 它是 ms-windows 的简单查找和替换实用程序。 原版托管在。

几点说明:

  1. 我正在定义一个从tk.Tk继承的类作为用户界面。 这样可以更容易地正确封装数据; 所有回调方法都会自动访问对象的属性。 您可以在没有类的情况下执行 tkinter 程序,但它往往会更混乱一些。

  2. __init__方法创建了对象(和必要的属性),但我已经将创建窗口的过程分离到create_window方法中。

  3. replace_step方法正在完成一项工作。

  4. 回调方法的名称以_cb 这只是使它们更容易找到的约定。

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

使用 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()
  相关解决方案