wxpython toolbarmenu keyboard navigation - python

I've been working on an application for blind people, so I decided to python and wxPython for the GUI. The problem is that I cannot navigate the wx.toolbar using tabs. This is my actual code.
menuToolBar = self.CreateToolBar(style=wx.TB_FLAT)
menuToolBar.AddSeparator()
menuToolBar.AddTool(ID_NUEVO, "Nuevo Archivo", wx.Bitmap(
os.path.join(self.__rutaPrincipal, "img/nueva pagina.png")), "Nuevo Archivo")
menuToolBar.AddSeparator()
menuToolBar.AddTool(ID_GUARDAR_ARCHIVO, "Guardar Archivo", wx.Bitmap(
os.path.join(self.__rutaPrincipal, "img/guardar pagina.png")), "Guardar Archivo")
menuToolBar.AddTool(ID_ABRIR_ARCHIVO, "Abrir Página", wx.Bitmap(
os.path.join(self.__rutaPrincipal, "img/abrir pagina.png")), "Abrir Página")
menuToolBar.AddSeparator()
menuToolBar.AddTool(ID_FOCUS_PANEL_ESCRITURA, "Foco Escritura", wx.Bitmap(
os.path.join(self.__rutaPrincipal, "img/panel escritura.png")), "Foco Escritura")
menuToolBar.AddTool(ID_FOCUS_PANEL_RESULTADO, "Foco Resultado", wx.Bitmap(
os.path.join(self.__rutaPrincipal, "img/panel resultado.png")), "Foco Resultado")
menuToolBar.AddSeparator()
menuToolBar.AddTool(ID_CERRAR_PAGINA, "Cerrar Página", wx.Bitmap(
os.path.join(self.__rutaPrincipal, "img/cerrar pagina.png")), "Cerrar Página")
menuToolBar.AddSeparator()
menuToolBar.Realize()
is there any configuration to achieve it? or maybe is there another component that help me?.
P.D. I am new to python development and Stackoverflow

