Stop a wx.Control from losing focus on arrow key press? - python

A pretty minimal example:
import wx
class Control(wx.Control):
def __init__(self, parent):
wx.Control.__init__(self, parent)
self.Bind(wx.EVT_CHAR, self.OnKey)
self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
def OnKey(self, event):
print("key pressed")
event.Skip()
def OnMouseClick(self, event):
self.SetFocus()
print("has focus")
event.Skip()
class Frame(wx.Frame):
def __init__(self, parent=None):
wx.Frame.__init__(self, parent)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
radio = wx.RadioButton(panel, label="Radio button")
button = wx.Button(panel, label="Button")
control = Control(panel)
sizer.Add(radio, 0, wx.ALL, 5)
sizer.Add(button, 0, wx.ALL, 5)
sizer.Add(control, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.Show()
if __name__ == "__main__":
app = wx.App()
frame = Frame()
app.MainLoop()
By clicking on the control, sets the focus to control. However, even though binding key presses to a function OnKey, an arrow key press changes focus to another button/widget.
Is there a method similar to AcceptsFocusFromKeyboard(self):
Description: Can this window be given focus by keyboard navigation? if not, the only way to give it focus (provided it accepts it at all) is to click it.
Except, a method where my control doesn't lose focus from keyboard navigation?

Both of you event handlers are missing the call to:
event.Skip()
Those events are non-wxCommandEvents and in order for them to work properly, OS has to perform whatever needs to be performed. That's what Skip() is for. Otherwise the event will be eaten by the control.
Please check the documentation for that method, add it and see if that fixes the issue.
Also, out of curiosity, what do you expect to happen when the user presses the arrow key?
If you want the focus to stay when the arrow key is pressed, try to filter the event by event.GetKeyCode() (check the docs for the proper function name) and don't call event.Skip() in this case.
If that doesn't work try to catch wx.EVT_CHAR_HOOK event.
If even this doesn't work, override FilterEvent().
Also, the structure of you GUI is very weird. Try to get the RAD tool (wxGlade, wxSmith, etc), build it there and look at the result. Or check the demo folder in wxPython distribution (available as a different download) to see how to make the normal layout in wxPython.
It is also possible that by fixing the GUI you will fix the arrow key issue.
Try this code instead:
import wx
class Frame(wx.Frame):
def __init__(self, parent=None):
wx.Frame.__init__(self, parent)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
radio = wx.RadioButton(panel, label="Radio button")
button = wx.Button(panel, label="Button")
self.Bind(wx.EVT_CHAR, self.OnChar)
radio.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
button.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
sizer.Add(radio, 0, wx.ALL, 5)
sizer.Add(button, 0, wx.ALL, 5)
sizer.Add(control, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.Show()
def OnKeyDown(self, event):
print("key pressed")
if not event.GetKeyPress() == wx.WXK_LEFT_ARROW: # Check the function name. I'm writing from memory
event.Skip()
def OnChar(self, event):
print("Character key pressed on the panel")
event.Skip()
def OnMouseClick(self, event):
self.SetFocus()
print("has focus")
event.Skip()
if __name__ == "__main__":
app = wx.App()
frame = Frame()
app.MainLoop()
You should get the idea. Call event.Skip() only if the key pressed is not an arrow key.

Related

What is the difference between parent.Bind and widget.Bind in wxPython

import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
btn = wx.Button(self, label="Press me")
btn.Bind(wx.EVT_BUTTON, self.on_button_press)
def on_button_press(self, event):
print("You pressed the button")
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title="Hello wxPython")
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(redirect=False)
frame = MyFrame()
app.MainLoop()
In the code above we used btn.Bind for binding wx.Button to wx.EVT_BUTTON. if instead, we use this way:
self.Bind(wx.EVT_BUTTON, self.on_button_press, btn)
The result will be the same as above. Now my question is the difference between self.Bind and btn.Bind.
Each widget has an Id.
Events when triggered, pass the Id of the triggering widget, in this case a button.
Binding an event to a function can be specific or generic i.e. a specific widget or any widget that fires that event type.
In short, in this case, the self.Bind binds any button event unless you specify a widget ID.
See: https://docs.wxpython.org/events_overview.html
Hopefully, the code below will help explain.
N.B. event.Skip() says don't stop at this event, see if there are more events to process.
import wx
class MyPanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
btn1 = wx.Button(self, label="Press me 1", pos=(10,10))
btn2 = wx.Button(self, label="Press me 2", pos=(10,50))
Abtn = wx.Button(self, label="Press me", pos=(10,90))
# Bind btn1 to a specific callback routine
btn1.Bind(wx.EVT_BUTTON, self.on_button1_press)
# Bind btn2 to a specific callback routine specifying its Id
# Note the order of precedence in the callback routines
self.Bind(wx.EVT_BUTTON, self.on_button2_press, btn2)
# or identify the widget via its number
# self.Bind(wx.EVT_BUTTON, self.on_button2_press, id=btn2.GetId())
# Bind any button event to a callback routine
self.Bind(wx.EVT_BUTTON, self.on_a_button_press)
# button 1 pressed
def on_button1_press(self, event):
print("You pressed button 1")
event.Skip()
# button 2 pressed
def on_button2_press(self, event):
print("You pressed button 2")
event.Skip()
# Any button pressed
def on_a_button_press(self, event):
print("You pressed a button")
event.Skip()
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title="Hello wxPython")
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(redirect=False)
frame = MyFrame()
app.MainLoop()

