Bind menu event handlers in another module in wxPython - python

I'm building a menu for a frame with wxPython (python3).
I want to separate the main.py from the menus.py - have these two separated files so the code is organized into smaller pieces.
I need to be able to pass control back to the main.py from menus.py.
In particular, I need that the handlers for the events I bind for menu items (in menus.py) will reside in main.py, or, alternatively, have the handlers in menus.py, but reference objects in main.py (for example, to Close() the application for the "Exit" menu item.
This is what I have so far, and I tried both ways with no success. How is this achieved?
main.py
import wx
import menus
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "My App Title", size=(1200, 800))
self.panel = wx.Panel(self, wx.ID_ANY)
mainMenu = menus.MainMenu()
mainMenu.build_main_menu(self)
def onExit(self):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
menus.py
from wx import Menu, MenuBar, MenuEvent, MenuItem, Frame, EVT_MENU
class MainMenu(object):
def build_main_menu(self, frame):
self.fileMenu = Menu()
exitMenuItem = self.fileMenu.Append(101, "E&xit", "Close the application")
menuBar = MenuBar()
menuBar.Append(self.fileMenu, "&File")
frame.Bind(EVT_MENU, MainFrame.onExit(frame), exitMenuItem)
frame.SetMenuBar(menuBar)
return self

You are really close, but just not quite there. For this sort of thing, there is no need to wrap the menu creation code inside a class, so I changed that to just a function. Here's menu.py:
from wx import Menu, MenuBar, MenuEvent, MenuItem, Frame, EVT_MENU
def build_main_menu(frame):
fileMenu = Menu()
exitMenuItem = fileMenu.Append(101, "E&xit", "Close the application")
menuBar = MenuBar()
menuBar.Append(fileMenu, "&File")
frame.Bind(EVT_MENU, frame.onExit, exitMenuItem)
frame.SetMenuBar(menuBar)
Note that you need to call the onExit using the frame instance, not by trying to call it via a class (MainFrame) that you haven't even imported. Also you do not call an event handler in the bind operation. It will get called when the button is pressed.
Next I updated your main.py:
import wx
import menus
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "My App Title", size=(1200, 800))
self.panel = wx.Panel(self, wx.ID_ANY)
mainMenu = menus.build_main_menu(self)
def onExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
There are two changes here. First we don't need to create an instance of the menu module's class as there is no class any longer. Secondly event handlers take two arguments: self and event.
Now it works!

Related

How could I successfully run onButton during startup?

import wx
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test")
panel = wx.Panel(self, wx.ID_ANY)
#Button is created; binded to onButton
button = wx.Button(panel, id=wx.ID_ANY, label="Press Me")
button.Bind(wx.EVT_BUTTON, self.onButton)
def onButton(self,EVT_BUTTON):
print("Hello world!")
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
#Runs Button command on startup
MyForm.onButton()
I want onButton() to run at startup, and have it be able to run when the
wx.Button is pressed. Unfortunetly, it comes up with this error:
>TypeError: onButton() missing 2 required positional arguments: 'self' and 'EVT_BUTTON'
It is slightly more difficult. I am guessing you are a beginner to programming. If so, I suggest, you learn some more basics. Doing GUI applications is a bit more advanced topic.
So, firstly, for your wxPython program to run, you must start an event loop, so your program should have something like this:
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
You defined your function onButton with 2 parameters. Therefore you must supply them when calling the function. The first one is instead of the self, and that is the frame. The second is named EVT_BUTTON (and giving the variable this name suggests that you actually do not understand these concepts, and that's the reason why I suggested that you start with studying basics).
So you could call
frame.OnButton(None)
before calling app.MainLoop() and the code will run. But that's probably not enough.

Dialog is not modal when ProgressDialog and Dialog are opened in sequence

I'm running wxPython 4.0.1 msw (phoenix) with Python 3.6.5 on a Windows7 machine, as well as wxPython 2.9.4 with Python 2.7.
I'm observing an issue with a modal dialog, which doesn't block the access to its parent window behind. This only occurs if I run a progress dialog followed by a modal dialog. This behavior is somehow related to custom dialogs. Integrated dialogs like wx.MessageDialog doesn't seem to have this issue.
To isolate the issue, I've written an example. The first two buttons open either the progress or the modal dialog and work properly. The third button opens both dialogs in sequence. In this case the modal functionality of the custom dialog doesn't work and I'm able to access and close the mainframe. Which causes multiple issues.
Dialog is not modal, the main window can be accessed and closed
import wx
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, title='SomeDialog',
style=wx.DEFAULT_DIALOG_STYLE)
self.button_ok = wx.Button(self, wx.ID_OK, size=(120,-1))
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.Add(self.button_ok, 0, wx.ALL|wx.ALIGN_CENTER, 10)
self.SetSizer(hsizer)
self.SetSize(self.BestSize)
self.Layout()
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(400, 400))
self.button_progress = wx.Button(self, -1, 'Show Progress')
self.button_modal = wx.Button(self, -1, 'Show Modal')
self.button_both = wx.Button(self, -1, 'Show Both')
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_progress)
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_modal)
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_both)
sizer = wx.BoxSizer()
sizer.Add(self.button_progress)
sizer.Add(self.button_modal)
sizer.Add(self.button_both)
self.SetSizer(sizer)
def on_button(self, event):
if event.EventObject is self.button_progress:
self._show_progress_dialog()
elif event.EventObject is self.button_modal:
self._show_modal_dialog()
else:
self._show_progress_dialog()
self._show_modal_dialog()
def _show_progress_dialog(self):
max = 10
dlg = wx.ProgressDialog('Progress dialog example', 'Some message',
maximum=max, parent=self,
style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE)
keepGoing = True
count = 0
while keepGoing and count < max:
count += 1
wx.MilliSleep(250)
wx.Yield()
(keepGoing, skip) = dlg.Update(count)
dlg.Destroy()
def _show_modal_dialog(self):
with SomeDialog(self) as dlg:
dlg.CenterOnParent()
dlg.ShowModal()
if __name__ == '__main__':
app = wx.App()
frame = TestFrame()
frame.Show()
app.MainLoop()
In case this is an issue in the wxpython framework and not an issue with my implementation, it would be great if someone could provide me a workaround to show such dialogs in sequence.
This looks like a bug to me. I'm not sure why its happening, but one workaround would be to use wx.CallLater
changing _show_modal_dialog to:
def _show_modal_dialog(self):
def _make_dialog():
with SomeDialog(self) as dlg:
dlg.CenterOnParent()
dlg.ShowModal()
wx.CallLater(50, _make_dialog) # 50 mils is arbitrary
Seems to resolve the issue of the dialog not acting modalish. The problem with this workaround is that it will be non-blocking, meaning that any code that has to wait for the dialog to return has to be moved into the dialog class or passed to the dialog as a callback.
In the meantime I found a workaround myself which I like to share.
I added a handler catching the windows close event.
class TestFrame(wx.Frame):
def __init__(self):
#...
self.Bind(wx.EVT_CLOSE, self.on_close)
This event function checks if some child dialog is open and modal and performs a veto.
def on_close(self, event):
# In case any modal dialog is open, prevent the frame from closing.
for children in (c for c in self.Children if isinstance(c, wx.Dialog)):
if children.IsModal():
event.Veto()
return
event.Skip()
This is also only a workaround, but I seems to work for my use cases.

Python shows wx window,but menu bar is not displayed. Why?

I've installed wxPython on my mac and tried this program
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(200,100))
self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
self.Show(True)
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.MainLoop()
I started python2.7 to run it, it prompts a window, but it doesn't show any menubar, out of my expectation.
Did I miss anything in my program, or I need any extra python/wx configurations?

Using accelerator table without menuitem

I have a wxpython desktop application and I am using python 2.7 and wxpython 2.8.
I know how to add an accelerator table to a menuitem but I would like to fire an event when a user press a certain combination of keys without having a menuitem.
The user could have the focus on any field in my UI but when he press (for instance) CTRL-L an event should be fired. How to do this ?
Thanks for any help
You always need to bind your accelerator table items to wx.EVT_MENU, but wxPython doesn't require that you use a menu item object. Here's a simple example:
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
randomId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onKeyCombo, id=randomId)
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('Q'), randomId )])
self.SetAcceleratorTable(accel_tbl)
text = wx.TextCtrl(panel)
text.SetFocus()
#----------------------------------------------------------------------
def onKeyCombo(self, event):
""""""
print "You pressed CTRL+Q!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
In this example, we just create a random id, bind that id to an event handler and then create an accelerator that will fire that handler, which in this case is CTRL+Q. To make things more interesting, I added a text control widget and set the focus to that. Then if you press CTRL+Q, you should see the event handler fire and some text appear in your console window.
You can read more about accelerators here:
http://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/

wxPython: make variables available to an imported class?

I have written a wxPython GUI which assigns some variables upon a button click.
def OnGo(self, event):
inputdatadirectory = self.txtbx1.GetValue()
outputsavedirectory = self.txtbx2.GetValue()
mcadata = self.txtbx3.GetValue()
current_dir = os.getcwd()
execfile(current_dir+"\\aEDXD.py")
I then run execfile, and the executed file imports and runs a class from another file.
How can I make the variables I've defined through my GUI available to the imported class?
Yes you can, although it will probably be difficult to debug. Here is a silly example:
import wx
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Test")
panel = wx.Panel(self)
btn = wx.Button(panel, label="Go")
btn.Bind(wx.EVT_BUTTON, self.onGo)
self.Show()
#----------------------------------------------------------------------
def onGo(self, event):
""""""
foobar = "This is a test!"
execfile("foo.py")
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
And here is the foo.py file.
print foobar
If you run the wxPython script, it will execute foo.py which has access to the locals and globals in the wxPython script. You won't be able to run foo.py by itself though. See the following:
Execute a file with arguments in Python shell
https://docs.python.org/2/library/functions.html#execfile

Categories

Resources