Choice widgets in grid - don't want Enter to leave grid

144 views
Skip to first unread message

Grant Paton-Simpson

unread,
Oct 12, 2009, 2:07:02 AM10/12/09
to wxPytho...@lists.wxwidgets.org
Hi,

I have a simple one-column grid and it has choice (dropdown) widgets in
each cell. I want the user to be able to make a selection in a cell and
hit Return, thus going down to the next row. Instead, the grid loses
focus and the dialog receives it. Unfortunately, this means the default
button is clicked and the dialog is closed. Not what I was wanting.

NB in Ubuntu I can intercept the wx.EVT_KEY_DOWN event but not in Windows.

Any thoughts?

Here is example code:

import wx
import wx.grid

class dlg(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None)
panel = wx.Panel(self)
szr = wx.BoxSizer(wx.VERTICAL)
self.grid = wx.grid.Grid(panel)
self.grid.CreateGrid(5,1)
for i in range(5):
self.grid.SetCellEditor(i, 0,
wx.grid.GridCellChoiceEditor(["opt 1", "opt 2"]))
btnOK = wx.Button(panel, wx.ID_OK)
btnOK.Bind(wx.EVT_BUTTON, self.OnOK)
szr.Add(self.grid)
szr.Add(btnOK)
panel.SetSizer(szr)
szr.SetSizeHints(self)
self.Layout()
self.grid.SetFocus()

def OnOK(self, event):
wx.MessageBox("OK was clicked")
self.Destroy()

app = wx.PySimpleApp()
frame = dlg()
frame.Show()
app.MainLoop()


All the best, Grant

Frank Millman

unread,
Oct 12, 2009, 2:35:54 AM10/12/09
to wxpytho...@googlegroups.com
Grant Paton-Simpson wrote:
>
> Hi,
>
> I have a simple one-column grid and it has choice (dropdown)
> widgets in each cell. I want the user to be able to make a
> selection in a cell and hit Return, thus going down to the
> next row. Instead, the grid loses focus and the dialog
> receives it. Unfortunately, this means the default button is
> clicked and the dialog is closed. Not what I was wanting.
>

This is not a complete answer, but may provide a clue.

I had a similar problem when I set up a customised cell text editor. I found
that pressing Enter to leave the editor fired the default button on the
panel, and pressing Tab caused focus to leave the grid and pass to the next
widget on the panel.

The solution was as follows. The following lines are taken from the demo
GridCustEditor.py -

class MyCellEditor(gridlib.PyGridCellEditor):
[...]

def Create(self, parent, id, evtHandler):
self._tc = wx.TextCtrl(parent, id, "")

On Robin's advice, I modified the last line to look like this -

self._tc = wx.TextCtrl(parent, id, "",
style=wx.TE_PROCESS_TAB|wx.TE_PROCESS_ENTER)

In your case you are using a Choice editor. I believe that there is a way of
getting to the TextControl component of the Choice control, but I am not
sure how. If you can get to that, and apply the above style, it may solve
your problem.

HTH

Frank Millman

pyGrant

unread,
Oct 12, 2009, 5:52:32 AM10/12/09
to wxPython-users
Thanks for that. If your comments result in a solution I will be very
grateful.

Sadly, I learned about the importance of wx.TE_PROCESS_TAB|
wx.TE_PROCESS_ENTER in a previous encounter with my grid widget ;-).
As for interacting with the text component of a wx.Choice widget, that
worked for me flawlessly in Ubuntu! Just not in Windows. A sad
emoticon does not do justice to how that discovery felt. I am willing
to make custom controls and custom editors if necessary but I am
hoping for a simple solution that is cross platform.

BTW another gotcha to watch for concerns validation of values you are
entering into an editor. These will not be picked up by
grid.GetCellValue( ) when validating if you are in the process of
leaving the editor (e.g. clicking on Return which your code has
intercepted). The solution I found was using
self.grid.DisableCellEditControl() once the Return keypress was
intercepted which had the effect of flushing the values through into
the grid. It was then easy to use grid.GetCellValue( ) to get the
entered values and conduct the validation. Not directly linked to the
problem but an example of a simple solution bypassing the need for
custom editors and controls etc.

Anyone able to follow Frank's lead?

Mike Driscoll

unread,
Oct 12, 2009, 11:25:08 AM10/12/09
to wxPython-users
This is how I get to the underlying control:

<code>
# this event is fired when a user initiate editing in a cell
self.myGrid.Bind(gridlib.EVT_GRID_EDITOR_CREATED, self.onCellEdit)

def onCellEdit(self, event):
'''
When cell is edited, get a handle on the editor widget
and bind it to EVT_KEY_DOWN
'''
editor = event.GetControl()
if wx.Platform == '__WXMSW__':
editor.Bind(wx.EVT_KEY_DOWN, self.onEditorKey)
event.Skip()
</code>

I don't remember why I wrapped that editor event binding in that IF.
Feel free to try it without that. I also didn't have any special
widgets in my cell, but I still think this should work.

-------------------
Mike Driscoll

Blog: http://blog.pythonlibrary.org

Frank Millman

unread,
Oct 12, 2009, 12:49:56 PM10/12/09
to wxpytho...@googlegroups.com
Mike Driscoll wrote:
> On Oct 12, 1:35 am, "Frank Millman" <fr...@chagford.com> wrote:
> >
> > In your case you are using a Choice editor. I believe that
> there is a
> > way of getting to the TextControl component of the Choice
> control, but
> > I am not sure how. If you can get to that, and apply the
> above style,
> > it may solve your problem.
> >
>
> This is how I get to the underlying control:
>
> <code>
> # this event is fired when a user initiate editing in a cell
> self.myGrid.Bind(gridlib.EVT_GRID_EDITOR_CREATED, self.onCellEdit)
>
> def onCellEdit(self, event):
> '''
> When cell is edited, get a handle on the editor widget
> and bind it to EVT_KEY_DOWN
> '''
> editor = event.GetControl()
> if wx.Platform == '__WXMSW__':
> editor.Bind(wx.EVT_KEY_DOWN, self.onEditorKey)
> event.Skip()
> </code>
>

That gets you to the underlying ComboBox control, which in turn is made up
of a text control and a listbox. I think (not sure) that you need to get a
reference to the text control, so that you can apply the style
wx.TE_PROCESS_TAB|wx.TE_PROCESS_ENTER to it, but I don't know how to do
that.

Frank

Robin Dunn

unread,
Oct 12, 2009, 3:21:35 PM10/12/09
to wxpytho...@googlegroups.com
On 10/11/09 11:35 PM, Frank Millman wrote:
>
> Grant Paton-Simpson wrote:
>>
>> Hi,
>>
>> I have a simple one-column grid and it has choice (dropdown)
>> widgets in each cell. I want the user to be able to make a
>> selection in a cell and hit Return, thus going down to the
>> next row. Instead, the grid loses focus and the dialog
>> receives it. Unfortunately, this means the default button is
>> clicked and the dialog is closed. Not what I was wanting.
>>

>


> On Robin's advice, I modified the last line to look like this -
>
> self._tc = wx.TextCtrl(parent, id, "",
> style=wx.TE_PROCESS_TAB|wx.TE_PROCESS_ENTER)
>
> In your case you are using a Choice editor. I believe that there is a way of
> getting to the TextControl component of the Choice control, but I am not
> sure how.

