Lots of EVT_CHILD_FOCUS events

Hi,

On wxPython 2.8.10.1-unicode on Ubuntu 8.04 I see (*) lots (about 125)
of EVT_CHILD_FOCUS events being fired when focus changes. In
complicated dialogs with a deep tree of widgets this causes noticable
delays when switching from one control to the next. Having a noop
event handler that doesn't call event.Skip() fixes the issue, but
breaks tab control (I think). Note that I'm not binding to
EVT_CHILD_FOCUS anywhere in my code.

With wxPython 2.8.10.1-unicode on Windows Vista I see only 3 or 4
EVT_CHILD_FOCUS events when switching between widgets.

Any suggestions?

Thanks, Frank

(*) With the Widget Inspection Tool, very cool...

Except for that noop event handler, that is...

Thanks, Frank

···

2009/10/8 Frank Niessink <frank@niessink.com>:

Hi,

On wxPython 2.8.10.1-unicode on Ubuntu 8.04 I see (*) lots (about 125)
of EVT_CHILD_FOCUS events being fired when focus changes. In
complicated dialogs with a deep tree of widgets this causes noticable
delays when switching from one control to the next. Having a noop
event handler that doesn't call event.Skip() fixes the issue, but
breaks tab control (I think). Note that I'm not binding to
EVT_CHILD_FOCUS anywhere in my code.

I get the same results on all 3 platforms with this example, there is one child focus event for the panel and one for the widget each time I tab. Maybe it's different with a more deeply nested containment hierarchy?

import wx
print wx.version()

class MyFrame(wx.Frame):
     def __init__(self, *args, **kw):
         wx.Frame.__init__(self, *args, **kw)

         pnl = wx.Panel(self)
         txt1 = wx.TextCtrl(pnl)
         txt2 = wx.TextCtrl(pnl)
         txt3 = wx.TextCtrl(pnl)
         txt4 = wx.TextCtrl(pnl)
         btn = wx.Button(pnl, -1, "Clicker")

         pnl.Sizer = wx.BoxSizer(wx.VERTICAL)
         pnl.Sizer.Add(txt1, 0, wx.ALL, 5)
         pnl.Sizer.Add(txt2, 0, wx.ALL, 5)
         pnl.Sizer.Add(txt3, 0, wx.ALL, 5)
         pnl.Sizer.Add(txt4, 0, wx.ALL, 5)
         pnl.Sizer.Add(btn, 0, wx.ALL, 5)

         self.Bind(wx.EVT_CHILD_FOCUS, self.OnChildFocus)

     def OnChildFocus(self, evt):
         print 'focus:', evt.GetWindow()

app = wx.App(False)
frm = MyFrame(None, title="child focus events")
frm.Show()
app.MainLoop()

···

On 10/8/09 1:21 PM, Frank Niessink wrote:

Hi,

On wxPython 2.8.10.1-unicode on Ubuntu 8.04 I see (*) lots (about 125)
of EVT_CHILD_FOCUS events being fired when focus changes. In
complicated dialogs with a deep tree of widgets this causes noticable
delays when switching from one control to the next. Having a noop
event handler that doesn't call event.Skip() fixes the issue, but
breaks tab control (I think). Note that I'm not binding to
EVT_CHILD_FOCUS anywhere in my code.

With wxPython 2.8.10.1-unicode on Windows Vista I see only 3 or 4
EVT_CHILD_FOCUS events when switching between widgets.

Any suggestions?

--
Robin Dunn
Software Craftsman

Hi Robin,

I get the same results on all 3 platforms with this example, there is
one child focus event for the panel and one for the widget each time I
tab. Maybe it's different with a more deeply nested containment hierarchy?

I think there's some exponential behavior going on. Attached script
(childfocus2.py) gives this output on wxMSW when selecting the text
control and then hitting tab once:

2.8.10.1 (msw-unicode)
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x2a60290> >
focus: <wx._controls.TextCtrl; proxy of <Swig Object of type
'wxTextCtrl *' at 0x1a70e38> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x2a60290> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x2a60138> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x2a60290> >
focus: <wx._controls.DatePickerCtrl; proxy of <Swig Object of type
'wxDatePickerCtrl *' at 0x2b83880> >

And on wxGTK it's this, probably partly caused by the fact that the
DatePickerCtrl is a composite control on wxGTK:

2.8.10.1 (gtk2-unicode)
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._controls.TextCtrl; proxy of <Swig Object of type
'wxTextCtrl *' at 0x845c600> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._controls.DatePickerCtrl; proxy of <Swig Object of type
'wxDatePickerCtrl *' at 0x852c770> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._core.Control; proxy of <Swig Object of type 'wxControl *'
at 0x852d808> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455418> >
focus: <wx._controls.TextCtrl; proxy of <Swig Object of type
'wxTextCtrl *' at 0x8532da8> >