Rather than delete the existing anwser, which in fact answers a different question, I'm leaving it in place because I personally believe it's useful, in a related way.
Here is an example of what I was referring to in my comment, about making your own toolbar, which you can navigate using Tab, Left and Right Arrow keys.
It should be enough to get you started.
It uses a normal button and some Generic bitmap buttons, both will work, as we need to illustrate which button currently has focus, which is achieved by using at least 2 different bitmaps for each button. One Normal and one for Focus.
import wx
import wx.lib.buttons as buttons
Valid_keys = [wx.WXK_F1,wx.WXK_RETURN,wx.WXK_TAB,wx.WXK_LEFT,wx.WXK_RIGHT]
class MainFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
self.funcs={"Button1":self.OnButton1,
"Button2":self.OnButton2,
"Button3":self.OnButton3,
"Button4":self.OnButton4}
wx.Frame.__init__(self, None, title="Self defined Toolbar", size=(400,400))
tool_panel = wx.Panel(self,-1,name="tool_panel")
tool_panel.SetBackgroundColour("lightgreen")
tool_panel.SetToolTip("Press F1 or Tab to Access Toolbar")
panel = wx.Panel(self,-1,size=(400,300), name="Main_panel")
panel.SetBackgroundColour("lightblue")
bmp = wx.Bitmap("Discord.png", wx.BITMAP_TYPE_ANY)
bmp1 = wx.Bitmap("Discord1.png", wx.BITMAP_TYPE_ANY)
bmp2 = wx.Bitmap("Discord2.png", wx.BITMAP_TYPE_ANY)
self.Button1 = buttons.GenBitmapButton(tool_panel,bitmap=bmp,size=(40,40),name="Button1")
self.Button2 = buttons.GenBitmapButton(tool_panel,bitmap=bmp,size=(40,40),name="Button2")
self.Button3 = buttons.GenBitmapButton(tool_panel,bitmap=bmp,size=(40,40),name="Button3")
self.Button4 = wx.Button(tool_panel,-1, label="",size=(40,40),name="Button4")
self.Button4.SetBitmap(wx.Bitmap('test.png'))
self.Button4.SetBitmapFocus(wx.Bitmap('testfocus.png'))
self.Text = wx.TextCtrl(panel, -1, size=(400,300), style=wx.TE_MULTILINE)
self.Button1.SetToolTip("Function1")
self.Button2.SetToolTip("Function2")
self.Button3.SetToolTip("Function3")
self.Button4.SetToolTip("Function4")
self.BitmapButtons = [self.Button1,self.Button2,self.Button3]
for i in range(0,len(self.BitmapButtons)):
self.BitmapButtons[i].SetBitmapLabel(bmp)
self.BitmapButtons[i].SetBitmapFocus(bmp2)
self.BitmapButtons[i].SetBitmapSelected(bmp1)
self.Bind(wx.EVT_BUTTON, self.OnButton)
self.Bind(wx.EVT_CHAR_HOOK, self.OnKey)
tool_sizer=wx.BoxSizer(wx.HORIZONTAL)
main_sizer=wx.BoxSizer(wx.VERTICAL)
tool_sizer.Add(self.Button1)
tool_sizer.Add(self.Button2)
tool_sizer.Add(self.Button3)
tool_sizer.Add(self.Button4)
tool_panel.SetSizer(tool_sizer)
main_sizer.Add(tool_panel,0,wx.EXPAND)
main_sizer.Add(panel,0,wx.EXPAND,0)
self.SetSizer(main_sizer)
self.Text.SetFocus()
self.Show()
def OnButton(self, event):
Id = event.GetId()
btn = event.GetEventObject()
x = self.funcs[btn.GetName()]
x()
self.Text.SetFocus()
def OnButton1(self):
print("Button 1")
def OnButton2(self):
print("Button 2")
def OnButton3(self):
print("Button 3")
def OnButton4(self):
print("Button 4")
def OnKey(self, event):
key = event.GetKeyCode()
if key not in Valid_keys: #Ignore all keys except F1, Enter and Navigation keys
event.Skip()
return
if key not in [wx.WXK_F1,wx.WXK_RETURN]:
event.Skip()
return
if key == wx.WXK_F1: #F1 focuses on the First Toolbar button
self.Button1.SetFocus()
return
i = self.get_focus()
if i == 1:
id="Button1"
elif i == 2:
id="Button2"
elif i == 3:
id="Button3"
elif i == 4:
id="Button4"
if i > 0: #Focus was a toolbar button, execute button function
x = self.funcs[id]
x()
self.Text.SetFocus()
event.Skip()
def get_focus(self):
focused = wx.Window.FindFocus()
if focused == self.Button1:
return 1
elif focused == self.Button2:
return 2
elif focused == self.Button3:
return 3
elif focused == self.Button4:
return 4
else:#Something other than the toolbar buttons has focus return Fail
return -1
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App()
frame = MainFrame()
app.MainLoop()

You need to implement Accelerators in your MenuBar and possibly your MenuItem's.
An accelerator table allows the application to specify a table of keyboard shortcuts for menu or button commands.
Often, these are set up using the text in the menu item, by-passing the wx.AcceleratorTable.
p1 = wx.MenuItem(playm, wx.NewIdRef(), '&Play\tCtrl+P')
Here the &Play sets P to be the Menu accelerator, and Ctrl+P to be the hotkey for the function represented by the menu item.
Put another way, when navigating the menus, pressing P will select the Play menu item. When not navigating the menus, just pressing Ctrl+P will run that function.
N.B.
You do not have to setup both.
Do not place the & ampersand character before the same letter more than once in a single menu or the menubar, as only the first one will function.
e.g. "&Save" and "Sa&ve as" would work, using S for "Save" and V for "Save as"
To access the Menu shortcuts Press the Alt button and then any of the underlined letters.
Some minimal code with Menu accelerators but no assigned hotkeys:
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)
# A Statusbar in the bottom of the window
self.CreateStatusBar()
# Setting up the menus
'''Define main items'''
filemenu = wx.Menu()
editmenu = wx.Menu()
infomenu = wx.Menu()
'''Items'''
# file menu
menu_open = filemenu.Append(wx.ID_OPEN, "Open")
filemenu.Append(wx.ID_NEW, "New")
filemenu.Append(wx.ID_SAVE, "Save")
filemenu.Append(wx.ID_SAVEAS, "Save as")
filemenu.Append(wx.ID_EXIT, '&Quit', 'Quit application')
filemenu.AppendSeparator()
filemenu.Append(wx.ID_PRINT, "&Print")
filemenu.Append(wx.ID_PRINT_SETUP, "Print setup")
filemenu.Append(wx.ID_PREVIEW, "Preview")
# edit menu
editmenu.Append(wx.ID_COPY, "Copy")
editmenu.Append(wx.ID_CUT, "Cut")
editmenu.Append(wx.ID_PASTE, "Paste")
editmenu.AppendSeparator()
editmenu.Append(wx.ID_UNDO, "Undo")
editmenu.Append(wx.ID_REDO, "Re-do it")
# info menu
infomenu.Append(wx.ID_ABOUT, "About")
'''Bind items for activation'''
# bind file menu
self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT)
self.Bind(wx.EVT_MENU, self.OnOpen)
# Creating the menubar.
menuBar = wx.MenuBar()
# Add menus
menuBar.Append(filemenu, "&File")
menuBar.Append(editmenu, "&Edit")
menuBar.Append(infomenu, "&Help")
# Adding the MenuBar to the Frame content.
self.SetMenuBar(menuBar)
self.Show(True)
def OnOpen(self, event):
print ("Menu item selected")
# event skip as we want to continue checking events in case Quit was chosen
event.Skip()
def OnExit(self, event):
self.Destroy()
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.MainLoop()
Run this code and Press the Alt key, press one of the undelined letters F, E or H and navigate the menus using the arrow keys or any underlined letters.
N.B. Certain functions are automatically assigned Hotkeys e.g. Ctrl+Q is Quit.

Related

Show wx.MenuItems's Help String in a determined wx.StatusBar's Field

Premise:
I'm creating a little wx.Frame with a wx.StatusBar with 3 Fields, the one on the left and the one on the right smaller and the one on the center bigger.
Now, By Default, when i put the mouse on a wx.MenuItem the wx.StatusBar set as Status Text the wx.MenuItem's Help String in the FIRST wx.StatusBar's Field like in the below picture:
Question:
Now, I want, if it's possible, that in the above situation the wx.MenuItem's Help String in the SECOND FIELD (the biggest one in the center)
You are looking for SetStatusBarPane() for the frame
It simply requires you to nominate a pane number (from 0) where the text should be displayed.
import wx
class Test(wx.Frame):
def __init__(self,parent):
wx.Frame.__init__(self,parent,title="Frame aka Window",size = (300,200))
panel = wx.Panel(self)
self.status=self.CreateStatusBar(3)
self.SetStatusBarPane(1)
self.status.SetStatusText("Status bar 0",0)
self.status.SetStatusText("Status bar 2",2)
menubar=wx.MenuBar()
firstm=wx.Menu()
secondm=wx.Menu()
fm1 = wx.MenuItem(firstm, wx.NewIdRef(), 'New Window\tAlt+N')
firstm.Append(fm1)
self.Bind(wx.EVT_MENU, self.OnMenu1, id=fm1.GetId())
fm2 = wx.MenuItem(firstm, wx.NewIdRef(), 'Open', "Text for the statusbar")
firstm.Append(fm2)
self.Bind(wx.EVT_MENU, self.OnMenu2, id=fm2.GetId())
fm3 = wx.MenuItem(firstm, -1, 'Quit\tAlt+Q')
firstm.Append(fm3)
self.Bind(wx.EVT_MENU, self.OnMenu3, id=fm3.GetId())
sm1 = wx.MenuItem(firstm, wx.ID_ANY, 'Re-Do', "Statusbar Re-Do")
secondm.Append(sm1)
self.Bind(wx.EVT_MENU, self.OnsMenu1, id=sm1.GetId())
sm2 = wx.MenuItem(secondm, wx.ID_ANY, 'Un-Do', "Statusbar Un-Do")
secondm.Append(sm2)
self.Bind(wx.EVT_MENU, self.OnsMenu2, id=sm2.GetId())
menubar.Append(firstm,"File")
menubar.Append(secondm,"Edit")
self.SetMenuBar(menubar)
t = wx.StaticText(panel,-1,"Hello i'm a test", pos=(10,20))
def OnMenu1(self, event):
print("Menu item 1",event.GetId())
def OnMenu2(self, event):
print("Menu item 2",event.GetId())
def OnMenu3(self, event):
print("Menu item 3 Quit",event.GetId())
self.Destroy()
def OnsMenu1(self, event):
print("2nd Menu item 1",event.GetId())
def OnsMenu2(self, event):
print("2nd Menu item 2",event.GetId())
if __name__=='__main__':
app=wx.App()
frame=Test(None)
frame.Show()
app.MainLoop()

Hiding the menubar in WxPython

I'm trying to find a way to hide the menubar when the mouse isnt over it, i know i could get the mouse xy position and check if its in a certain area but im having no luck hiding the menubar itself.
My code:
class HelloFrame(wx.Frame):
images = []
def __init__(self, *args, **kw):
# ensure the parent's __init__ is called
super(HelloFrame, self).__init__(*args, **kw)
# create a panel in the frame
pnl = wx.Panel(self)
# create a menu bar
self.makeMenuBar()
# and a status bar
#self.CreateStatusBar()
#self.SetStatusText("Welcome to wxPython!")
def makeMenuBar(self):
fileMenu = wx.Menu()
helloItem = fileMenu.Append(-1, "&Load Backround Image")
fileMenu.AppendSeparator()
exitItem = fileMenu.Append(wx.ID_EXIT)
helpMenu = wx.Menu()
aboutItem = helpMenu.Append(wx.ID_ABOUT)
menuBar = wx.MenuBar()
menuBar.Append(fileMenu, "&File")
#menuBar.Append(OptionsMenu, "&Options") TBM
menuBar.Append(helpMenu, "&Help")
# Give the menu bar to the frame
self.SetMenuBar(menuBar)
# Finally, associate a handler function with the EVT_MENU event for
# each of the menu items. That means that when that menu item is
# activated then the associated handler function will be called.
self.Bind(wx.EVT_MENU, self.OpenImage, helloItem)
self.Bind(wx.EVT_MENU, self.OnExit, exitItem)
self.Bind(wx.EVT_MENU, self.OnAbout, aboutItem)
def OnExit(self, event):
self.Close(True)
def OnAbout(self, event):
wx.MessageBox("Test",
"About",
wx.OK|wx.ICON_INFORMATION)
def OpenImage(self, event):
with wx.FileDialog (None, "Choose image", wildcard="PNG files (*.png)|*.png", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return # the user changed their mind
else:
# Gets rid of other images
for i in self.images:
i.Destroy()
self.images.remove(i)
# Displays image and adds image object to [self.images]
ImageName = fileDialog.GetPath()
png = wx.Image(ImageName, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.images.append(wx.StaticBitmap(self, -1, png, (0, 0), (png.GetWidth(), png.GetHeight())))
print(ImageName)
if __name__ == '__main__':
app = wx.App()
frm = HelloFrame(None, title='FileHeader Test')
frm.Show()
app.MainLoop()
ive tried just adding menubar.Hide() in the function makemenubar but to no avail
ive also tried self.Hide() in the same area but no luck
I should start by saying that I don't recommend that you do this.
It's non-standard and could confuse.
It's expensive cpu-wise for no obvious benefit.
That said, here is one way of doing it, using Show, Hide and EVT_MOTION.
The issue is that once it is hidden/removed, the next mouse OVER doesn't happen because it is no longer there. As you suppose, we can assign a territorial range where we suppose it would be. If the cursor strays into that zone, the menu can be shown or hidden.
import wx
class HelloFrame(wx.Frame):
images = []
def __init__(self, *args, **kw):
# ensure the parent's __init__ is called
super(HelloFrame, self).__init__(*args, **kw)
# create a panel in the frame
pnl = wx.Panel(self)
pnl2 = wx.Panel(self)
pnl.SetBackgroundColour("green")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(pnl,1, wx.EXPAND,10)
sizer.Add(pnl2,1, wx.EXPAND,10)
self.SetSizer(sizer)
# create a menu bar
self.makeMenuBar()
# and a status bar
#self.CreateStatusBar()
#self.SetStatusText("Welcome to wxPython!")
pnl.Bind(wx.EVT_MOTION, self.MenuPos)
def makeMenuBar(self):
fileMenu = wx.Menu()
helloItem = fileMenu.Append(-1, "&Load Backround Image")
fileMenu.AppendSeparator()
exitItem = fileMenu.Append(wx.ID_EXIT)
helpMenu = wx.Menu()
aboutItem = helpMenu.Append(wx.ID_ABOUT)
self.menuBar = wx.MenuBar()
self.menuBar.Append(fileMenu, "&File")
self.menuBar.Append(helpMenu, "&Help")
# Give the menu bar to the frame
self.SetMenuBar(self.menuBar)
self.menuBar.Hide()
# Finally, associate a handler function with the EVT_MENU event for
# each of the menu items. That means that when that menu item is
# activated then the associated handler function will be called.
self.Bind(wx.EVT_MENU, self.OpenImage, helloItem)
self.Bind(wx.EVT_MENU, self.OnExit, exitItem)
self.Bind(wx.EVT_MENU, self.OnAbout, aboutItem)
def MenuPos(self, event):
x, y = event.GetPosition()
if y <= 2:
self.menuBar.Show()
if y >= 3:
self.menuBar.Hide()
event.Skip()
def OnExit(self, event):
self.Close(True)
def OnAbout(self, event):
wx.MessageBox("Test",
"About",
wx.OK|wx.ICON_INFORMATION)
def OpenImage(self, event):
with wx.FileDialog (None, "Choose image", wildcard="PNG files (*.png)|*.png", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return # the user changed their mind
else:
# Gets rid of other images
for i in self.images:
i.Destroy()
self.images.remove(i)
# Displays image and adds image object to [self.images]
ImageName = fileDialog.GetPath()
png = wx.Image(ImageName, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
self.images.append(wx.StaticBitmap(self, -1, png, (0, 0), (png.GetWidth(), png.GetHeight())))
print(ImageName)
if __name__ == '__main__':
app = wx.App()
frm = HelloFrame(None, title='FileHeader Test')
frm.Show()
app.MainLoop()

wxPython Program Scan

I am trying to get a better understanding of how wxPython 'scans'.
Please see my code below:
import os
import wx
from time import sleep
NoFilesToCombine = 0
class PDFFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(400,400))
panel = wx.Panel(self)
self.Show()
try: #Set values of PDFNoConfirmed to zero on 1st initialisation
if PDFNoConfimed != 0:
None
except UnboundLocalError:
PDFNoConfimed = 0
try: #Set values of PDFNoConfirmed to zero on 1st initialisation
if PDFNoConfirmedInitially != 0:
None
except UnboundLocalError:
PDFNoConfirmedInitially = 0
while ((PDFNoConfimed == 0) and (PDFNoConfirmedInitially == 0)):
while PDFNoConfirmedInitially == 0:
BoxInputNo = wx.NumberEntryDialog(panel, "So You Want To Combine PDF Files?", "How Many?", "Please Enter", 0, 2, 20)
if BoxInputNo.ShowModal() == wx.ID_OK: #NumberEntryDialog Pressed OK
NoFilesToCombine = BoxInputNo.GetValue()
PDFNoConfirmedInitially = 1
elif BoxInputNo.ShowModal() == wx.ID_CANCEL:
exit()
print(NoFilesToCombine)
ConfirmationLabel = wx.StaticText(panel, -1, label="You Have Selected " + str(NoFilesToCombine) + " Files To Combine, Is This Right?", pos=(20, 100))
ConfirmationBoxConfirm = wx.ToggleButton(panel, label="Confirm", pos=(20, 200))
ConfirmationBoxCancel = wx.ToggleButton(panel, label="Cancel", pos=(180, 200))
#if ConfirmationBoxConfirm.GetValue() == 1:
# exit()
if ConfirmationBoxCancel.GetValue() == 1:
PDFNoConfirmedInitially = 0
app = wx.App()
frame = PDFFrame(None, title="Robs PDF Combiner Application")
app.MainLoop()
Now this is a work in progress so it obviously isn't complete. However what I'm trying to accomplish with the above is:
Display a number entry popup. If user presses 'cancel' exit the application (this works but needs 2 presses for some reason?). If press OK, then:
Display the number entered in step 1, with 2 additional buttons. The 'confirm' doesn't do anything as yet, but the 'cancel' should take you back to step 1. (by resetting the PDFNoConfirmedInitially flag).
Now step 2 doesn't work. When I debug it almost appears as though the PDFFrameonly gets scanned once. My presumably false assumption being that this would be continually scanned due to app.MainLoop() continually scanning wx.App() which in turn would call the child frame?
Help/ pointers/ deeper understanding always appreciated,
Thanks
Rob
1) ShowModal() shows dialog window and you use it two times
if BoxInputNo.ShowModal() == wx.ID_OK:
and
elif BoxInputNo.ShowModal() == wx.ID_CANCEL:
so it shows your window two times.
And only at second time you check wx.ID_CANCEL.
You should run it only once and check its result
result = BoxInputNo.ShowModal()
if result == wx.ID_OK:
pass
elif result == wx.ID_CANCEL:
self.Close()
return
2) You have to assign function to button and this function should reset variable and show dialog window again. But I think wx.Button could be better then wx.ToggleButton
ConfirmationBoxCancel = wx.Button(panel, label="Cancel", pos=(180, 200))
ConfirmationBoxCancel.Bind(wx.EVT_BUTTON, self.on_button_cancel)
def on_button_cancel(self, event):
#print('event:', event)
pass
# reset variable and show dialog window
Frankly I don't understand some of your variables. Maybe if you use True/False instead of 0/1 then they will be more readable. But main problem for me are while loops. GUI frameworks (wxPython, tkinter, PyQt, etc) should run only one loop - Mainloop(). Any other loop may block Mainloop() and GUI will freeze.
I created own version without any while loop but I don't know if it resolve all problems
import wx
class PDFFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, -1, title, size=(400,400))
self.panel = wx.Panel(self)
self.Show()
# show dialog at start
if self.show_dialog(self.panel):
# create StaticLabel and buttons
self.show_confirmation(self.panel)
else:
# close main window and program
self.Close()
def show_dialog(self, panel):
"""show dialog window"""
global no_files_to_combine
box_input_no = wx.NumberEntryDialog(panel, "So You Want To Combine PDF Files?", "How Many?", "Please Enter", 0, 2, 20)
result = box_input_no.ShowModal()
if result == wx.ID_OK: #NumberEntryDialog Pressed OK
no_files_to_combine = box_input_no.GetValue()
return True
elif result == wx.ID_CANCEL:
print('exit')
return False
def show_confirmation(self, panel):
"""create StaticLabel and buttons"""
self.confirmation_label = wx.StaticText(self.panel, -1, label="You Have Selected {} Files To Combine, Is This Right?".format(no_files_to_combine), pos=(20, 100))
self.confirmation_box_confirm = wx.Button(self.panel, label="Confirm", pos=(20, 200))
self.confirmation_box_cancel = wx.Button(self.panel, label="Cancel", pos=(180, 200))
# assign function
self.confirmation_box_confirm.Bind(wx.EVT_BUTTON, self.on_button_confirm)
self.confirmation_box_cancel.Bind(wx.EVT_BUTTON, self.on_button_cancel)
def update_confirmation(self):
"""update existing StaticLabel"""
self.confirmation_label.SetLabel("You Have Selected {} Files To Combine, Is This Right?".format(no_files_to_combine))
def on_button_cancel(self, event):
"""run when pressed `Cancel` button"""
#print('event:', event)
# without `GetValue()`
if self.show_dialog(self.panel):
# update existing StaticLabel
self.update_confirmation()
else:
# close main window and program
self.Close()
def on_button_confirm(self, event):
"""run when pressed `Confirn` button"""
#print('event:', event)
# close main window and program
self.Close()
# --- main ---
no_files_to_combine = 0
app = wx.App()
frame = PDFFrame(None, title="Robs PDF Combiner Application")
app.MainLoop()

Can't go back to wxpython main frame after creating and destroying Dialogue using ShowModal via Pubsub

I am programming an application using wxpython and wx.lib.pubsub. in python 2.7.3
1- There is a Frame with a menu item. When this menu is clicked, a message is published by pubsub.
2- This message destroys (if possible) and creates a "first level" dialogue.
3- "First Level" dialogue has an list of valules and an "add value" button. (NOTE: Such list of variables can be modified so I am trying to update this list)
4- When the "add value" button is clicked, another message is published by pubsub.
5- This message creates a "Second Level" dialogue, so a new name for the new variable can be written.
6- There is a "continue" button in this "second level" dialogue which has two consequences:
First one: Self.Destroy();
Second one: goes to step 2, i.e. destroys the "first level" dialogue and creates it again.
To that point the program seems to work fine, however, when I finish "adding" variables to the "first level" dialogue I Destroy it and then I cannot go back to the main Frame stated in step 1.
Why is this happening?
All the Dialogues are shown via ShowModal(). However if I use only Show() it seems to work fine but, since the program has many menus and items, ShowModal() is preferred.
Any idea why it works with Show() but not with ShowModal()?
If there is a simpler way to perform the task I want to do, it would be appreciated.
import wx
from wx.lib.pubsub import Publisher as pub
class itemReceiver(object):
def __init__(self):
pub.subscribe(self.__OnShowDialog, 'show.dialog')
def __OnShowDialog(self, message):
self.dlgParent = message.data[0]
print str(self.dlgParent)
self.valuesToShow = message.data[1]
print self.valuesToShow
#try to destroy dialog before creating a new one
try:
self.manageParametersDialog.Destroy()
except:
pass
self.manageParametersDialog = manageParamsDialog(self.dlgParent, self.valuesToShow)
print "ready to show first level dialogue"
self.manageParametersDialog.ShowModal() #if .Show() instead, there is no problem
class secondaryReceiver(object):
def __init__(self):
pub.subscribe(self.__OnShowDialog, 'add.item')
def __OnShowDialog(self, message):
dlgParent = message.data[0]
dlgGrandParent = message.data[1]
self.variableList = message.data[2]
editParameterDialog = editParamDlg(dlgParent, dlgGrandParent, self.variableList)
editParameterDialog.ShowModal()
class manageParamsDialog (wx.Dialog):
def __init__(self, parent, valueList):
self.valueList = valueList
self.parent = parent
wx.Dialog.__init__(self, parent, -1, "first level dialogue", style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
sizer=wx.BoxSizer(wx.VERTICAL)
self.optionList = wx.ListBox(self, -1, size=(200, 70), choices = valueList)
sizer.Add(self.optionList)
addButton = wx.Button(self, -1, 'Add New')
self.Bind(wx.EVT_BUTTON, self.OnButton, addButton)
sizer.Add(addButton)
cancelButton = wx.Button(self, -1, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelButton)
sizer.Add(cancelButton)
self.SetSizer(sizer)
self.Fit()
def OnButton (self, e):
pub.sendMessage('add.item', [self, self.parent, self.valueList])
def OnCancel(self,e):
self.Destroy()
class editParamDlg(wx.Dialog):
def __init__(self, parent, grandParent, variableList):
self.variableList = variableList
self.grandParent = grandParent
wx.Dialog.__init__(self, parent, -1, "second level dialogue", style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
hboxSizer = wx.BoxSizer(wx.HORIZONTAL)
self.textInput = wx.TextCtrl(self, -1)
hboxSizer.Add(self.textInput)
addButton = wx.Button(self, -1, 'Continue')
self.Bind(wx.EVT_BUTTON, self.OnAdd, addButton)
hboxSizer.Add(addButton)
cancelButton = wx.Button(self, -1, 'Cancel')
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelButton)
hboxSizer.Add(cancelButton)
self.SetSizer(hboxSizer)
self.Fit()
def OnAdd(self, e):
self.variableList.append(self.textInput.GetValue())
self.Destroy()
pub.sendMessage('show.dialog',[self.grandParent, self.variableList])
def OnCancel(self,e):
self.Destroy()
class ToolbarFrame(wx.Frame):
#this ToolbarFrame is the main window, with a Toolbar and a white panel below.
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, "this is a frame", size=(480, 320))
myPanel = wx.Panel(self)
myPanel.SetBackgroundColour("White")
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
menuItem = wx.MenuItem(fileMenu, -1, "menu item", "opens dialog via pubsub")
self.Bind(wx.EVT_MENU, self.OnMenuItem, menuItem)
fileMenu.AppendItem(menuItem)
menuBar.Append(fileMenu, "File")
self.SetMenuBar(menuBar)
def OnMenuItem(self, e):
pub.sendMessage('show.dialog', [self, ["one", "two", "three"]])
app = wx.PySimpleApp()
frame = ToolbarFrame(parent=None, id=-1)
frame.Show()
newItemListener = itemReceiver()
editParameterListener = secondaryReceiver()
app.MainLoop()
try changing secondaryReciever as follows
class secondaryReceiver(object):
def __init__(self):
pub.subscribe(self.__OnShowDialog, 'add.item')
def __OnShowDialog(self, message):
dlgParent = message.data[0]
dlgGrandParent = message.data[1]
self.variableList = message.data[2]
editParameterDialog = editParamDlg(dlgParent, dlgGrandParent, self.variableList)
editParameterDialog.ShowModal()
#this line will not execute till the dialog closes
self.dlgParent.optionList.SetItems(editParameterDialog.variableList)
editParameterDialog.Destroy()
and also change editParamDlg
def OnAdd(self, e):
self.variableList.append(self.textInput.GetValue())
self.Close()
the problem was that you would call the show.modal from that OnAdd ... which would try to destroy the existing window and then open a new one... but the old one wasnt destroyed ... this left weird remnants that caused you errors ... and really all you want to do is update the item list ...

Call functions from within a wxPython event handler

I'm struggling to find a way to use function from within a wxPython event handler function. Say I have a button that when clicked it runs a function called OnRun using an event handler. However, the user forgot to click a RadionButton before the OnRun button and I want to pop-up a MessageDialog telling them they forgot a step. I'm going to reuse this MessageDialog several times, thus rather than doing a copy/paste of the same code I would like to just have this MessageDialog in a function and call this MessageDialog function if the user forgets to check a RadioButton.
If this wasn't a function used in an Event Handler I know I could simply put the function as an argument but I'm not seeing a way I can do this with these. Any help here would be appreciated.
The following code shows how to create a little method that you can reuse to show custom dialogs and tells the user that they need to accept the agreement. You can change the conditionals to do whatever you want, of course. And you can change the "showMsg" method so that the icon changes too with just a little tweaking.
import wx
########################################################################
class TestFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Test")
panel = wx.Panel(self)
self.radios = wx.RadioBox(panel, label="Choices",
choices = ["None", "Accept", "Reject"])
button = wx.Button(panel, label="Run")
button.Bind(wx.EVT_BUTTON, self.onBtn)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.radios, 0, wx.ALL, 5)
sizer.Add(button, 0, wx.ALL, 5)
panel.SetSizer(sizer)
#----------------------------------------------------------------------
def onBtn(self, event):
""""""
btn = event.GetEventObject()
btn.SetLabel("Running")
radio_value = self.radios.GetStringSelection()
if radio_value == "None":
self.showMsg("Error", "Please Choose 'Accept' or 'Reject'!")
elif radio_value == "Accept":
self.showMsg("Message", "Thank you for accepting!")
else:
self.showMsg("Message", "We're sorry, but you cannot continue the install")
#----------------------------------------------------------------------
def showMsg(self, title, msg):
""""""
dlg = wx.MessageDialog(None, msg, title, wx.OK | wx.ICON_QUESTION)
dlg.ShowModal()
dlg.Destroy()
if __name__ == "__main__":
app = wx.App(False)
frame = TestFrame()
frame.Show()
app.MainLoop()
I will make a stab at this, even if the answer seems too direct. I would set a property in the enclosing frame that flags whether the Radio Button has been clicked or not. Then when OnRun is called check that property. Should it be in the wrong state, call the MessageDialog and abort/pause/modify the OnRun.
EDIT Here is what I mean, a trivial example with two buttons, neither of which will lead to further action unless a user agreement is clicked.
import wx
class ButtonFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Button Example',
size=(300, 100))
panel = wx.Panel(self, -1)
self.radio = wx.RadioButton(panel, -1, "Accept user agreement", pos=(50, 10))
self.button = wx.Button(panel, -1, "Run", pos=(50, 30))
self.Bind(wx.EVT_BUTTON, self.OnRun, self.button)
self.button.SetDefault()
self.btn2 = wx.Button(panel, -1, "Walk", pos=(150, 30))
self.Bind(wx.EVT_BUTTON, self.OnWalk, self.btn2)
def OnRun(self, event):
if not self.CheckRadio():
return
self.button.SetLabel("Running")
def OnWalk(self, event):
if not self.CheckRadio():
return
self.btn2.SetLabel("Walking")
def CheckRadio(self):
accepted = self.radio.GetValue()
if not accepted:
dlg = wx.MessageDialog(None, 'First accept the user agreement',
'MessageDialog', wx.OK | wx.ICON_QUESTION)
result = dlg.ShowModal() # result not used in this demo
dlg.Destroy()
return False
else:
return True
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = ButtonFrame()
frame.Show()
app.MainLoop()
Code is adapted from Listing 7.11 of wxPython in Action. I hope this helps, if you have not already solved this n the time that has passed.
You can create your own MessageDialog (inheriting), or you can use functools.partial/lambda to pass an additional argument to the event handler:
self.Bind(wx.MY_EVENT, lambda evt: self.OnEventX(evt, handler=foo), id=12)

Categories

Resources