wxNotebook and wxBoxSizer behaviour

I have a GUI with two wxNotebook-elements like this:
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"My App",size=(800,600),pos=((wx.DisplaySize()[0]-800)/2,(wx.DisplaySize()[1]-600)/2),style= wx.SYSTEM_MENU | wx.CAPTION | wx.MINIMIZE_BOX | wx.CLOSE_BOX)
self.SetBackgroundColour((232,232,232))
self.p = wx.Panel(self,size=(800,6300),pos=(0,0))
self.SetPages()
def SetPages(self):
self.nb = wx.Notebook(self.p,style=wx.NB_BOTTOM)
page1 = PageOne(self.nb)
page2 = PageTwo(self.nb)
self.nb.AddPage(page1, "page1")
self.nb.AddPage(page2, "page2")
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.p.SetSizer(self.sizer)
Now I want to create a third Notebook-page & set focus on it at a certain event. But this does not work:
def CreateNewPageEvent(self, event):
self.CreateNewPage()
def CreateNewPage(self):
page3 = PageThree(self.nb)
self.nb.AddPage(page3, "page3")
I must admit that I'm not sure what a "BoxSizer" does =/
Any ideas to get this working?
Edit: OK, this works for an event inside my MainFrame-class. But I also want to create a new nb-page from an event of another class:
class ContinueApp(MainFrame):
def foo(self):
super(ContinueApp, self).CreateNewPage()
def continueapp(event):
cont = ContinueApp()
cont.foo()
The BoxSizer (and other sizers) are for laying out widgets so you don't have to position them yourself. They also help control which widgets expand or stretch when you make your application window larger or smaller. In your case, you should NOT add the same widget to the same sizer twice. You shouldn't add one widget to two different sizers either.
You need to remove this:
self.nb.AddPage(page1, "page3")
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.p.SetSizer(self.sizer)
Also note that you are adding page1 to the notebook again when you should be adding page3:
page3 = PageThree(self.nb)
self.nb.AddPage(page3, "page3")
If you want to switch between tabs programmatically, you should use the notebook's SetSelection method. I have an example app you can look at in the following tutorial (or the answer below it):
http://www.blog.pythonlibrary.org/2012/07/18/wxpython-how-to-programmatically-change-wx-notebook-pages/
wxpython: How to make a tab active once it is opened via an event handler?
Once you have switched tabs, you may want to set the focus on a widget within that tab. I find that using pubsub to send events is probably the cleanest way to communicate between classes. I have a couple of tutorials on that subject:
For early versions of wxPython 2.8 - http://www.blog.pythonlibrary.org/2010/06/27/wxpython-and-pubsub-a-simple-tutorial/
For later versions of wxPython 2.8 and all of 2.9 - http://www.blog.pythonlibrary.org/2013/09/05/wxpython-2-9-and-the-newer-pubsub-api-a-simple-tutorial/
This should help you, just click "GoTo Blue Panel" button.
import wx
import wx.lib
import wx.lib.flatnotebook as FNB
class MyFlatNotebook(FNB.FlatNotebook):
def __init__(self, parent):
mystyle = FNB.FNB_DROPDOWN_TABS_LIST|\
FNB.FNB_FF2|\
FNB.FNB_SMART_TABS|\
FNB.FNB_X_ON_TAB
super(MyFlatNotebook, self).__init__(parent, style=mystyle)
# Attributes
self.textctrl = wx.TextCtrl(self, value="edit me", style=wx.TE_MULTILINE)
self.blue = wx.Panel(self)
self.blue.SetBackgroundColour(wx.BLUE)
# Setup
self.AddPage(self.textctrl, "Text Editor")
self.AddPage(self.blue, "Blue Panel")
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
# Make some buttons
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
button = wx.Button(self, wx.ID_OK, "GoTo Blue Panel")
self.Bind(wx.EVT_BUTTON, self.OnButton, button)
hbox.Add(button, 0, wx.ALL, 5)
self.nb = MyFlatNotebook(self)
vbox.Add(hbox, 0, wx.EXPAND)
vbox.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(vbox)
def OnButton(self, event):
self.nb.SetSelection(1)
if __name__=='__main__':
app = wx.App(False)
frame = MyFrame(None, -1, "NoteTest")
frame.Show()
app.MainLoop()

