如何使用 wxPython Phoenix 正确处理对 wx.App() 的多次调用

How to Properly Handle Multiple Calls to wx.App() using wxPython Phoenix

我正在尝试 troubleshoot/de-bug 我在使用 wxPython 4.0.7 的应用程序中遇到的问题。

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

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

我读到调用 wx.App() 的多个实例是 "no-no"(参见

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

好的,所以我现在明白了,但是我不确定 "fix" 是针对我的特定问题的。

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

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

当用户单击 wxPython GUI 中的 "execute" 按钮时,我有一个使用以下代码启动的进度对话框:

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 loop" 做一堆事情(实际上是从 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() 而不会在第一次迭代后使我的应用程序崩溃?

你可能要分class 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()