pythonuser-interfacewxpythonmodeless

Is it possible to create a modeless window with wxPython which stays along (to display text info)?


Quite new with UI and python, I am wondering if it's possible to have a window (in my case with wxPython) which stays alive all the time until we destroy it, to perform many other actions in the meantime. If so, where the main running code should be? Here is an example where it is wrong and doesn't work of course:

from time import sleep
import wx


def do_actions(g):
    for i in range(100):
        print('Iteration: ', i)
        g.SetValue(i)
        sleep(1)


class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title)

        panel = wx.Panel(self, -1)
        self.gauge = wx.Gauge(panel, -1, 50, size=(250, 25))

        do_actions(self.gauge)


class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, 'wx.Gauge')
        frame.Show(True)


app = MyApp(0)
app.MainLoop()

Solution

  • You are probably trying to run, before you've learnt to walk but there's nothing wrong with ambition.
    You will need to read up on threading/multiprocessing i.e. running separate processes at the same time.
    wxPython has a single main event loop, which is used to monitor all of the the GUI events, so that has to be in control at all times.
    We fire up a thread to run each job and the GUI updates are issued as events.

    That's probably not a very good explanation so here is a bit of simplistic test code, which illustrates one way of setting this up. The threads only run for a limited time but obviously, you can adjust that.
    Hopefully, you can pick the bones out of this enough to get you started.

    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.process1 = None
            self.process2 = None
            self.jobs = []
            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):
            self.process1 = ProcessingFrame(title='Threaded Task 1', parent=self, job_no=1)
            self.jobs.append(1)
            self.process2 = ProcessingFrame(title='Threaded Task 2', parent=self, job_no=2)
            self.jobs.append(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)
            if self.process1:
                txt = "process1 alive "+str(self.process1.mythread.is_alive())+"\n"
            else:
                txt = "process1 is dead\n"
            if self.process2:
                txt += "process2 alive "+str(self.process2.mythread.is_alive())+"\n"
            else:
                txt += "process2 is dead\n"
            self.txt.write(txt)
    
    class ProcessingFrame(wx.Frame):
    
        def __init__(self, title, parent=None,job_no=None):
            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.is_alive():
                self.mythread.terminate() # Shutdown the thread
                self.mythread.join() # Wait for it to finish
                self.parent.jobs.remove(self.job_no)
                if not self.parent.jobs:
                    self.parent.btn_start.Enable(True)
            self.Destroy()
    
        def OnPause(self, event):
            if self.mythread.is_alive():
                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()
    

    enter image description here