If I nest the second text control in another panel (childfocus3.py), I
get this output on wxMSW:

2.8.10.1 (msw-unicode)
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c0420> >
focus: <wx._controls.TextCtrl; proxy of <Swig Object of type
'wxTextCtrl *' at 0x16c0e38> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c0420> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c02c8> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c0420> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c0578> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c0420> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c02c8> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x28c0420> >
focus: <wx._controls.DatePickerCtrl; proxy of <Swig Object of type
'wxDatePickerCtrl *' at 0x29e3828> >

But on wxGTK it's this!:

2.8.10.1 (gtk2-unicode)
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._controls.TextCtrl; proxy of <Swig Object of type
'wxTextCtrl *' at 0x845c600> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852c7e8> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._controls.DatePickerCtrl; proxy of <Swig Object of type
'wxDatePickerCtrl *' at 0x852d938> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852c7e8> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._core.Control; proxy of <Swig Object of type 'wxControl *'
at 0x852e800> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852c7e8> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x852bf88> >
focus: <wx._windows.Panel; proxy of <Swig Object of type 'wxPanel *'
at 0x8455758> >
focus: <wx._controls.TextCtrl; proxy of <Swig Object of type
'wxTextCtrl *' at 0x8533da0> >

Note how the panel at 0x8455758 now receives the event 12 times, twice
as much as before!

If one additional layer (one panel) doubles the number of events sent
I'm not surprised that my dialog with 10 layers or so becomes very
sluggish :frowning:

Thanks, Frank

childfocus3.py (814 Bytes)

childfocus2.py (703 Bytes)

···

2009/10/9 Robin Dunn <robin@alldunn.com>:

Indeed. Please create a bug ticket about this. Personally I'm a bit surprised to see the events coming from the panel(s) when the child which is actually receiving focus is not the panel, but there may be a good reason for that which I'm not seeing at the moment. But the doubling of the number of events is clearly a bug.

···

On 10/9/09 12:42 PM, Frank Niessink wrote:

Note how the panel at 0x8455758 now receives the event 12 times, twice
as much as before!

If one additional layer (one panel) doubles the number of events sent
I'm not surprised that my dialog with 10 layers or so becomes very
sluggish :frowning:

--
Robin Dunn
Software Craftsman

If one additional layer (one panel) doubles the number of events sent
I'm not surprised that my dialog with 10 layers or so becomes very
sluggish :frowning:

Indeed. Please create a bug ticket about this.

Done: wxTrac has been migrated to GitHub Issues - wxWidgets

Personally I'm a bit
surprised to see the events coming from the panel(s) when the child
which is actually receiving focus is not the panel, but there may be a
good reason for that which I'm not seeing at the moment. But the
doubling of the number of events is clearly a bug.

One work-around I found was to catch and Skip(False) the
EVT_CHILD_FOCUS events. What kind of consequences may that have?

Thanks, Frank

···

2009/10/9 Robin Dunn <robin@alldunn.com>:

On 10/9/09 12:42 PM, Frank Niessink wrote:

Hi Robin, Andrea,

Note how the panel at 0x8455758 now receives the event 12 times, twice
as much as before!

If one additional layer (one panel) doubles the number of events sent
I'm not surprised that my dialog with 10 layers or so becomes very
sluggish :frowning:

Indeed. Please create a bug ticket about this. Personally I'm a bit
surprised to see the events coming from the panel(s) when the child
which is actually receiving focus is not the panel, but there may be a
good reason for that which I'm not seeing at the moment. But the
doubling of the number of events is clearly a bug.

I've been bitten by this issue again I think. This time on Windows.
When opening a dialog with a wx.lib.agw.customtreectrl.CustomTreeCtrl
in it the customtreectrl calls self.SetFocus() at the end of its
__init__ method. This call takes 0,5 second. Since this dialog has two
customtreectrls in it, opening the dialog takes a whole second; quite
noticeable for users.

The call to self.SetFocus in CustomTreeCtrl.__init__ causes 74
wx.EVT_CHILD_FOCUS events. I guess this is understandable since the
dialog is quite complicated (a notebook with 8 pages, each with
several widgets). However, the main window of the application uses
wx.lib.agw.aui to manage its panes. The framemanager binds to
wx.EVT_CHILD_FOCUS events. For each wx.EVT_CHILD_FOCUS event received
it activates the active pane. Here's the framemanager.OnChildFocus
handler:

    def OnChildFocus(self, event):
        """
        Handles the wx.EVT_CHILD_FOCUS event for L{AuiManager}.
        :param `event`: a L{wx.ChildFocusEvent} to be processed.
        """

        # when a child pane has it's focus set, we should change the
        # pane's active state to reflect this. (this is only true if
        # active panes are allowed by the owner)

        window = event.GetWindow()
        if isinstance(window.GetParent(), AuiFloatingFrame):
            rootManager = GetManager(window)
        else:
            rootManager = self

        if rootManager:
            rootManager.ActivatePane(window)

        event.Skip()