Not for the stock wx.Choice, it doesn't have a separate wx control for
the text portion :-(

>> NB in Ubuntu I can intercept the wx.EVT_KEY_DOWN event but not in Windows.
>>
>> Any thoughts?
>>

On Windows the keys used for navigation are taken from the stream before
the key-down events, so I expect that the only thing you can do is turn
off the navigation features. I don't know if this will work in a dialog
since it also supports navigation, but try putting the grid and other
widgets in a wx.Panel with style=0, and put that panel in the dialog.


--
Robin Dunn
Software Craftsman
http://wxPython.org

pyGrant

unread,
Oct 12, 2009, 5:59:30 PM10/12/09
to wxPython-users
Hi Robin,

Thanks for that. panel = wx.Panel(self, -1, style=0) certainly makes
a difference but the central problem remains.

The main problem is that I still can't intercept Return keypresses
from the choice widget while it is in editing mode i.e. the user is
making their selection. Intercepting during rendering mode is easy

self.grid.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
...
def OnKeyDown(self, event):
print event.GetKeyCode()

but that doesn't help with selections during editing mode e.g. the
user selects an option from the choice widget and presses Enter.

I need to see if it is a Return and then decide whether to let them
move down or not depending on validation results.

That being the case, I am considering the custom editor / custom
control approach. I have done this before so I have some idea what
I'm in for. I was using a composite control containing a text box and
a browse button. It was tricky, but I still didn't have to change the
functionality of either sub-widget. But I have no idea how to make a
custom choice replacement. Ideally, I would subclass a standard
control and intercept keystrokes at that point. I can then send a
custom event outwards to be picked up by the grid e.g. to indicate a
user pressed Enter while making a choice.

Robin - is subclassing the existing choice widget an option? I would
rather not build a choice control from scratch (painting to screen
etc).

Or can I just get the custom editor class to intercept any
keypresses? See http://wiki.wxpython.org/wxGrid#Capturing_Cell_Edits_when_focus_changes

I'll report back on the latter. Fingers crossed. Reminds me of an
old advert for the British navy IIRC. A sailor is swinging off a rope
under a helicopter in the pouring rain and gale-force winds. He is
soaking wet and bashing back and forth into the cliff. The caption is
something like "If you couldn't stand a joke you shouldn't have joined
the navy" !

pyGrant

unread,
Oct 12, 2009, 6:36:12 PM10/12/09
to wxPython-users
No luck with the second approach mentioned in my post immediately
above. I misinterpreted http://wiki.wxpython.org/wxGrid#Capturing_Cell_Edits_when_focus_changes
*. It was about getting the frame to intercept the key down event.
It is what Mike was suggesting much earlier and has nothing to do with
custom editors.

The strategy of intercepting a grid editor being created, and then
binding to that editor's EVT_KEY_DOWN event, seemed to have some
potential. But it still can't pick up the Enter and Tab keys. I
guess that makes sense. It isn't where it is being handled that is
the issue. As Robin has noted, "the keys used for navigation are
taken from the stream before the key-down events".

For the record, here are the main parts of the code I wrote:

self.frame.Bind(wx.grid.EVT_GRID_EDITOR_CREATED,
self.OnGridEditorCreated)

def OnGridEditorCreated(self, event):
editor = event.GetControl()
editor.Bind(wx.EVT_KEY_DOWN, self.OnGridKeyDownIntercepted)
event.Skip()

def OnGridKeyDownIntercepted(self, event):
keycode = event.GetKeyCode()
print "OnGridKeyDownIntercepted: %s" % keycode
if keycode == wx.WXK_RETURN:
print "A Return captured !@#@#!@#@!@"
else:
event.Skip()

Back to the first approach I mentioned. There has to be a way of
getting a custom control to do what is required, even in Windows. The
standard text control allows interception of Returns and Tabs when
TE_PROCESS_ENTER and TE_PROCESS_TAB are used. Is there some way of
getting the same sort of thing to happen with other controls? A way
of preventing the navigation keys from leaving the stream without
having to disable navigation altogether?

* [quick correction to the linked reference] It is
wx.grid.EVT_GRID_EDITOR_CREATED not wx.EVT_GRID_EDITOR_CREATED
> keypresses?  Seehttp://wiki.wxpython.org/wxGrid#Capturing_Cell_Edits_when_focus_changes

Robin Dunn

unread,
Oct 13, 2009, 8:29:57 PM10/13/09
to wxpytho...@googlegroups.com
On 10/12/09 3:36 PM, pyGrant wrote:

>
> Back to the first approach I mentioned. There has to be a way of
> getting a custom control to do what is required, even in Windows. The
> standard text control allows interception of Returns and Tabs when
> TE_PROCESS_ENTER and TE_PROCESS_TAB are used. Is there some way of
> getting the same sort of thing to happen with other controls? A way
> of preventing the navigation keys from leaving the stream without
> having to disable navigation altogether?

Have you tried using the wx.WANTS_CHARS style?

pyGrant

unread,
Oct 13, 2009, 10:36:56 PM10/13/09
to wxPython-users
Solved! In summary, the solution is to get the control (e.g. a choice
widget) when the editor is created, set its style to WANTS_CHARS, and
bind the KEY DOWN event to it.

As per Mike's first post, you need to bind to the
EVT_GRID_EDITOR_CREATED event so you can get to the control inside the
editor.

E.g. self.frame.Bind(wx.grid.EVT_GRID_EDITOR_CREATED,
self.OnGridEditorCreated)

Then you get the control and add to its Windows style so that it is
wx.WANTS_CHARS (thanks Frank via http://wiki.wxpython.org/Change%20wxGrid%20CheckBox%20with%20one%20click).
You also bind the EVT_KEY_DOWN to it.

E.g.

def OnGridEditorCreated(self, event):
"""
Need to bind KeyDown to the control itself e.g. a choice control.
wx.WANTS_CHARS makes it work.
"""
control = event.GetControl()
control.WindowStyle |= wx.WANTS_CHARS
control.Bind(wx.EVT_KEY_DOWN, self.OnGridKeyDown)
event.Skip()

Result: I can capture Enter and Tab and take control from that point
(validation, deciding where the next selection should be etc). I like
how Michael Hipp put it in http://archives.devshed.com/forums/showpost.php?p=5719770&postcount=1:

"I want to keep the user "trapped" in the [control] and grab lots of
keystrokes
(e.g. arrows, Enter, Tab, etc.) and do magical things with them".

BTW I am speaking at the NZ PyCon soon and wxPython will be one of the
topics I will be covering in my talk on the SOFA Statistics package
(http://www.sofastatistics.com). I will emphasise the strength of the
wxPython community for solving problems and the level of control and
sophistication possible using the wxPython widgets.

Thanks :-)
Reply all
Reply to author
Forward
0 new messages