Clearly I'm confused about keyboard focus and static bitmaps

Hi wxPythoneres,

I made this (to me) really slick subclass of wx.Panel that acted like
a wx.Button, but did things that I really needed, specific to my app.
The panel housed several wx.StaticBitmaps that I could show() and hide
() depending on the state of various things, and I bound it to right
and left button clicks, and it worked just dandy.

Then, I decided to add some wx.TextCtrls to the same application,
along with bunches of these buttons. That's when a problem made
itself apparent: if you click in a wx.TextCtrl, and THEN click one of
my buttons, you would expect focus (both plain, and keyboard focus) to
leave the text control. If I use wx.Buttons, it does that, but when I
use my buttons, the focus stays on the text control. It thoroughly
confuses users of my app that type in the text control, enter some
data, and then just want to "click away" from the text control to have
the data "take" (as opposed to hitting <tab>, which works fine).

I've made an as-simple-as-I-could-get-it app that demonstrates this,
and I'll paste it below. You'll have to change 'huh.bmp' to be some
bitmap that you have lying around... sorry about that. Anyway, once
it's running, if you click in the text control, then click on the
"button", you'll notice the I-beam cursor still in the text control.
Keyboard typing still goes to the text control too.

I've tried everything I can think of to salvage this, including
changing my superclass to wx.PyPanel, and overriding the
AcceptsFocusFromKeyboard and AcceptsFocus methods (that's included in
the code below). I also tried changing the wx.StaticBitmap to
wx.BitmapButton and changing wx.Panel to wx.Control. Both of these
attempts made the display look ugly, and they didn't work either.

I really hate to throw away the 700 lines of code I wrote for my
button class, but perhaps I've gone down a rabbit hole that has no
escape.

A second question, if I haven't gone on too long already... When you
run this app below, you may notice (I do) that some clicks on MyButton
don't take. Rapidly clicking on it most of the time calls OnClick,
but not always. Same with my real version of MyButton. Any ideas on
that?

Thanks, and here's the code...

Bill.

import wx

class MyButton(wx.PyPanel):

    def __init__(self, parent, id):

        wx.PyPanel.__init__(self, parent)

        image = wx.Image('huh.bmp') #### <---- PLEASE CHANGE THIS TO
A BITMAP YOU HAVE LYING AROUND
        self.defaultBitmap = wx.StaticBitmap(self, -1,
wx.BitmapFromImage(image))
        self.defaultBitmap.Bind(wx.EVT_LEFT_DOWN, self.OnClick)

    def OnClick(self, evt):
        print "Button clicked"

    # I had high hopes that subclassing wx.PyPanel instead of
wx.Panel, and including these
    # would help, but no go.
    def AcceptsFocusFromKeyboard(self):
        """Overridden base class virtual."""
        return True

    def AcceptsFocus(self):
        """ Overridden base class virtual. """
        return True

class MyForm(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, 'my bitmap "button"
never takes focus away from wx.TextCtrl')

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)

        txt = wx.TextCtrl(panel, wx.ID_ANY, "")
        txt.Bind(wx.EVT_SET_FOCUS, self.onFocus)
        txt.Bind(wx.EVT_KILL_FOCUS, self.onKillFocus)
        btn = MyButton(panel, wx.ID_ANY)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(txt, 0, wx.ALL, 5)
        sizer.Add(btn, 0, wx.ALL, 5)
        panel.SetSizer(sizer)

    def onFocus(self, event):
        print "text widget received focus!"

    def onKillFocus(self, event):
        print "text widget lost focus!"

# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

Hi,

···

On Tue, Feb 2, 2010 at 7:52 PM, Bill williamwestakabill@gmail.com wrote:

Hi wxPythoneres,

I made this (to me) really slick subclass of wx.Panel that acted like

a wx.Button, but did things that I really needed, specific to my app.

The panel housed several wx.StaticBitmaps that I could show() and hide

() depending on the state of various things, and I bound it to right