This calls ActivatePane:

    def ActivatePane(self, window):
        """
        Activates the pane to which `window` is associated.
        :param `window`: a L{wx.Window} derived window.
        """

        if self.GetFlags() & AUI_MGR_ALLOW_ACTIVE_PANE:
            while window:
                ret, self._panes = SetActivePane(self._panes, window)
                if ret:
                    break

                window = window.GetParent()

            self.RefreshCaptions()

I think that is the culprit: ret is True when the active pane is
changed, but in my case it is never True, since the dialog is not
managed by AUI. Consequence is that ActivatePane calls SetActivePane
for each widget that sends wx.EVT_CHILD_FOCUS (which is already 74
times) and each of its ancestors. ActivatePane takes 0.015 seconds per
call, long enough to matter.

Adapting OnChildFocus to ignore wx.EVT_CHILD_FOCUS events coming from
dialogs fixes the issue for me, see attached patch. I'm not sure
that's the best solution though. What do you think, Andrea, Robin?

Thanks, Frank

patch.txt (812 Bytes)

···

2009/10/9 Robin Dunn <robin@alldunn.com>:

On 10/9/09 12:42 PM, Frank Niessink wrote:

Hi Frank,

2009/10/20 Frank Niessink:

Hi Robin, Andrea,

Note how the panel at 0x8455758 now receives the event 12 times, twice
as much as before!

If one additional layer (one panel) doubles the number of events sent
I'm not surprised that my dialog with 10 layers or so becomes very
sluggish :frowning:

Indeed. Please create a bug ticket about this. Personally I'm a bit
surprised to see the events coming from the panel(s) when the child
which is actually receiving focus is not the panel, but there may be a
good reason for that which I'm not seeing at the moment. But the
doubling of the number of events is clearly a bug.

I've been bitten by this issue again I think. This time on Windows.
When opening a dialog with a wx.lib.agw.customtreectrl.CustomTreeCtrl
in it the customtreectrl calls self.SetFocus() at the end of its
__init__ method. This call takes 0,5 second. Since this dialog has two
customtreectrls in it, opening the dialog takes a whole second; quite
noticeable for users.

The call to self.SetFocus in CustomTreeCtrl.__init__ causes 74
wx.EVT_CHILD_FOCUS events. I guess this is understandable since the
dialog is quite complicated (a notebook with 8 pages, each with
several widgets). However, the main window of the application uses
wx.lib.agw.aui to manage its panes. The framemanager binds to
wx.EVT_CHILD_FOCUS events. For each wx.EVT_CHILD_FOCUS event received
it activates the active pane. Here's the framemanager.OnChildFocus
handler:

def OnChildFocus(self, event):
"""
Handles the wx.EVT_CHILD_FOCUS event for L{AuiManager}.
:param `event`: a L{wx.ChildFocusEvent} to be processed.
"""

   \# when a child pane has it&#39;s focus set, we should change the
   \# pane&#39;s active state to reflect this\. \(this is only true if
   \# active panes are allowed by the owner\)

   window = event\.GetWindow\(\)
   if isinstance\(window\.GetParent\(\), AuiFloatingFrame\):
       rootManager = GetManager\(window\)
   else:
       rootManager = self

   if rootManager:
       rootManager\.ActivatePane\(window\)

   event\.Skip\(\)

This calls ActivatePane:

def ActivatePane(self, window):
"""
Activates the pane to which `window` is associated.
:param `window`: a L{wx.Window} derived window.
"""

   if self\.GetFlags\(\) &amp; AUI\_MGR\_ALLOW\_ACTIVE\_PANE:
       while window:
           ret, self\.\_panes = SetActivePane\(self\.\_panes, window\)
           if ret:
               break

           window = window\.GetParent\(\)

       self\.RefreshCaptions\(\)

I think that is the culprit: ret is True when the active pane is
changed, but in my case it is never True, since the dialog is not
managed by AUI. Consequence is that ActivatePane calls SetActivePane
for each widget that sends wx.EVT_CHILD_FOCUS (which is already 74
times) and each of its ancestors. ActivatePane takes 0.015 seconds per
call, long enough to matter.

Adapting OnChildFocus to ignore wx.EVT_CHILD_FOCUS events coming from
dialogs fixes the issue for me, see attached patch. I'm not sure
that's the best solution though. What do you think, Andrea, Robin?

I have applied your patch as it is: I don't see anything strange in
doing what you suggest and I can't think of any way it would hurt AUI.

Thank you for the patch!

Andrea.

"Imagination Is The Only Weapon In The War Against Reality."
http://xoomer.alice.it/infinity77/

···

2009/10/9 Robin Dunn <robin@alldunn.com>:

On 10/9/09 12:42 PM, Frank Niessink wrote: