Binding events to a menu option

Hi,
I’m attempting to bind an event to the Open option in my menu. Despite various attempts, the following code never runs:

item = wxglade_tmp_menu.Append(wx.ID_ANY, "&Open\tCtrl+O", "")

        self.Bind(wx.EVT_MENU, self.OnFileOpen, id=item.GetId())

A bit long, but here is the event handler:

def OnFileOpen(self, event):  

        if self.contentNotSaved:

            if wx.MessageBox("Current content has not been saved! Proceed?", "Please confirm",

                wx.ICON_QUESTION | wx.YES_NO, self) == wx.NO:

                    return

            with wx.FileDialog(self.Myframe, "Open MXP file", wildcard="MXP files (*.MXP)|*.mxp",

            style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:

                if fileDialog.ShowModal() == wx.ID_CANCEL:

                    return     

        pathname = fileDialog.GetPath()

        try:

            with open(pathname, 'r') as file:

                self.LoadTree(file)

        except IOError:

            wx.LogError("Cannot open file '%s'." % file)

I’d be extremely grateful for any suggestions as to how I can find out what’s going on with this code,
David.

At first glance, it seems like it should work. I would suggest trying to produce a complete runnable example that exhibits the problem. That will either help you spot the problem or help us help you. :slight_smile:

OK, I’ve isolated the code, and generated an application which just has an open button, here goes:

import wx

class MyFrame(wx.Frame):

    def __init__(self, *args, **kwds):

        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE

        wx.Frame.__init__(self, *args, **kwds)

        self.SetSize((400, 300))

        self.SetTitle("OpenButton")

        self.panel_1 = wx.Panel(self, wx.ID_ANY)

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        self.button_1 = wx.Button(self.panel_1, wx.ID_ANY, "Open")

        self.button.Bind(wx.EVT_BUTTON, self.OnFileOpen)

        sizer_1.Add(self.button_1, 0, 0, 0)

        self.panel_1.SetSizer(sizer_1)

        self.Layout()

        def OnFileOpen(self, event):  # wxGlade: MyFrame.<event_handler>

            if self.contentNotSaved:

                if wx.MessageBox("Current content has not been saved! Proceed?", "Please confirm",

                    wx.ICON_QUESTION | wx.YES_NO, self) == wx.NO:

                        return

                with wx.FileDialog(self.Myframe, "Open MXP file", wildcard="MXP files (*.MXP)|*.mxp",

                style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:

                    if fileDialog.ShowModal() == wx.ID_CANCEL:

                        return     

            pathname = fileDialog.GetPath()

            try:

                with open(pathname, 'r') as file:

                    self.LoadTree(file)

            except IOError:

                wx.LogError("Cannot open file '%s'." % file)

        # end wxGlade

# end of class MyFrame

class MyApp(wx.App):

    def OnInit(self):

        self.frame = MyFrame(None, wx.ID_ANY, "")

        self.SetTopWindow(self.frame)

        self.frame.Show()

        return True

# end of class MyApp

if __name__ == "__main__":

    app = MyApp(0)

    app.MainLoop()

Thanks for any thoughts,
David.

Well, I don’t see anything menu related in your last post.

When you call self.Bind(wx.EVT_MENU, is self the frame with the menu? Events will not propagate from one frame to another.

Some of your code seems to be generated by wxGlade. This should not create invalid menu bindings. If it does, please submit a ticket at the bug tracker (*).
When you define a menu item in wxGlade with the handler ‘OnFileOpen’, a method stub should be created for the containing frame. For more complex things, you may use a lambda function.

P.S.: I think wxGlade does not yet support lambda handlers for events.

(*) https://github.com/wxGlade/wxGlade/issues

Just for the record, that line can be written like this, saving a few keystrokes:

self.Bind(wx.EVT_MENU, self.OnFileOpen, item)

The third parameter (source) can be anything with a GetId method, and it will call the GetId method for you when building the binding object.

I also have the same question about the problem as @DietmarSchwertberger, what is self in that line?

That’s a very good question. Self, I think, refers to the frame in which the menu resides.
You’re right, the original post was generated by WXGlade. The second post was my stripped down code containing a frame with a button which should have run OnFileOpen,
Thanks,

Thanks for pointing this out. wxGlade will now generate the simpler version. Also, lambda event handlers are now supported for menu events.

OK, so perhaps I’m misunderstanding.
So here’s the question in a more succinct form:
What is the best way to invoke my OnFileOpen code.
There’s another button in this program which invokes another dialog outside the frame, i.e. hide the frame, show the dialog, and depending on the response show the refreshed frame. So the answer to this question will be quite important,
Thanks,
David.

Don’t hide the frame. That’s very non-standard.
Just open the dialog as modal dialog. This will prevent events and refreshes for the frame anyway.
Depending on the user choice, update your frame.

The master branch of wxGlade contains a dialog example. I have just extended this to open the dialog from either a button or the menu:
https://github.com/wxGlade/wxGlade/tree/master/examples/dialog

For wxGlade release 1.0 I hope I will find time to extend the documentation on dialog usage.

Regards,
Dietmar

1 Like

Dietmar,
Many thanks for your time on changing this, and providing a clear example of how this should be done.
Now I’ve found some questions on which I can do research independently. There are various tutorials out there on event handling, and I’m intending to read them so that my understanding will be clearer,
Many thanks,
David.

Please provide feedback via the wxGlade issue tracker or the mailing list if you think that the documentation could be improved:

http://wxglade.sourceforge.net/docs/source_code.html#code-for-event-handlers

Hi,
Despite Dietmar’s example working fine. I’ve applied it to my code, and it still isn’t working:

item = wxglade_tmp_menu.Append(wx.ID_ANY, "&Add node", "")

        self.Bind(wx.EVT_MENU, self.OnTreeAddNode, item)

Then

def OnTreeAddNode(self, event):  # wxGlade: MyFrame.<event_handler>

        with MyDialog(self) as dlg:

            dlg.text_ctrl_1.SetValue("")

            # show as modal dialog

            if dlg.ShowModal() == wx.ID_OK:

                 leaf=dlg.text_ctrl_1.GetValue()

                 AddToTree(self,leaf)

Missing something very important here,
Thanks,
David.

Then you probably need to strip down your code and post a runnable sample here. From the fragments it’s impossible to find the root cause.

Success! of a sort. Code now works, but when activating OK or Cancel buttons, nothing happens, i.e. I don’t go back to the original form.
Do I need to associate an event handler to the OK or Cancel buttons in the dialog?
Thanks,
David.

No, but you need to be sure that the OK button has actually the ID wx.ID_OK.
In wxGlade, activate Properties -> Widget -> Stockitem -> OK.

The code should look like this:

        self.button_2 = wx.Button(self, wx.ID_OK, "")
        self.button_2.SetDefault()

Same for CANCEL.
Regards,
Dietmar

P.S.: For the OK button also activate Properties -> Widget -> Default such that Enter will act as OK.

Hi! I’ve tried to glance an eye to your code, I don’t know exactly your target, at least, I’ve succeded to bind a buttonEvent as you wanted. but for that I’ve modified a litle bit your code.

import wx

class MyFrame(wx.Frame):

    def __init__(self, *args, **kwds):

        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE

        wx.Frame.__init__(self, *args, **kwds)

        self.contentNotSaved = True

        self.SetSize((400, 300))

        self.SetTitle("OpenButton")

        self.panel_1 = wx.Panel(self, wx.ID_ANY)

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        self.button_1 = wx.Button(self.panel_1, wx.ID_ANY, "Open")

        self.button_1.Bind(wx.EVT_BUTTON, self.OnFileOpen)

        sizer_1.Add(self.button_1, 0, 0, 0)

        self.panel_1.SetSizer(sizer_1)

        self.Layout()



    def OnFileOpen(self, event):  



        if self.contentNotSaved:

            msg = wx.MessageDialog(self, "Current content has not been saved! Proceed?", "Please confirm",

               style = wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT)
            response =  msg.ShowModal()
            if response == wx.ID_YES:
                file_path = wx.FileDialog(self)
                if file_path.ShowModal()== wx.ID_OK :
                    print("Fullpath :",file_path.GetPath())
                    print("filename :",file_path.GetFilename())
                    print("folder path :",file_path.GetDirectory())
                file_path.Destroy()
            else:
                return

        #     with wx.FileDialog(self.Myframe, "Open MXP file", wildcard="MXP files (*.MXP)|*.mxp",

        #     style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:

        #         if fileDialog.ShowModal() == wx.ID_CANCEL:

        #             return     

        # pathname = fileDialog.GetPath()

        # try:

        #     with open(pathname, 'r') as file:

        #         self.LoadTree(file)

        # except IOError:

        #     wx.LogError("Cannot open file '%s'." % file)

        # end wxGlade

# end of class MyFrame

# class MyApp(wx.App):

#     def OnInit(self):

#         self.frame = MyFrame(None, wx.ID_ANY, "")

#         self.SetTopWindow(self.frame)

#         self.frame.Show()

#         return True

# end of class MyApp

if __name__ == "__main__":

    app = wx.App()
    frame = MyFrame(None, wx.ID_ANY, "")

    app.SetTopWindow(frame)

    frame.Show()

    app.MainLoop()

Well! I hope it will help you.
An advice! you’d better lean how to make GUI with wx.python only. at the biginnig it’s dificult, but once you spend more time on it, yo’ll see that everything is now easy.

Marley,

I’m rapidly coming to the conclusion that it would be best to generate code myself in the first instance. I find WXGlade extremely useful, but by learning the correct way to do this, it’ll give me a better understanding when things go wrong,

Thanks.

I’m currently extending wxGlade with some options for some windows and widgets. E.g. when adding a Dialog, there will be an option to add OK and Cancel buttons automatically.

Dietmar,

Very happy to test it out,

David.