【问题标题】:How to Properly Handle Multiple Calls to wx.App() using wxPython Phoenix如何使用 wxPython Phoenix 正确处理对 wx.App() 的多个调用
【发布时间】:2020-01-09 05:52:58
【问题描述】:

我正在尝试对使用 wxPython 4.0.7 的应用程序遇到的问题进行故障排除/调试。

我重新编写了在 Windows 7 32 位系统上运行 Python 2.7 和 wxPython 2.8 的整个程序,现在可以在 64 位 Windows 10 系统上运行 64 位 Python 3.7.4 和 wxPython 4.0.7。

我遇到的问题是我的程序要求它根据用户指定的循环数进行多次迭代,并从使用的两个不同的 python 脚本中调用wx.App() 的实例。

我读过调用wx.App() 的多个实例是“不可以”(参见creating multiple instances of wx.App

显然这是这个版本的 wxPython 的问题,因为我的应用程序现在在第一次迭代后崩溃了,而之前它运行良好。

好的,我现在明白这一点,但我不确定针对我的特定问题的“修复”是什么。

我的应用程序的基本大纲是这样的: 将启动一个“runner.py”脚本,其中包含主要的wx.frame() gui,并将以下代码附加到脚本的末尾:

app = wx.App()
frame = Runner(parent=None, foo=Foo)
frame.Show()
app.MainLoop()

当用户点击 wxPython GUI 中的“执行”按钮时,我有一个使用此代码启动的进度对话框:

pd = wx.ProgressDialog(title = "Runner.py", message= "Starting Model", parent=self, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH | wx.PD_CAN_ABORT )
pd.Update(15)

runner.py 脚本执行一个“for 循环”,它会做很多事情(实际上是从 R 脚本中读取一些输入),然后一旦完成,它就会打开第二个 python 脚本(“looping.py”)并根据用户在从 runner.py 启动的 GUI 中指定的循环数迭代一组进程。

由于用户需要直观地查看模型运行所经历的循环过程,我在第二个“looping.py”脚本中,还有另一个 wx.App() 实例调用了另一个 wx.ProgressDialog(),以及脚本看起来像这样:

#Progress Bar to user to start model
app = wx.App()
pd = wx.ProgressDialog("looping.py", "Setup Iteration", parent=None, style=wx.PD_AUTO_HIDE | wx.PD_SMOOTH |  wx.PD_CAN_ABORT )
pd.Update(15)

我的具体问题是:如何在“looping.py”脚本中成功启动wx.ProgressDialog(),而不会使我的应用程序在第一次迭代后崩溃?

【问题讨论】:

    标签: python python-multiprocessing wxpython


    【解决方案1】:

    您可能必须继承wx.ProgressDialog,可以说编写自己的进度条显示可能更容易。
    像这样的东西,可能会给你一些想法。
    我已经包含了运行多个线程做不同事情的能力,使用pausestop 按钮。主框架有一个按钮,用于在运行线程时测试 Gui 是否仍然处于活动状态。
    来自线程的更新由event 驱动 您可能希望减少或增加其选项。

    import time
    import wx
    from threading import Thread
    import wx.lib.newevent
    progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
    
    class MainFrame(wx.Frame):
    
        def __init__(self):
            wx.Frame.__init__(self, None, title='Main Frame', size=(400,400))
            panel = MyPanel(self)
            self.Show()
    
    class MyPanel(wx.Panel):
    
        def __init__(self, parent):
            wx.Panel.__init__(self, parent)
            self.text_count = 0
            self.parent=parent
            self.btn_start = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(180,30), pos=(10,10))
            btn_test = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(180,30), pos=(10,50))
            self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(300,100))
    
            self.btn_start.Bind(wx.EVT_BUTTON, self.Start_Process)
            btn_test.Bind(wx.EVT_BUTTON, self.ActiveText)
    
        def Start_Process(self, event):
            process1 = ProcessingFrame(title='Threaded Task 1', parent=self, job_no=1)
            process2 = ProcessingFrame(title='Threaded Task 2', parent=self, job_no=2)
            self.btn_start.Enable(False)
    
        def ActiveText(self,event):
            self.text_count += 1
            txt = "Gui is still active " + str(self.text_count)+"\n"
            self.txt.write(txt)
    
    class ProcessingFrame(wx.Frame):
    
        def __init__(self, title, parent=None,job_no=1):
            wx.Frame.__init__(self, parent=parent, title=title, size=(400,400))
            panel = wx.Panel(self)
            self.parent = parent
            self.job_no = job_no
            self.btn = wx.Button(panel,label='Stop processing', size=(200,30), pos=(10,10))
            self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
            self.btn_pause = wx.Button(panel,label='Pause processing', size=(200,30), pos=(10,50))
            self.btn_pause.Bind(wx.EVT_BUTTON, self.OnPause)
            self.progress = wx.Gauge(panel,size=(200,10), pos=(10,90), range=60)
            self.process = wx.TextCtrl(panel,size = (200,250), pos=(10,120), style = wx.TE_MULTILINE)
            #Bind to the progress event issued by the thread
            self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
            #Bind to Exit on frame close
            self.Bind(wx.EVT_CLOSE, self.OnExit)
            self.Show()
    
            self.mythread = TestThread(self)
    
        def OnProgress(self, event):
            self.progress.SetValue(event.count)
            self.process.write(event.process+"\n")
            self.Refresh()
            if event.count >= 60:
                self.OnExit(None)
    
        def OnExit(self, event):
            if self.mythread.isAlive():
                self.mythread.terminate() # Shutdown the thread
                self.mythread.join() # Wait for it to finish
                self.parent.btn_start.Enable(True)
            self.Destroy()
    
        def OnPause(self, event):
            if self.mythread.isAlive():
                self.mythread.pause() # Pause the thread
    
    class TestThread(Thread):
        def __init__(self,parent_target):
            Thread.__init__(self)
            self.target = parent_target
            self.stopthread = False
            self.process = 1 # Testing only - mock process id
            self.start()    # start the thread
    
        def run(self):
            # A selectable test loop that will run for 60 loops then terminate
            if self.target.job_no == 1:
                self.run1()
            else:
                self.run2()
    
        def run1(self):
            curr_loop = 0
            while self.stopthread != True:
                if self.stopthread == "Pause":
                    time.sleep(1)
                    continue
                curr_loop += 1
                self.process += 10 # Testing only - mock process id
                if curr_loop <= 60: # Update progress bar
                    time.sleep(1.0)
                    evt = progress_event(count=curr_loop,process="Envoking process "+str(self.process))
                    #Send back current count for the progress bar
                    try:
                        wx.PostEvent(self.target, evt)
                    except: # The parent frame has probably been destroyed
                        self.terminate()
            self.terminate()
    
        def run2(self):
            curr_loop = 0
            while self.stopthread != True:
                if self.stopthread == "Pause":
                    time.sleep(1)
                    continue
                curr_loop += 1
                self.process += 100 # Testing only - mock process id
                if curr_loop <= 60: # Update progress bar
                    time.sleep(1.0)
                    evt = progress_event(count=curr_loop,process="Checking process"+str(self.process))
                    #Send back current count for the progress bar
                    try:
                        wx.PostEvent(self.target, evt)
                    except: # The parent frame has probably been destroyed
                        self.terminate()
            self.terminate()
    
        def terminate(self):
            self.stopthread = True
    
        def pause(self):
            if self.stopthread == "Pause":
                self.stopthread = False
                self.target.btn_pause.SetLabel('Pause processing')
            else:
                self.stopthread = "Pause"
                self.target.btn_pause.SetLabel('Continue processing')
    
    if __name__ == '__main__':
        app = wx.App(False)
        frame = MainFrame()
        app.MainLoop()
    

    【讨论】:

    • 我接受您的回答,因为它最有助于为我指明解决此问题的方向。开始/停止循环非常有用,因为在我的案例中我有 9 个循环可以迭代,这有助于我更好地可视化多线程过程。非常感谢您的努力。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    • 2021-10-23
    • 2017-04-29
    • 1970-01-01
    • 1970-01-01
    • 2021-09-09
    相关资源
    最近更新 更多