wxpython: Why do I enter the binding function twice?

The code is only used to write a simple UI( there is only a textbox on the window), and bind the event wx.EVT_KEY_DOWN to the function OnKeyDown, but when I pressed the Esc key, the window will pop up Esc, Test, then another Esc, Test, finally it will exit after the four message box, why? I only define two message box int the wx.WXK_ESCAPE bindings.
# -*- coding: utf-8 -*-
import wx
class Command(wx.Frame):
def __init__(self, parent, title):
super(Command, self).__init__(parent, title=title,
size=(600, 500))
self.InitUI()
self.Centre()
self.Show()
def InitUI(self):
pnl = wx.Panel(self)
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.__tc_command = wx.TextCtrl(pnl, style=wx.TE_MULTILINE)
self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown)
hbox.Add(self.__tc_command, proportion=1, flag=wx.ALL|wx.EXPAND, border=15)
pnl.SetSizer(hbox)
def OnKeyDown(self, evt):
"""Enter to send data, Esc to exit."""
key = evt.GetKeyCode()
if key == wx.WXK_ESCAPE:
##################Only two MessageBox, but pop up four##################
wx.MessageBox("Esc")
wx.MessageBox("Test")
##################Only two MessageBox, but pop up four##################
self.Close()
if key == wx.WXK_RETURN:
wx.MessageBox("Enter")
evt.Skip()
if __name__ == '__main__':
app = wx.App(redirect=False)
Command(None, title='Command')
app.MainLoop()
You are calling self.Bind(wx.EVT_CHAR_HOOK, self.OnKeyDown) twice in your code.

In linux, wx.PopupWindow don't get any mouse event when it popup from wx.Dialog?

