Search code examples
pythonuser-interfacedynamicwxpython

wxPython: pause main script and wait for button press


I am working on a GUI using wxPython. This GUI should dynamically ask to the user several sets of inputs in the same window, updating the window with the new set of inputs after a button "ok" is pressed.

To do this I have a for loop which calls a function that prompts the input controls on the window. I have tried to use the threading.Event class, letting the script wait for the button to be pressed, but python crashes.

Here below is the interested part of the code.

        # Iterate through parameters
        self.cv = threading.Event()
        for key in parameters:
            self.input_sizer = [None]*len(parameters[key])
            self.cv.clear()
            self.make_widgets(key, parameters[key])
            self.cv.wait()
        # Panel final settings
        self.panel.SetSizer(self.main_sizer)
        self.main_sizer.SetSizeHints(self)

    def make_widgets(self, request, parameters):
        # Function for widget prompting on the panel
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.controls = {"request": wx.StaticText(self.panel, label="Please type the new alias' " + request),
                         "txt": [None]*len(parameters),
                         "tc": [None]*len(parameters)}
        self.main_sizer.Add(self.controls["request"], 1, wx.ALL, 10)
        for i in range(len(parameters)):
            self.input_sizer[i] = wx.BoxSizer(wx.HORIZONTAL)
            self.main_sizer.Add(self.input_sizer[i], 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
            # Text
            self.controls['txt'][i] = wx.StaticText(self.panel, label=parameters[i])
            self.input_sizer[i].Add(self.controls["txt"][i], 0, wx.ALIGN_CENTER | wx.LEFT, 10)
            # Input
            self.controls['tc'][i] = wx.TextCtrl(self.panel)
            self.input_sizer[i].Add(self.controls["tc"][i], 0, wx.ALIGN_CENTER)
        # Ok button
        self.button_ok = wx.Button(self.panel, label="Ok")
        self.main_sizer.Add(self.button_ok, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
        self.button_ok.Bind(wx.EVT_BUTTON, self.carry_on)

    def carry_on(self, event):
        self.cv.set()

Does anyone has any idea?

Thanks, here below there's the complete code.

import wx
from collections import OrderedDict
import threading


############################################################################
class AddMainName(wx.Frame):
    # Class definition for main Name addition
    def __init__(self, parent, title):
        # Constructor
        super().__init__(parent, title=title)
        # Run the name addition
        self.set_alias_name()
        self.Centre()
        self.Show()

    def set_alias_name(self):
        # Function for definition of alias name
        # Panel
        self.panel = wx.Panel(self)
        # Lists of parameters to be typed
        parameters = OrderedDict([("name parts", [
            "Prefix", "Measurement", "Direction", "Item", "Location", "Descriptor", "Frame", "RTorigin"
            ]),
                                  ("SI units", [
                                      "A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"
                                      ]),
                                  ("normal display unit", [
                                      "A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"
                                  ]),
                                  ("other parameters", [
                                      "Meaning", "Orientation Convention", "Contact Person", "Note",
                                  ])])
        # Iterate through parameters
        self.cv = threading.Event()
        for key in parameters:
            self.input_sizer = [None]*len(parameters[key])
            self.cv.clear()
            self.make_widgets(key, parameters[key])
            self.cv.wait()
        # Panel final settings
        self.panel.SetSizer(self.main_sizer)
        self.main_sizer.SetSizeHints(self)

    def make_widgets(self, request, parameters):
        # Function for widget prompting on the panel
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.controls = {"request": wx.StaticText(self.panel, label="Please type the new alias' " + request),
                         "txt": [None]*len(parameters),
                         "tc": [None]*len(parameters)}
        self.main_sizer.Add(self.controls["request"], 1, wx.ALL, 10)
        for i in range(len(parameters)):
            self.input_sizer[i] = wx.BoxSizer(wx.HORIZONTAL)
            self.main_sizer.Add(self.input_sizer[i], 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
            # Text
            self.controls['txt'][i] = wx.StaticText(self.panel, label=parameters[i])
            self.input_sizer[i].Add(self.controls["txt"][i], 0, wx.ALIGN_CENTER | wx.LEFT, 10)
            # Input
            self.controls['tc'][i] = wx.TextCtrl(self.panel)
            self.input_sizer[i].Add(self.controls["tc"][i], 0, wx.ALIGN_CENTER)
        # Ok button
        self.button_ok = wx.Button(self.panel, label="Ok")
        self.main_sizer.Add(self.button_ok, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
        self.button_ok.Bind(wx.EVT_BUTTON, self.carry_on)

    def carry_on(self, event):
        self.cv.set()


############################################################################
class AddCommonName(wx.Frame):
    # Class definition for common name addition
    def __init__(self, parent, title):
        # Constructor
        super().__init__(parent, title=title,
                         size=(400, 150))
        # Run the name addition
        self.set_alias()
        self.Centre()
        self.Show()

    def set_alias(self):
        panel = wx.Panel(self)
        sizer = wx.GridBagSizer(5, 5)
        text1 = wx.StaticText(panel, label="Please type the new alias name.")
        sizer.Add(text1, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM,
                          border=15)
        panel.SetSizer(sizer)


############################################################################
class AliasGUI(wx.Frame):

    def __init__(self, parent, title):
        # Constructor
        super().__init__(parent, title=title)
        # Run first dialog of the GUI
        self.begin()
        self.Centre()
        self.Show()

    def begin(self):
        # Panel initial settings
        panel_start = wx.Panel(self)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        # Alias type selection
        text_start = wx.StaticText(panel_start, label="Please select the type of the new alias.")
        main_sizer.Add(text_start, 1, wx.ALL, 10)
        # Buttons
        buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
        main_sizer.Add(buttons_sizer, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
        # Main name button
        button_main_name = wx.Button(panel_start, label="Main Name")
        buttons_sizer.Add(button_main_name, 0, wx.ALIGN_CENTER | wx.RIGHT, 10)
        button_main_name.Bind(wx.EVT_BUTTON, self.main_name)
        # Common name button
        button_common_name = wx.Button(panel_start, label="Common Name")
        buttons_sizer.Add(button_common_name, 0, wx.ALIGN_CENTER)
        button_common_name.Bind(wx.EVT_BUTTON, self.common_name)
        # Panel final settings
        panel_start.SetSizer(main_sizer)
        main_sizer.SetSizeHints(self)

    @staticmethod
    def main_name(event):
        # Function for main name addition
        frame = AddMainName(None, title="New Main Name")
        frame.Centre()
        frame.Show()

    @staticmethod
    def common_name(event):
        # Function for common name addition
        frame = AddCommonName(None, title="New Common Name")
        frame.Centre()
        frame.Show()


# GUI execution
if __name__ == '__main__':
    app = wx.App()
    AliasGUI(None, title="Aliases Management GUI")
    app.MainLoop()

Solution

  • It's not really clear what you intend to do here. Your Script doesnt crash, it just waits indefinatly on the event at line 40. You did not start any additional thread which could set the event, so the other script would continue.

    Also be aware that wxPython is not thread safe - so if you start adding widgets from inside another thread, you will (really) crash there.

    The setup of the actual frame is not started until your __ init __ function is done. Inside this call you wait for a button press which cannot happen, as the window is yet to be created. So your script waits.

    I would have posted an actual solution to your problem, but as stated above, I do not see what you intend to do here.

    Edit:

    As stated in the comment, I would use a dialog to ask the user for the items.

    Create a Dialog Subclass:

    class getDataDlg(wx.Dialog):
    
        def __init__(self, *args, **kwargs):
            self.parameters = parameters = kwargs.pop('parameters', None)
            request = kwargs.pop('request', None)
            assert parameters is not None
            assert request is not None
    
            wx.Dialog.__init__(self, *args, **kwargs)
    
            self.data = {}
    
            vsizer = wx.BoxSizer(wx.VERTICAL)
            reqLbl = wx.StaticText(self, label="Please type new alias {}".format(request))
            vsizer.Add(reqLbl, 1, wx.ALL, 10)
            self.controls = controls = {}
    
            for item in parameters:
                hsizer = wx.BoxSizer(wx.HORIZONTAL)
                parLbl = wx.StaticText(self, label=item)
                hsizer.Add(parLbl, 0, wx.ALIGN_CENTER | wx.LEFT, 10)
                ctrl = controls[item] = wx.TextCtrl(self)
                hsizer.Add(ctrl, 0, wx.ALIGN_CENTER)
                vsizer.Add(hsizer,  1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
            okBtn = wx.Button(self, id=wx.ID_OK)
            vsizer.Add(okBtn, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
            self.SetSizer(vsizer)
            self.Fit()
            self.Layout()
    
            okBtn.Bind(wx.EVT_BUTTON, self.saveData)
    
        def saveData(self, event):
            for item in self.parameters:
                self.data[item] = self.controls[item].GetValue()
            event.Skip()
    

    Change main_name function to the following:

    def main_name(self, event):
        parameters = OrderedDict([("name parts", ["Prefix", "Measurement", "Direction", "Item", "Location", "Descriptor", "Frame", "RTorigin"]),
                                  ("SI units", ["A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"]),
                                  ("normal display unit", ["A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"]),
                                  ("other parameters", ["Meaning", "Orientation Convention", "Contact Person", "Note",])])
        # Iterate through parameters
        self.cv = threading.Event()
        for itemKey in parameters:
            item = parameters[itemKey]
            dlg = getDataDlg(self, parameters=item, request=itemKey)
            result = dlg.ShowModal()
            if result == wx.ID_OK:
                print("Got Result from Dialog:")
                print(dlg.data)
            dlg.Destroy()  
    

    Michael