wxpython:单击停止按钮时是否可以停止正在运行的程序?

时间:2018-12-22 09:56:04

标签: python python-3.x user-interface wxpython wxwidgets

假设我有一个大型程序,需要一段时间才能完成,所以当程序启动时,我发现某些参数未正确设置,因此我想停止它。而不是完全终止程序,我希望运行的计算停止,但是wx.Frame仍然显示。可能吗?下面是示例代码:

import wx
import time

class Test(wx.Frame):

    def __init__(self, title):
        super().__init__(None, title=title)
        self.panel = wx.Panel(self)

        self.initUI()
        self.Centre()

    def initUI(self):
        vbox = wx.BoxSizer(wx.VERTICAL)
        button1 = wx.Button(self.panel, label="START Calculation")
        button1.Bind(wx.EVT_BUTTON, self.start_test)

        button2 = wx.Button(self.panel, label="STOP Calculation")
        button2.Bind(wx.EVT_BUTTON, self.stop_test)

        vbox.Add(button1, 0, wx.ALL, 5)
        vbox.Add(button2, 0, wx.ALL, 5)
        self.panel.SetSizer(vbox)

    def start_test(self, e):
        for i in range(20000):
            print("Program is running...")
            time.sleep(5)

    def stop_test(self, e):
        print("How do I stop the test when click this button?")


if __name__ == '__main__':
    app = wx.App()
    Test("A large program").Show()
    app.MainLoop()

enter image description here

如程序所示,当我单击开始按钮时,程序将运行。我希望我可以停止该程序,但仍然保留应用程序窗口。目前,当我单击开始按钮时,甚至无法单击停止按钮。是否可以实现我想要的目标?

3 个答案:

答案 0 :(得分:1)

您无法通过单个线程来做到这一点。

您必须在另一个线程中运行计算循环,并让停止按钮设置一个标志,这样循环才能在某个时间点停止(因为您不能强行杀死线程:Is there any way to kill a Thread?。TL; DR?是

类似这样的东西:

import threading

...

  def run_code(self):
      for i in range(20000):
          print("Program is running...")
          time.sleep(5)
          if self.__stopped:
             break
      self.__stopped = True

  def start_test(self, e):
      if not self.__stopped:
         self.__stopped = False
         threading.Thread(target=run_code).start()

  def stop_test(self, e):
      self.__stopped = True

现在,当您单击“开始”时,它将在线程中启动run_code方法,因此它会退回到主循环,并且“停止”按钮处于活动状态。

由于线程在另一种方法中运行,因此可以共享__stopped属性。

请小心地不时发出time.sleep()调用(甚至很小),因为线程在Python中不使用时间切片(因为大多数python版本都实现了全局解释器锁)。正在运行的线程必须不时向主线程释放一些CPU,否则将无法正常工作。好吧,您可以先尝试一下再看看。

也不要在线程中使用wx调用。使用wx.CallAfter安排线程内的wx调用,以在主线程中执行。

答案 1 :(得分:1)

我只是稍微修改了您的代码。我添加了一个线程来运行calculate方法,因此它不会阻塞主线程。

import wx
import time
import threading

class Test(wx.Frame):

    def __init__(self, title):
        super().__init__(None, title=title)
        self.panel = wx.Panel(self)

        self.initUI()
        self.Centre()

    def initUI(self):
        vbox = wx.BoxSizer(wx.VERTICAL)
        button1 = wx.Button(self.panel, label="START Calculation")
        button1.Bind(wx.EVT_BUTTON, self.start_test)

        button2 = wx.Button(self.panel, label="STOP Calculation")
        button2.Bind(wx.EVT_BUTTON, self.stop_test)

        vbox.Add(button1, 0, wx.ALL, 5)
        vbox.Add(button2, 0, wx.ALL, 5)
        self.panel.SetSizer(vbox)
        self.stop = False

    def create_thread(self, target):
        thread = threading.Thread(target=target)
        thread.daemon = True
        thread.start()

    def calculate(self):
        for i in range(20000):
            if self.stop:
                break
            print("Program is running...")
            time.sleep(5)
        self.stop = False

    def start_test(self, e):
        self.create_thread(self.calculate)

    def stop_test(self, e):
        print("The calculation has been stopped!")
        self.stop = True


if __name__ == '__main__':
    app = wx.App()
    Test("A large program").Show()
    app.MainLoop()

如果self.stop为True,它将从foor循环中退出。这样,您只需单击两个按钮即可开始和停止计算。

答案 2 :(得分:0)

有些人似乎不赞成这种方法,但我发现它简单而有用。
利用Yield,它会暂时将控制权交还给主循环。
这样可以使主循环仍然响应,即可以在循环迭代之间激活停止按钮。

import wx
import time

class Test(wx.Frame):

    def __init__(self, title):
        super().__init__(None, title=title)
        self.panel = wx.Panel(self)

        self.initUI()
        self.Centre()

    def initUI(self):
        vbox = wx.BoxSizer(wx.VERTICAL)
        button1 = wx.Button(self.panel, label="START Calculation")
        button1.Bind(wx.EVT_BUTTON, self.start_test)

        button2 = wx.Button(self.panel, label="STOP Calculation")
        button2.Bind(wx.EVT_BUTTON, self.stop_test)

        vbox.Add(button1, 0, wx.ALL, 5)
        vbox.Add(button2, 0, wx.ALL, 5)
        self.panel.SetSizer(vbox)

    def start_test(self, e):
        self.running = 0
        loop = 0
        while self.running == 0:
            print("Program is running..."+str(loop))
            time.sleep(1)
            loop +=1
            wx.GetApp().Yield()

    def stop_test(self, e):
        print("Process has stopped")
        self.running = 1

if __name__ == '__main__':
    app = wx.App()
    Test("A large program").Show()
    app.MainLoop()