and left button clicks, and it worked just dandy.

Then, I decided to add some wx.TextCtrls to the same application,

along with bunches of these buttons. That’s when a problem made

itself apparent: if you click in a wx.TextCtrl, and THEN click one of

my buttons, you would expect focus (both plain, and keyboard focus) to

leave the text control. If I use wx.Buttons, it does that, but when I

use my buttons, the focus stays on the text control. It thoroughly

confuses users of my app that type in the text control, enter some

data, and then just want to “click away” from the text control to have

the data “take” (as opposed to hitting , which works fine).

I’ve made an as-simple-as-I-could-get-it app that demonstrates this,

and I’ll paste it below. You’ll have to change ‘huh.bmp’ to be some

bitmap that you have lying around… sorry about that. Anyway, once

it’s running, if you click in the text control, then click on the

“button”, you’ll notice the I-beam cursor still in the text control.

Keyboard typing still goes to the text control too.

I’ve tried everything I can think of to salvage this, including

changing my superclass to wx.PyPanel, and overriding the

AcceptsFocusFromKeyboard and AcceptsFocus methods (that’s included in

the code below). I also tried changing the wx.StaticBitmap to

wx.BitmapButton and changing wx.Panel to wx.Control. Both of these

attempts made the display look ugly, and they didn’t work either.

I really hate to throw away the 700 lines of code I wrote for my

button class, but perhaps I’ve gone down a rabbit hole that has no

escape.

A second question, if I haven’t gone on too long already… When you

run this app below, you may notice (I do) that some clicks on MyButton

don’t take. Rapidly clicking on it most of the time calls OnClick,

but not always. Same with my real version of MyButton. Any ideas on

that?

Thanks, and here’s the code…

Bill.

Hopefully Robin et al was will have advice, but I know that the default wx.Panel will not accept focus if it has a child widget on it that can accept focus. It just forwards the focus on to that widget. The wx.TextCtrl is one of those widgets (as are wx.Buttons), which is why it broke. I don’t have any suggestions though…


Mike Driscoll

Blog: http://blog.pythonlibrary.org

Hi again all, and thanks Mike for your reply,

One more bit of trivia on my failed attempts: when I replaced the
wx.Panel that contains my StaticBitmap with a wx.Control, things
looked ugly, but worked ALMOST right...if you clicked in the Control,
but not over the bitmap. But it seems that, even though a bitmap can
react to mouse click events, they seems to hide whatever event will
take focus away from a wx.TextCtrl in the same app.

Thanks in advance to anyone with any ideas,

Bill.

···

On Feb 2, 7:56 pm, Mike Driscoll <m...@pythonlibrary.org> wrote:

Hi,

On Tue, Feb 2, 2010 at 7:52 PM, Bill <williamwestakab...@gmail.com> wrote:
> Hi wxPythoneres,

> I made this (to me) really slick subclass of wx.Panel that acted like
> a wx.Button, but did things that I really needed, specific to my app.
> The panel housed several wx.StaticBitmaps that I could show() and hide
> () depending on the state of various things, and I bound it to right
> and left button clicks, and it worked just dandy.

> Then, I decided to add some wx.TextCtrls to the same application,
> along with bunches of these buttons. That's when a problem made
> itself apparent: if you click in a wx.TextCtrl, and THEN click one of
> my buttons, you would expect focus (both plain, and keyboard focus) to
> leave the text control. If I use wx.Buttons, it does that, but when I
> use my buttons, the focus stays on the text control. It thoroughly
> confuses users of my app that type in the text control, enter some
> data, and then just want to "click away" from the text control to have
> the data "take" (as opposed to hitting <tab>, which works fine).

> I've made an as-simple-as-I-could-get-it app that demonstrates this,
> and I'll paste it below. You'll have to change 'huh.bmp' to be some
> bitmap that you have lying around... sorry about that. Anyway, once
> it's running, if you click in the text control, then click on the
> "button", you'll notice the I-beam cursor still in the text control.
> Keyboard typing still goes to the text control too.

