Automating a wxPython main menu setup? - python

I am trying to find a way to condense and automate the construction of a main menu (underneath the title bar, with file, edit, help, etc.) in wxPython.
Writing out each and every menu item is direct, but I notice I repeat myself a lot, between Appending, sorting IDs, etc. Followed by other unique pits like if I want to add an icon to a specific menu, or if I have submenus and they may have submenus, etc. Without one consistent way to itemize everything, simply by adding information to maybe a list or dictionary, or a combo of the two, my wx.Frame object will get very dense.
I can't see a clean an organized way of doing that, short of a 3-dimensional array. And even then, I don't know how to organize that 3D array uniformly so every item is ready to go.
Here is what I have so far (pardon any indentation errors; it works fine on me):
class frameMain(wx.Frame):
"""The main application frame."""
def __init__(self,
parent=None,
id=-1,
title='TITLE',
pos=wx.DefaultPosition,
size=wx.Size(550, 400),
style=wx.DEFAULT_FRAME_STYLE):
"""Initialize the Main frame structure."""
wx.Frame.__init__(self, parent, id, title, pos, size, style)
self.Center()
self.CreateStatusBar()
self.buildMainMenu()
def buildMainMenu(self):
"""Creates the main menu at the top of the screen."""
MainMenu = wx.MenuBar()
# Establish menu item IDs.
menuID_File = ['exit']
menuID_Help = ['about']
menuID_ALL = [menuID_File,
menuID_Help]
# Make a dictionary of the menu item IDs.
self.menuID = {}
for eachmenu in menuID_ALL:
for eachitem in eachmenu:
self.menuID[eachitem] = wx.NewId()
# Create the menus.
MM_File = wx.Menu()
FILE = {}
MM_File.AppendSeparator()
FILE['exit'] = MM_File.Append(self.menuID['exit'],
'Exit',
'Exit application.')
self.Bind(wx.EVT_MENU, self.onExit, FILE['exit'])
MainMenu.Append(MM_File, 'File')
MM_Help = wx.Menu()
HELP = {}
MM_Help.AppendSeparator()
HELP['about'] = MM_Help.Append(self.menuID['about'],
'About',
'About the application.')
self.Bind(wx.EVT_MENU, self.onAbout, HELP['about'])
MainMenu.Append(MM_Help, 'Help')
# Install the Main Menu.
self.SetMenuBar(MainMenu)
I tried using the list-to-dictionary thing to make it so I don't need a specific index number when referring to an ID, just write in a keyword and it gets the ID. I write it once, and it's applied across the rest of the function.
Notice how I have to make a whole new variable and repeat itself, like MM_File, MM_Edit, MM_Help, and each time I do I put in similar information to append and bind. And keep in mind, some of the menus may need Separators, or have menus in menus, or I may want to use a sprite next to any of these menu items, so I'm trying to figure how to organize my arrays to do that.
What is the appropriate way to organize this into a concise system so it doesn't bloat this class?

There are several approaches you can take with this. You can put the menu generation code into a helper function if you like. Something like this should work:
def menu_helper(self, menu, menu_id, name, help, handler, sep=True):
menu_obj = wx.Menu()
if sep:
menu_obj.AppendSeparator()
menu_item = menu_obj.Append(menu_id, name, help)
self.Bind(wx.EVT_MENU, handler, menu_item)
self.MainMenu.Append(menu_obj, menu)
Here's a complete example:
import wx
class frameMain(wx.Frame):
"""The main application frame."""
def __init__(self,
parent=None,
id=-1,
title='TITLE',
pos=wx.DefaultPosition,
size=wx.Size(550, 400),
style=wx.DEFAULT_FRAME_STYLE):
"""Initialize the Main frame structure."""
wx.Frame.__init__(self, parent, id, title, pos, size, style)
self.Center()
self.CreateStatusBar()
self.buildMainMenu()
def buildMainMenu(self):
"""Creates the main menu at the top of the screen."""
self.MainMenu = wx.MenuBar()
# Establish menu item IDs.
menuID_File = 'exit'
menuID_Help = 'about'
menuID_ALL = [menuID_File,
menuID_Help]
# Make a dictionary of the menu item IDs.
self.menuID = {item: wx.NewId() for item in menuID_ALL}
# Create the menus.
self.menu_helper('File', self.menuID['exit'], 'Exit',
'Exit application', self.onExit)
self.menu_helper('Help', self.menuID['about'], 'About',
'About the application.', self.onAbout)
# Install the Main Menu.
self.SetMenuBar(self.MainMenu)
def menu_helper(self, menu, menu_id, name, help, handler, sep=True):
"""
"""
menu_obj = wx.Menu()
if sep:
menu_obj.AppendSeparator()
menu_item = menu_obj.Append(menu_id, name, help)
self.Bind(wx.EVT_MENU, handler, menu_item)
self.MainMenu.Append(menu_obj, menu)
#----------------------------------------------------------------------
def onExit(self, event):
pass
def onAbout(self, event):
pass
if __name__ == '__main__':
app = wx.App(False)
frame = frameMain()
frame.Show()
app.MainLoop()
Or you could create a class that handles all the menu creation. You could also create a config file that has all this information in it that you read to create your menu. Another alternative would be to use XRC, although I personally find that a bit limiting.

Related

Can I get clickable menu item with submenu in wxPython?

I'm building system tray application with wxPython. It's easy to understand and works great so far, but I'm trying to get a non-standard behavior. I would like menu item to be clickable, but have a submenu at the same time. I know it's weird, but it would meet my needs the best. I want to execute default action on click, but have more options in submenu. The below code creates items just fine without submenu.
class TrackerMenuItem(wx.MenuItem):
#staticmethod
def create_and_bind(parent_menu, label, handler):
menu_item = TrackerMenuItem(parent_menu, -1, label)
parent_menu.Bind(wx.EVT_MENU, handler, id=menu_item.GetId())
parent_menu.Append(menu_item)
return menu_item
But if I try to add submenu, binding stops working, and my item is just a parent for submenu (this is just a generic code for tests)
class TrackerMenuItem(wx.MenuItem):
#staticmethod
def create_and_bind(parent_menu, label, handler):
menu_item = TrackerMenuItem(parent_menu, -1, label)
submenu = wx.Menu()
submenuitem = wx.MenuItem(submenu, wx.ID_ANY, 'Submenu Item')
submenu.Append(submenuitem)
menu_item.SetSubMenu(submenu)
parent_menu.Bind(wx.EVT_MENU, handler, id=menu_item.GetId())
parent_menu.Append(menu_item)
return menu_item
Is it possible to work it around in a reasonable manner?

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/

How do I programmatically pull down a wx.Menu in wxPython

If I have a wx.Menu (in a wx.MenuBar, at the top of a frame, like normal) - how can I cause that menu to drop down and take focus, without clicking on it. I want the behavior to be as if the user had pressed the keyboard accelerator shortcut for that menu (so Alt+F for example, for the &File menu)
Try with wx.PostEvent:
event = wx.MenuEvent(wx.wxEVT_LEFT_DOWN, menuitem.GetId(), menu)
wx.PostEvent(frame, event)
Other wx mouse events: http://www.wxpython.org/docs/api/wx.MouseEvent-class.html
Found in google groups thread
I had the same requirement and found the simple way of using the PopupMenu function. It is not called from the menu object but from the parent of the menu (the window, frame, etc..)
To make sure that the menu appears at a specific position, regardless of your mouse, supply to the PopupMenu function a position parameter as well.
In the example bellow, I turned a platebtn that was opening the menu only when clicked in the right side, in the small area of the down arrow, into a button that opens the same menu in the same way when you click it anywhere on its surface.
Example:
import wx
import wx.lib.platebtn as platebtn
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wx.Frame.__init__(self, parent, ID, title, size=(300, 250))
wx.Panel(self,-1, style=wx.SUNKEN_BORDER)
droparrow = platebtn.PB_STYLE_DROPARROW | platebtn.PB_STYLE_SQUARE | platebtn.PB_STYLE_GRADIENT
self.btn1 = platebtn.PlateButton(self, wx.ID_ANY, label=" File ", style=droparrow)
self.btn1.SetPressColor(wx.LIGHT_GREY)
self.menu1 = wx.Menu()
self.menu1.Append(1, "New")
self.menu1.Append(2, "Open")
self.menu1.Append(3, "Exit")
sm = wx.Menu()
sm.Append(8, "sub item 1")
sm.Append(9, "sub item 1")
self.menu1.AppendMenu(7, "Test Submenu", sm)
self.btn1.SetMenu(self.menu1)
self.Bind(wx.EVT_BUTTON, self.OnFile, self.btn1)
def OnFile(self, event):
self.btn1.PopupMenu(self.menu1, pos=(1, self.btn1.GetSize()[1]))
app = wx.App(False)
frame = MyFrame(None, -1, "PopupMenu example")
frame.Show()
app.MainLoop()
To define accelerators for menu in your program,Understand through the given example
example:
file_menu=wx.Menu()
menubar=wx.MenuBar()
menubar.Append(file_menu,"&File")
self.SetMenuBar(menubar)
Now we can access the File menu (here) by pressing ALT+F.
If we have other menus too,on pressing ALT ,it will point to the first Letter of menu bar,from which you can press the next key according to the name of menu_item.

