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/
Related
I have a wx.menubar in my application, and I want to set focus to it by pressing a key, for example, "alt+o", and have as a result this:
I would like to know if it is possible? or if an event in wxpython to do it exists. Thank you. P.D: I have tried to use the SetFocus () method which extends from wx.Window but it doesn't work
You could use accelerators
For example:
# ...
# Menu Bar
self.menubar = wx.MenuBar()
file_menu = wx.Menu()
item = file_menu.Append(wx.ID_EXIT, "&Salir", "")
self.menubar.Append(file_menu, "Archiv&o")
self.SetMenuBar(self.menubar)
#...
You should be able to select the "Archivo" menu using "Alt + o".
The problem is simple but i still coudn't figure it out, when i run the code, the Ampersands don't appear in Menu Items. Why?!
I'm using Python 3.7.7 and wxPython 4.1.0, on Thonny v3.2.7.
import wx
def OnQuit(e):
frame.Close()
app = wx.App()
frame = wx.Frame(None, -1, "wxPython Menu")
frame.SetSize(400, 300)
submenu1 = wx.Menu()
submenu1.Append(121, "Import Newsfeed List")
submenu1.Append(122, "Import Bookmarks")
submenu1.Append(123, "Import Mail")
menu1 = wx.Menu()
menu1.Append(10, "Open")
menu1.Append(11, "Save")
menu1.AppendSeparator()
menu1.AppendSubMenu(submenu1, "Import")
menu1.Append(13, "No Exit")
menu2 = wx.Menu()
menu2.Append(20, "Cut")
menu2.Append(21, "Copy")
menu2.Append(22, "Paste")
menu2.Enable(20, False)
menu3 = wx.Menu()
item = wx.MenuItem(menu3, 30, "&Quit\tCtrl+Q")
item.SetBitmap(wx.Bitmap("quit.png"))
menu3.Append(item)
menu3.Bind(wx.EVT_MENU, OnQuit, id = 30)
menuBar = wx.MenuBar()
menuBar.Append(menu1, "&File")
menuBar.Append(menu2, "&Edit")
menuBar.Append(menu3, "&Extras")
frame.SetMenuBar(menuBar)
frame.Centre()
frame.Show()
app.MainLoop()
SetItemLabel(self, label)
Sets the label associated with the menu item.
Note that if the ID of this menu item corresponds to a stock ID, then it is not necessary to specify a label: wxWidgets will automatically use the stock item > label associated with that ID. See the constructor for more info.
The label string for the normal menu items (not separators) may include the
accelerator which can be used to activate the menu item from keyboard. An accelerator key can be specified using the ampersand & character. In order to embed an ampersand character in the menu item text, the ampersand must be doubled. **
https://docs.wxpython.org/wx.MenuItem.html#wx-menuitem
The ampersand (&) is traditionally used as an accelerator on menu items.
If you press the ALT button your menus will (if defined with accelerators i.e. &File where the & next to F means pressing F will open the File menu) display with the accelerator letter underlined.
Press F and the File menu opens.
If menu items in the File menu have accelerators, they too will have a letter underlined, pressing that letter will select that menu item.
If you require an actual ampersand (&) without special meaning e.g. Close & Quit, you need to double the ampersand i.e. the items label should created as Close && Quit or &Close && Quit if the menu should not only display as Close & Quit but also respond when the user presses the letter C.
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.
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/
In my application i have text control.
I want my text ctrl should be read only but when some one right click on this he is able to copy the value from that ctrl and he can paste that value in other text control.
If i made my text control read only with wx.TE_READONLY then copy/paste is not working.
Is there any requirement to handle other type of wx event or i have to set more type of wx style flags while creating the text control object.
Thanks in advance.
Hmm Im on windows 7 and don't have any problem copying from a textCtrl that has the wx.READ_ONLY style flag set.
You said you wanted to paste into another textCtrl -obviously you can't have the wx.READ_ONLY style flag set in the textCtrl you want to be able to paste into.
Try out the demo below, you should be able to copy from the textCtrl on the left(which is read only) to the one on the right(which doesn't have the read only style set).
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, id=-1):
wx.Frame.__init__(self,parent,id, size=(200,200))
self.panel = wx.Panel(self,wx.ID_ANY)
bsizer = wx.BoxSizer()
read_only_txtCtrl = wx.TextCtrl(self,-1,
"This textCtrl is read only",
style=wx.TE_MULTILINE|wx.TE_READONLY)
editable_txtCtrl = wx.TextCtrl(self,-1,
"This textCtrl is editable",
style=wx.TE_MULTILINE)
bsizer.Add(read_only_txtCtrl, 1, wx.EXPAND)
bsizer.Add(editable_txtCtrl, 1, wx.EXPAND)
self.SetSizerAndFit(bsizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MainWindow(None)
frame.SetSize((200,200))
frame.Show()
app.MainLoop()
We should bind wx.EVT_TEXT_COPY and wx.EVT_TEXT_PASTE with text control.
one can copy and paste data from text ctrl although text ctrl is read only mode.
Actually I had the same challenge. I needed a textbox where users could paste information (or open -> read file via a menu). The app would then analyze the info for correctness. But I didn't want to allow editing in the textbox - it would suggest that you could correct in the same textbox, while the app was for analysis only. Yeah, requirements, I know.
Weird thing was, I could make a readonly TextCtrl under MacOSX that would allow pasting (but not editing), but not on Windows.
In order to support both, I ended up creating a read/writesuper(MyWin, self).init(None, size=(800,600)) textbox (to allow pasting under windows) and binding wx.EVT_TEXT events to it (apart from wx.EVT_TEXT_PASTE for the obvious pasting). The EVT_TEXT handler that's triggered when the textctrl's contents change, simply shows a dialog that you aren't allowed to do this.
In the app a boolean this.painted plays the following role: when this.painted is true, then modifications of the textctrl aren't allowed. So, the onpaste handler first sets this.painted to false, then modifies the TextCtrl's contents, then sets this.painted to true (otherwise the dialog alert would also go off during the paste event, which I wanted to allow). Unfortunately when the EVT_TEXT handler goes off, the window's contents have already been modified by a user hitting some key. Therefore the app also needs a backup buffer to put back into the TextCtrl when such editing has been detected.
According to the Python docs, TextCtrl.ChangeValue() instead of SetValue() should not trigger the EVT_TEXT event (which would be handy) but I couldn't get that to work, might be my mistake due to not-enough-time-to-investigate-because-it-should-have-been-done-yesterday.
Not an elegant solution, but works.
import wx
class MyWin(wx.Frame):
def __init__(self):
super(MyWin, self).__init__(None, size=(800,600))
self.painted = True
self.backup = ''
self.text = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.HSCROLL)
self.text.Bind(wx.EVT_TEXT_PASTE, self.onpaste)
self.text.Bind(wx.EVT_TEXT, self.ontextchange)
self.Show()
def onpaste(self, event):
if not wx.TheClipboard.IsOpened():
buf = wx.TextDataObject()
wx.TheClipboard.Open()
success = wx.TheClipboard.GetData(buf)
wx.TheClipboard.Close()
if success:
self.painted = False
self.backup = buf.GetText()
self.text.SetValue(self.backup)
self.painted = True
def ontextchange(self, event):
if self.painted:
dlg = wx.MessageDialog(self, 'Editing not allowed', '', wx.OK)
dlg.ShowModal()
dlg.Destroy()
self.painted = False
self.text.SetValue(self.backup)
self.painted = True
app = wx.App()
frame = MyWin()
app.MainLoop()