> I've tried everything I can think of to salvage this, including
> changing my superclass to wx.PyPanel, and overriding the
> AcceptsFocusFromKeyboard and AcceptsFocus methods (that's included in
> the code below). I also tried changing the wx.StaticBitmap to
> wx.BitmapButton and changing wx.Panel to wx.Control. Both of these
> attempts made the display look ugly, and they didn't work either.

> I really hate to throw away the 700 lines of code I wrote for my
> button class, but perhaps I've gone down a rabbit hole that has no
> escape.

> A second question, if I haven't gone on too long already... When you
> run this app below, you may notice (I do) that some clicks on MyButton
> don't take. Rapidly clicking on it most of the time calls OnClick,
> but not always. Same with my real version of MyButton. Any ideas on
> that?

> Thanks, and here's the code...

> Bill.

Hopefully Robin et al was will have advice, but I know that the default
wx.Panel will not accept focus if it has a child widget on it that can
accept focus. It just forwards the focus on to that widget. The wx.TextCtrl
is one of those widgets (as are wx.Buttons), which is why it broke. I don't
have any suggestions though...

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

Blog: http://blog.pythonlibrary.org

Bill wrote:

One more bit of trivia on my failed attempts: when I replaced the
wx.Panel that contains my StaticBitmap with a wx.Control,

Ahh -- I think this may be the issue:

You might want to not use a StaticBitmap at all -- rather, draw the bitmap yourself with a DC, that way there is only one wx.Window (the Panel) involved, so noting to steal focus.

Also, StaticBitmap is a bit of a weird beast -- it isn't a full-on control on all platforms. There is a genbitmap (generic bitmap, I may have the ame a bit wrong) in wx.lib somewhere, which shold be used if you want a StaticBitmap to be a real control. YOu might also look at how it's implemented to get ideas.

-CHB

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

I've made an as-simple-as-I-could-get-it app that demonstrates this,
and I'll paste it below. You'll have to change 'huh.bmp' to be some
bitmap that you have lying around... sorry about that. Anyway, once
it's running, if you click in the text control, then click on the
"button", you'll notice the I-beam cursor still in the text control.
Keyboard typing still goes to the text control too.

As others have alluded to already, panels will cooperate with each other to implement tab traversal and moving the focus to widgets which will accept it. You might be able to disrupt this if you use style=0 on your panels (the default style is wx.TAB_TRAVERSAL) but you'll probably be better off taking a different approach. See below.

I also tried changing the wx.StaticBitmap to
wx.BitmapButton and changing wx.Panel to wx.Control. Both of these
attempts made the display look ugly, and they didn't work either.

Ugly in what way?

A second question, if I haven't gone on too long already... When you
run this app below, you may notice (I do) that some clicks on MyButton
don't take. Rapidly clicking on it most of the time calls OnClick,
but not always. Same with my real version of MyButton. Any ideas on
that?

In your example you are only binding the mouse event to the static bitmap, so it will be the only one that gets the event. If you click outside of the bounds of that widget then the panel will get the event, but you are not binding it there so you wont see it. As Chris mentioned this can also be affected by what platform you are running on as the static bitmap may not be a true window but is just drawn on the parent, so it's possible that events will be even more confused.

Instead of using child widgets you will likely have better results by drawing the bitmap(s) yourself directly on your control window. If you derive from wx.PyControl then you can override the AcceptsFocus method and then your custom widget will play nicer when it's on a panel and focus is managed via the panel's tab traversal algorithm. For some examples look at the wx.lib.buttons and wx.lib.statbmp modules.

···

On 2/2/10 5:52 PM, Bill wrote:

--
Robin Dunn
Software Craftsman

Thanks Robin & Chris!

I'll try drawing my own bitmaps on a wx.PyControl next. It does sound
like I was heading off in a bad direction, and I appreciate your help!

