【问题标题】:How do you create a Tkinter GUI stop button to break an infinite loop?你如何创建一个 Tkinter GUI 停止按钮来打破无限循环?
【发布时间】:2015-01-18 22:58:37
【问题描述】:

所以我有一个带有两个简单选项的 Tkinter GUI,一个开始和停止按钮。我已经定义了 GUI 布局:

from Tkinter import *

def scanning():
    while True:
        print "hello"

root = Tk()
root.title("Title")
root.geometry("500x500")

app = Frame(root)
app.grid()

这里的开始按钮运行无限循环扫描,而停止按钮应该在按下时中断:

start = Button(app, text="Start Scan",command=scanning)
stop = Button(app, text="Stop",command="break")

start.grid()
stop.grid()

但是,当我点击开始按钮时,它总是被按下(假设是因为无限循环)。但是,我无法单击停止按钮来跳出 while 循环。

【问题讨论】:

    标签: python user-interface tkinter


    【解决方案1】:

    最好的方法是使用线程和全局变量。您的代码已修改为包含这些。希望对您有所帮助。

        from tkinter import *
        from threading import Thread
    
        def scanning():
            while True:
                print ("hello")
                if stop == 1:   
                    break   #Break while loop when stop = 1
    
        def start_thread():
            # Assign global variable and initialize value
            global stop
            stop = 0
    
            # Create and launch a thread 
            t = Thread (target = scanning)
            t.start()
    
        def stop():
            # Assign global variable and set value to stop
            global stop
            stop = 1
    
        root = Tk()
        root.title("Title")
        root.geometry("500x500")
    
        app = Frame(root)
        app.grid()
    
        start = Button(app, text="Start Scan",command=start_thread)
        stop = Button(app, text="Stop",command=stop)
    
        start.grid()
        stop.grid()
    

    【讨论】:

    • 我喜欢你的回答,但直到我添加了这行代码后它才真正运行:app.mainloop()
    【解决方案2】:

    另一种解决方案是创建一个执行该功能的可执行文件,while 不是 while-true,而是从外部读取的条件(例如使用 pickle 的二进制文件)

    condition = True
    while condition:
        condition = pickle.load(open(condition.p,'rb'))
        print('hello from executable')
    # endwhile condition
    

    因此,在 GUI 中,您有一个调用“暂停”方法的按钮。它修改了文件“condition.p”的内容,因此修改了所需的循环

    def pause(self):
        self.condition = not self.condition
        pickle.dump(self.condition, open('condition.p','wb'))
        if self.condition == True: # reset infinite loop again! :)
            os.system('executable.exe')
    # enddef pause
    

    【讨论】:

      【解决方案3】:

      这是一个不同的解决方案,具有以下优点:

      1. 不需要手动创建单独的线程

      2. 不使用Tk.after 调用。相反,保留了具有连续循环的原始代码样式。这样做的主要优点是您不必手动指定确定循环内代码运行频率的毫秒数,它只需按照硬件允许的频率运行即可。

      注意:我只在 python 3 上试过这个,而不是在 python 2 上。我想在 python 2 中也应该这样,我只是不100% 肯定知道。

      对于 UI 代码和启动/停止逻辑,我将使用与 iCodez 的答案中大部分相同的代码。一个重要的区别是我假设我们将始终运行一个循环,但在该循环中根据最近按下的按钮来决定要做什么:

      from tkinter import *
      
      running = True  # Global flag
      idx = 0  # loop index
      
      def start():
          """Enable scanning by setting the global flag to True."""
          global running
          running = True
      
      def stop():
          """Stop scanning by setting the global flag to False."""
          global running
          running = False
      
      root = Tk()
      root.title("Title")
      root.geometry("500x500")
      
      app = Frame(root)
      app.grid()
      
      start = Button(app, text="Start Scan", command=start)
      stop = Button(app, text="Stop", command=stop)
      
      start.grid()
      stop.grid()
      
      while True:
          if idx % 500 == 0:
              root.update()
      
          if running:
              print("hello")
              idx += 1
      

      在这段代码中,我们没有调用root.mainloop() 来让 tkinter GUI 不断更新。相反,我们每隔一段时间手动更新一次(在这种情况下,每 500 次循环迭代)。

      理论上,这意味着我们可能不会在点击停止按钮后立即停止循环。例如,如果在我们按下停止按钮的确切时刻,我们正处于迭代 501,那么此代码将继续循环,直到击中迭代 1000。因此,此代码的缺点是理论上我们的 GUI 响应性稍差(但如果循环中的代码很快,则不会引起注意)。作为回报,我们让循环内的代码尽可能快地运行(只是有时会产生 GUI update() 调用的开销),并让它在主线程内运行。

      【讨论】:

      • 这让我找到了一个解决方案,当我在 matplotlib 中有一个循环时,我无法访问 tkinter 事件。在循环中包含 root.update() 会使程序再次响应这些事件。
      【解决方案4】:

      您不能在 Tkinter 事件循环正在运行的同一线程中启动 while True: 循环。这样做会阻塞 Tkinter 的循环并导致程序冻结。

      对于一个简单的解决方案,您可以使用Tk.after 每秒左右在后台运行一个进程。下面是一个演示脚本:

      from Tkinter import *
      
      running = True  # Global flag
      
      def scanning():
          if running:  # Only do this if the Stop button has not been clicked
              print "hello"
      
          # After 1 second, call scanning again (create a recursive loop)
          root.after(1000, scanning)
      
      def start():
          """Enable scanning by setting the global flag to True."""
          global running
          running = True
      
      def stop():
          """Stop scanning by setting the global flag to False."""
          global running
          running = False
      
      root = Tk()
      root.title("Title")
      root.geometry("500x500")
      
      app = Frame(root)
      app.grid()
      
      start = Button(app, text="Start Scan", command=start)
      stop = Button(app, text="Stop", command=stop)
      
      start.grid()
      stop.grid()
      
      root.after(1000, scanning)  # After 1 second, call scanning
      root.mainloop()
      

      当然,您可能希望将此代码重构为一个类,并让running 成为它的一个属性。此外,如果您的程序变得复杂,最好查看 Python 的 threading module 以便您的 scanning 函数可以在单独的线程中执行。

      【讨论】:

      • 我应该补充一点,我需要 while 循环,因为它连续扫描蓝牙 RSSI 信号。因此,这个程序对我不起作用。有没有其他方法可以通过while循环来解决这个问题? @iCodez
      • @JonathanDavies - 好吧,您总是可以将 root.after(1000, scanning) 更改为 root.after(1, scanning) 以让代码每毫秒执行一次 scanning。这将具有与连续 while 循环大致相同的效果。否则,您需要将循环放在单独的线程中。我上面给出的链接有更多信息,但基本上你会把循环放在一个函数中,然后把这个函数交给threading.Thread
      • 所以据我所知,我只是这样做:start = Button(app, text="Start Scan",command=threading.Thread(name="bluetooth",target=scanning))。这不起作用,如果我在 Thread() 之后添加 .start(),它会立即启动该功能,而无需我单击。 @iCodez
      • 不,您可以将循环放入scanning,然后执行thread = threading.Thread(name="bluetooth",target=scanning); thread.start()。这将使循环在不同的线程中执行。将它连接到按钮会有点棘手。您将需要使用线程安全的容器,例如 Queue.Queue 来向工作线程发送消息。
      • 那是因为您在创建按钮时调用了thread.start。你需要做command=thread.start。但我认为更好的方法是在启动程序时创建工作线程,然后让它不断检查队列。开始和停止按钮只会将消息放入队列中,以告诉工作人员开始或停止扫描。
      猜你喜欢
      • 2018-09-01
      • 1970-01-01
      • 2017-01-26
      • 2021-11-11
      • 2013-08-22
      • 1970-01-01
      • 2023-01-30
      • 2014-01-21
      • 2020-11-04
      相关资源
      最近更新 更多