Hi, everyone.
I have a little python GUI program based on wxPython 2.8.x.
It create a wx.Dialog widget and show the dialog by calling ShowModal().
At some situation, the wx.Dialog will show a wx.PopupWindow with a wx.ListCtrl locate inside.
The wx.PopupWindow appears correctly, but trouble comes.
wx.ListCtrl, and its parent widget, wx.PopupWindow, couldn't receive any mouse event,
this cause wx.ListCtrl and wx.PopupWindow have no response while user generate any mouse action.
If wx.Dialog is opened by calling Show(),the above situation won't happen, wx.PopupWindow and wx.ListCtrl work correctly.
However, the above situation won't happen in windows version of wxPython 2.8.x even if we show wx.Dialog by calling ShowModal(),it happen in linux only.
Any suggestion?
Thanks.
Here is my source code.
It may be too long but is easy enough to test the above situation (just copy and paste).
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import wx
class TestPopupWindow(wx.PopupWindow): # modify here to change different Widget
def __init__(self, *args, **kwargs):
super(TestPopupWindow, self).__init__(*args, **kwargs)
self.SetSize((200, 200))
self.testButton = wx.Button(self, label='Test')
self.testButton.Bind(wx.EVT_BUTTON, self.__onEvtButton, self.testButton)
self.Show()
def __onEvtButton(self, event):
event.Skip()
print 'PopupWindow: test button pushed!'
self.Hide()
class TestDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
wx.Dialog.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
panel.SetAutoLayout(True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Fit(panel)
sizer.SetSizeHints(panel)
panel.SetSizer(sizer)
for i in xrange(2):
ctrl = wx.TextCtrl(panel)
sizer.Add(ctrl, 0, wx.ADJUST_MINSIZE|wx.EXPAND, 0)
for i in xrange(2):
ctrl = wx.ComboBox(panel)
sizer.Add(ctrl, 0, wx.ADJUST_MINSIZE|wx.EXPAND, 0)
self.openPopupWindowButton = wx.Button(panel, label='Open PopupWindow')
sizer.Add(self.openPopupWindowButton, 0, 0, 0)
standardDialogButtonSizer = wx.StdDialogButtonSizer()
self.standardDialogButtonSizerOK = wx.Button(panel, wx.ID_OK)
standardDialogButtonSizer.AddButton(self.standardDialogButtonSizerOK)
self.standardDialogButtonSizerCancel = wx.Button(panel, wx.ID_CANCEL)
standardDialogButtonSizer.AddButton(self.standardDialogButtonSizerCancel)
standardDialogButtonSizer.Realize()
sizer.Add(standardDialogButtonSizer, 0, wx.EXPAND, 0)
panel.Layout()
# event binding
self.openPopupWindowButton.Bind(wx.EVT_BUTTON, self.__onEvtButtonOpenPopupWindow, self.openPopupWindowButton)
self.popupWindow = None
# event handler
def __onEvtButtonOpenPopupWindow(self, event):
event.Skip()
if self.popupWindow is not None:
self.popupWindow.Close()
self.popupWindow = TestPopupWindow(self)
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
panel.SetAutoLayout(True)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Fit(panel)
sizer.SetSizeHints(panel)
panel.SetSizer(sizer)
self.openButton = wx.Button(panel, label='Open Dialog')
sizer.Add(self.openButton, 0, 0, 0)
self.openPopupWindowButton = wx.Button(panel, label='Open PopupWindow')
sizer.Add(self.openPopupWindowButton, 0, 0, 0)
panel.Layout()
# event binding
self.openButton.Bind(wx.EVT_BUTTON, self.__onEvtButtonOpen, self.openButton)
self.openPopupWindowButton.Bind(wx.EVT_BUTTON, self.__onEvtButtonOpenPopupWindow, self.openPopupWindowButton)
self.Show()
self.popupWindow = None
# event handler
def __onEvtButtonOpenPopupWindow(self, event):
event.Skip()
if self.popupWindow is not None:
self.popupWindow.Close()
self.popupWindow = TestPopupWindow(self)
def __onEvtButtonOpen(self, event):
event.Skip()
dialog = TestDialog(self, title='TestDialog')
result = dialog.ShowModal()
if result == wx.ID_OK:
print >>sys.stderr, 'OK!'
else:
print >>sys.stderr, 'Cancel!'
def main(argv=sys.argv[:]):
app = wx.PySimpleApp()
frame = TestFrame(None, title='TestFrame', style=wx.TAB_TRAVERSAL|wx.DEFAULT_FRAME_STYLE)
app.SetTopWindow(frame)
app.MainLoop()
return 0
if __name__ == '__main__':
sys.exit(main())
Instead of using a PopupWindow, just show a second wx.Dialog. Or open a frame. PopupWindows can be kind of weird with complex widgets like the ListCtrl.

wxpython auinotebook close tab event

What event is used when I close a tab in an auinotebook? I tested with
EVT_AUINOTEBOOK_PAGE_CLOSE(D). It didn't work.
I would also like to fire a right click on the tab itself event.
Where can I find all the events that can be used with the aui manager/notebook? Might just be my poor searching skills, but I can't find any lists over the different events that exist, not for mouse/window events either. It would be really handy to have a complete list.
#!/usr/bin/python
#12_aui_notebook1.py
import wx
import wx.lib.inspection
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.nb = wx.aui.AuiNotebook(self)
self.new_panel('Page 1')
self.new_panel('Page 2')
self.new_panel('Page 3')
self.nb.Bind(wx.EVT_AUINOTEBOOK_PAGE_CLOSED, self.close)
def new_panel(self, nm):
pnl = wx.Panel(self)
pnl.identifierTag = nm
self.nb.AddPage(pnl, nm)
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(self.sizer)
def close(self, event):
print 'closed'
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, '12_aui_notebook1.py')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = MyApp(0)
# wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
Oerjan Pettersen
This is the bind command you want:
self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSED, self.close, self.nb)
To detect a right click on the tab (e.g. to show a custom context menu):
self.Bind(wx.aui.EVT_AUINOTEBOOK_TAB_RIGHT_DOWN, self.right, self.nb)
Here's a list of the aui notebook events:
EVT_AUINOTEBOOK_PAGE_CLOSE
EVT_AUINOTEBOOK_PAGE_CLOSED
EVT_AUINOTEBOOK_PAGE_CHANGED
EVT_AUINOTEBOOK_PAGE_CHANGING
EVT_AUINOTEBOOK_BUTTON
EVT_AUINOTEBOOK_BEGIN_DRAG
EVT_AUINOTEBOOK_END_DRAG
EVT_AUINOTEBOOK_DRAG_MOTION
EVT_AUINOTEBOOK_ALLOW_DND
EVT_AUINOTEBOOK_DRAG_DONE
EVT_AUINOTEBOOK_BG_DCLICK
EVT_AUINOTEBOOK_TAB_MIDDLE_DOWN
EVT_AUINOTEBOOK_TAB_MIDDLE_UP
EVT_AUINOTEBOOK_TAB_RIGHT_DOWN
EVT_AUINOTEBOOK_TAB_RIGHT_UP
From: {python folder}/Lib/site-packages/{wxpython folder}/wx/aui.py

Categories

Resources