I thought I'd be clever by keeping a cache of every bitmap I might
ever need (sometimes on the order of 64 of them) all in the same place
on the same panel, and call Hide() and Show() on them depending on the
state of my "button". I thought that might save processing time by
having the bitmaps "pre-rendered" in that fashion, but drawing them as
needed and not having the StaticBitmaps sounds like the way to go.

(In answer to your question Robin, "Ugly", in that a border was drawn
around the wx.PyControl, and on the wx.BitmapButton. I'm sure there
was a way to turn that off, but I just didn't get around to tracking
it down, since it didn't seem to work anyway.)

I'm running this on Windows XP... perhaps that has something to do
with the slow response time?

Anyway, I will definitely look at the examples you mentioned Chris.

Again, thanks all,

Bill.

···

On Feb 3, 1:00 pm, Robin Dunn <ro...@alldunn.com> wrote:

On 2/2/10 5:52 PM, Bill wrote:

> I've made an as-simple-as-I-could-get-it app that demonstrates this,
> and I'll paste it below. You'll have to change 'huh.bmp' to be some
> bitmap that you have lying around... sorry about that. Anyway, once
> it's running, if you click in the text control, then click on the
> "button", you'll notice the I-beam cursor still in the text control.
> Keyboard typing still goes to the text control too.

As others have alluded to already, panels will cooperate with each other
to implement tab traversal and moving the focus to widgets which will
accept it. You might be able to disrupt this if you use style=0 on your
panels (the default style is wx.TAB_TRAVERSAL) but you'll probably be
better off taking a different approach. See below.

> I also tried changing the wx.StaticBitmap to
> wx.BitmapButton and changing wx.Panel to wx.Control. Both of these
> attempts made the display look ugly, and they didn't work either.

Ugly in what way?

> A second question, if I haven't gone on too long already... When you
> run this app below, you may notice (I do) that some clicks on MyButton
> don't take. Rapidly clicking on it most of the time calls OnClick,
> but not always. Same with my real version of MyButton. Any ideas on
> that?

In your example you are only binding the mouse event to the static
bitmap, so it will be the only one that gets the event. If you click
outside of the bounds of that widget then the panel will get the event,
but you are not binding it there so you wont see it. As Chris mentioned
this can also be affected by what platform you are running on as the
static bitmap may not be a true window but is just drawn on the parent,
so it's possible that events will be even more confused.

Instead of using child widgets you will likely have better results by
drawing the bitmap(s) yourself directly on your control window. If you
derive from wx.PyControl then you can override the AcceptsFocus method
and then your custom widget will play nicer when it's on a panel and
focus is managed via the panel's tab traversal algorithm. For some
examples look at the wx.lib.buttons and wx.lib.statbmp modules.

--
Robin Dunn
Software Craftsmanhttp://wxPython.org

Hello,

In my multithreading odyssey, I am calling various GUI related routines another thread, and making liberal use of wx.CallAfter to mediate those. A problem I'm having that I can't seem to find any clarity on from numerous Googling sessions is that I get a "TypeError 'None' type is not iterable" in some cases...

My code calls a routine, MakeGUIchange(imagename) from a call after like this:

      imagename = self.getStringFromFile() #some minidom reading work
      wx.CallAfter(MakeGUIChange, imagename)

But sometimes the string name is an empty string which is interpreted by the MakeGUIChange to mean use a default blank image for an element of the GUI.

Any suggestion how I deal with that? I suppose I could try defining an arbitrary non-None global value which I would pass instead of an empty string, but that seems a bit kludgey, and I'd also like to understand how iterability relates to passing a variable in the wx.CallAfter.

Any suggestions are greatfully welcomed.

Regards,

Ross.

Hmm, my confusion was misleading me a bit on this. I'm getting the TypeError, even with a non-None imagename in the example below.

To clarify, my call is actually expecting something back as well, as in:

      imagename = self.getStringFromFile() #some minidom reading work
      wid, hig = wx.CallAfter(MakeGUIChange, imagename)

and in the tests I've just run, I know my imagename is a real value "mypic.jpg"

And the result is:

Exception in thread Thread-2:
Traceback (most recent call last):
   File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/threading.py", line 486, in __bootstrap_inner
     self.run()
   File "/MyTool/src/root/MyParser.py", line 656, in run
      wid, hig = wx.CallAfter(MakeGUIChange, imagename)
TypeError: 'NoneType' object is not iterable

I don't know what's going on here... What is the NoneType being referenced?

Ross.

···

On 4-Feb-10, at 11:17 AM, Ross wrote:

Hello,

In my multithreading odyssey, I am calling various GUI related routines another thread, and making liberal use of wx.CallAfter to mediate those. A problem I'm having that I can't seem to find any clarity on from numerous Googling sessions is that I get a "TypeError 'None' type is not iterable" in some cases...

My code calls a routine, MakeGUIchange(imagename) from a call after like this:

     imagename = self.getStringFromFile() #some minidom reading work
     wx.CallAfter(MakeGUIChange, imagename)

But sometimes the string name is an empty string which is interpreted by the MakeGUIChange to mean use a default blank image for an element of the GUI.

Any suggestion how I deal with that? I suppose I could try defining an arbitrary non-None global value which I would pass instead of an empty string, but that seems a bit kludgey, and I'd also like to understand how iterability relates to passing a variable in the wx.CallAfter.

Any suggestions are greatfully welcomed.

Regards,

Ross.

--
To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en

Hi,

···

On Thu, Feb 4, 2010 at 10:45 AM, Ross <rossgk@gmail.com> wrote:

Exception in thread Thread-2:
Traceback (most recent call last):
File
"/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/threading.py",
line 486, in __bootstrap_inner
self.run()
File "/MyTool/src/root/MyParser.py", line 656, in run
wid, hig = wx.CallAfter(MakeGUIChange, imagename)
TypeError: 'NoneType' object is not iterable

I don't know what's going on here... What is the NoneType being referenced?

wx.CallAfter does not return a value. You are trying to unpack the
return value into two variables

wid, hig = wx.CallAfter(...)

Since CallAfter returns None, you are getting this error.

Cody

Ah - I see. So I need to find a different way to get at the result of that routine.

The iteration then makes sense in the unpacking process. I didn't see that.

Thanks for your timely response.

Ross.

···

On 4-Feb-10, at 12:09 PM, Cody Precord wrote:

Hi,

On Thu, Feb 4, 2010 at 10:45 AM, Ross <rossgk@gmail.com> wrote:

Exception in thread Thread-2:
Traceback (most recent call last):
File
"/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/threading.py",
line 486, in __bootstrap_inner
   self.run()
File "/MyTool/src/root/MyParser.py", line 656, in run
    wid, hig = wx.CallAfter(MakeGUIChange, imagename)
TypeError: 'NoneType' object is not iterable

I don't know what's going on here... What is the NoneType being referenced?

wx.CallAfter does not return a value. You are trying to unpack the
return value into two variables

wid, hig = wx.CallAfter(...)

Since CallAfter returns None, you are getting this error.

Cody

--
To unsubscribe, send email to wxPython-users+unsubscribe@googlegroups.com
or visit http://groups.google.com/group/wxPython-users?hl=en

Bill wrote:

I thought I'd be clever by keeping a cache of every bitmap I might
ever need (sometimes on the order of 64 of them) all in the same place
on the same panel, and call Hide() and Show() on them depending on the
state of my "button". I thought that might save processing time by
having the bitmaps "pre-rendered" in that fashion, but drawing them as
needed and not having the StaticBitmaps sounds like the way to go.

you can still cache them as wx.Bitmaps -- that is the platform native binary format, so drawing them is about as fast as you can get -- if they aren't huge, I'm sure you won't notice a delay of any sort.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov