0

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()

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

5 Comments

thanks for your answer and apologize for my poor language. Indeed the script waits indefinitely and at a certain point does not answer, but it does not crash. What I want is different windows popping up one after the other, once an "ok" button is pressed (and the current window should also close). I did not know that the setup of the frame is not started until my __init__ function is done. What I have done now is to move the for loop in the main_name function, so that a new frame is generated for each set of inputs. Putting also the wait there the script still waits indefinitely.
Personally I would use a Modal Dialog to ask the user for Information and not a frame. The rest of the script waits automatically until the dialog is done and you can get the info you want from there.
Thanks for your suggestion. The point is that I would like to use wxPython since the set of inputs requested to the user is quite various. First I should have some windows asking for some texts, then other windows having a scroll list and other text. I am not managing to find a solution to recursively pop up the frames and close them. The problem appears to be the fact that this is done inside a for cycle. So there is the main script running, which produces all the frames at the same time. Otherwise if I use wait at any point, I always end up waiting for an unlimited time.
I added an example above to show how I would do it. Maybe that helps. You will get the result of the dialog as dlg.data after the dialog is closed.
Thanks @Lokla, your example above gave me the solution to what I was looking for.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.