wxPython menu text alignment

I'm developing a wxPython GUI, and would like to right-align shortcut text. Obviously one can cludge this using tab characters, but I'd prefer to do it natively if there is a way to.
The default menu item creation is thus:
menu = wx.Menu()
item_id = 1
item_name = 'My menu item'
help_text = 'Clicking this does something interesting.'
item = menu.Append(item_id, item_name, help_text)
I'll be expanding on this with shortcuts, so if I were using tabs, it would be something like:
item_name = 'My menu item\t\tCtrl+Alt+H'
However, that involves a lot of manual \t entries to make sure everything lines up, and anytime a menu item changes name or another item is added, they would all potentially need to be updated. Is there any way around this, e.g. a class method I'm not seeing to automatically associate the keybinding to the menu item?
Edit: I know that when passing text like &My menu item, it does something automatically with the keybinding associated with the ID specified if there is a definition associated with that ID in the accelerators table, correct?
I went digging through a couple other application's code to find the answer. It turns out the default behavior with \t doesn't do what it looks like it would do (i.e. insert a tab character), but is sensibly interpreted by the toolkit as doing precisely what I wanted to do. Thus, the way to right-align a short cut is simple: create it with the text you desire, followed by \t<shortcut> (rather as I had above). In the example code I pasted above, if I wanted my shortcut to be Ctrl + T, it should therefore be thus:
menu = wx.Menu()
item_id = 1
item_name = 'My menu item\tCtrl+T'
help_text = 'Clicking this does something interesting.'
item = menu.Append(item_id, item_name, help_text)
Edit: updated the following section based on Mike Driscoll's very helpful answer.
Note that this creates the shortcut binding (wxPython picks that up), but it doesn't make it selectable using e.g. the Alt key on Windows.
You can associate the Alt key to quickly open the menu and navigate to it by using the ampersand in your item_name text, but you'll still need to associate the desired keybinding manually via the AcceleratorTable:
menu = wx.Menu()
item_id = 1
# Ctrl+T is bound to the keybinding
accelerator_table = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('T'), item_id)])
self.setAcceleratorTable(accelerator_table)
# Ctrl+T is not included, but the menu item can be accessed via Alt key
item_name = '&My menu item'
help_text = 'Clicking this does something interesting.'
item = menu.Append(item_id, item_name, help_text)
This, I imagine, would actually be the preferred pattern, as then anywhere that the item_id was referenced, the shortcut could be referenced automatically. This would also make for seamless updates.
While Chris is right about "\t" indenting the keybinding correctly in the menu, I don't really see what he means by automatically associating anything using an ampersand. The ampersand (&) DOES allow the used to type ALT to get the File menu to open and then if you type another letter that has had the ampersand applied to it, it will jump to that menu item, but the & does not connect the menu item to the accelerator table. That is done via the menu item's ID.
See the following code:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "wx.Menu Tutorial")
self.panel = wx.Panel(self, wx.ID_ANY)
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
exitId = wx.NewId()
exitMenuItem = fileMenu.Append(exitId, "&Exit/tCtrl+X",
"Exit the application")
self.Bind(wx.EVT_MENU, self.onExit, id=exitId )
menuBar.Append(fileMenu, "&File")
self.SetMenuBar(menuBar)
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('X'), exitId )])
self.SetAcceleratorTable(accel_tbl)
#----------------------------------------------------------------------
def onExit(self, event):
""""""
self.Close()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
Note that the exitId is used to create the menu item, bind the menu item to EVT_MENU and finally, it is used in the AcceleratorTable so the user can use the shortcut key.
Here are a few references that might be helpful:
http://www.blog.pythonlibrary.org/2008/07/02/wxpython-working-with-menus-toolbars-and-accelerators/
http://wiki.wxpython.org/WorkingWithMenus
http://zetcode.com/wxpython/menustoolbars/
http://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/

Generate wxPython widget groups from lists

This is one of those questions that is really frustrating for me to ask because I'm sure the answer is out there, I just haven't been able to word the searches correctly. Basically I am new to GUI programming (done a decent amount of embedded C/C++) and learning wxPython to start out.
I'm making an app to read and write to a config file. So I have a StaticText to display the name of the parameter to be read/written, a TextCtrl to display the value and allow user input, and then a Get button and a Set button. I'll call all of these together a "group" of widgets. For this app obviously this group will be repeated several times. Rather than write and maintain all that code by hand, I thought it would be easier to simply have a list of the config parameters I want to be able to edit, and then iterate through the list and generate an instance of this "group" of widgets for each item in the list. I made it work except for one thing: I had to bind all of the Get buttons to the same function. Same thing with the Set buttons. Is there any way from within those functions to know which Get or Set button was pushed, and thus which parameter to find and edit in the config file? I'm sure there's a way to do this with parent or IDs or something but I'm just too new to OOP.
I assume that the get button reads the parameter value from the config file and displays the value.
Why do you need one get button for each parameter? I would have just one get button for tham all. When the user clicks the get button, every parameter is read from the file and all the displays are updated.
A similar approach for the set button. One set button for them all - when the button is pressed every parameter that has a new value entered is updated in the config file. Parameters where the user has not entered a new value remain with their previous values.
This scheme is easier to code, and easier for the user also.
However, I really suggest you look at the wxPropertyGrid widget. This has the potential to make your life a lot easier! Here's a screenshot showing one in action
In your button's event handler, you can do something like this:
btn = event.GetEventObject()
btn.GetId()
btn.GetName()
Then you just use an If statement to decide what to do based on whatever info you want to use. Note: you can set the button's name when you create the button like this:
setBtn = wx.Button(self, label="Set", name="SetX")
You might find this article on wxPython and ConfigObj helpful too: http://www.blog.pythonlibrary.org/2010/01/17/configobj-wxpython-geek-happiness/
You can derive your own class from the wx.Button and add one or more attributes to it to make the button remember any information you want.
You can use this stored information during a callback function call. Something like:
import wx
L = [("1", "One"), ("2", "Two"), ("3", "Three")]
# =====================================================================
class MemoryButton(wx.Button):
def __init__(self, memory, *args, **kwargs):
wx.Button.__init__(self, *args, **kwargs)
self.memory = memory
# =====================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.buttons = []
for description in L:
button = MemoryButton(memory=description[1], parent=self.panel,
label=description[0])
button.Bind(wx.EVT_BUTTON, self.OnMemoryButton)
self.buttons.append(button)
self.sizer = wx.BoxSizer()
for button in self.buttons:
self.sizer.Add(button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# -----------------------------------------------------------------
def OnMemoryButton(self, e):
print("Clicked '%s'" % e.GetEventObject().memory)
# =====================================================================
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
or alternatively:
import wx
L = [("1", "One"), ("2", "Two"), ("3", "Three")]
# =====================================================================
class MemoryButton(wx.Button):
def __init__(self, memory, *args, **kwargs):
wx.Button.__init__(self, *args, **kwargs)
self.memory = memory
def OnButton(self, e):
print("Clicked '%s'" % self.memory)
# =====================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.buttons = []
for description in L:
button = MemoryButton(memory=description[1], parent=self.panel,
label=description[0])
button.Bind(wx.EVT_BUTTON, button.OnButton)
self.buttons.append(button)
self.sizer = wx.BoxSizer()
for button in self.buttons:
self.sizer.Add(button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# =====================================================================
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Categories

Resources