[wxPython] Update on tabbing through TextCtrls

Sorry about the delay in my reply. I have identified the problem when tabbing through enabled and disabled text ctrls.

If you put the control in a panel and disable one, tabbing works as expected - the caret moves between the two enabled ctrls.

If you use the sample code below, however, tabbing makes the caret disappear (the wxPanel containing the disabled control gets focus).

Here's the small piece of code:

···

--------------

from wxPython.wx import *

class myApplication(wxApp):
    def OnInit(self):
        # Create the GUI frames

        self.frame = myFrame(NULL, -1, "Test")
        self.SetTopWindow(self.frame)
        self.frame.Show(true)

        # All done
        return true

class myFrame(wxFrame):
    def __init__(self, parent, id, caption):
        wxFrame.__init__(self, parent, id, caption)
        panel = myPanel(self)

class myPanel(wxPanel):
    def __init__(self, parent):
        wxPanel.__init__(self, parent, -1)
        entry1 = myLabelAndTextCtrl(self, TRUE)
        entry2 = myLabelAndTextCtrl(self, FALSE)
        entry3 = myLabelAndTextCtrl(self, TRUE)

        sizer = wxBoxSizer(wxVERTICAL)
        sizer.Add(entry1, 0)
        sizer.Add(entry2, 0)
        sizer.Add(entry3, 0)

        self.SetAutoLayout(TRUE)
        self.SetSizer(sizer)
        sizer.Fit(self)

class myLabelAndTextCtrl(wxPanel):
    def __init__(self, parent, enabled):
        wxPanel.__init__(self, parent, -1)
        label = wxStaticText(self, -1, "Text")
        textCtrl = wxTextCtrl(self, -1)
        textCtrl.Enable(enabled)

        sizer = wxBoxSizer(wxHORIZONTAL)
        sizer.Add(label, 1, wxEXPAND)
        sizer.Add(textCtrl, 2, wxEXPAND)
        self.SetAutoLayout(TRUE)
        self.SetSizer(sizer)
        sizer.Fit(self)

if __name__ == "__main__":
    app = myApplication()
    app.MainLoop()

-------------------

Any suggestions on how to fix this?

That's reminded me. I remember reading here that someone suggested making a composite label + textCtrl widget. Has there been any progress on this?

Cheers,

James Shaw

_________________________________________________________________
Join the world�s largest e-mail service with MSN Hotmail. http://www.hotmail.com

James Shaw wrote:

Sorry about the delay in my reply. I have identified the problem when tabbing through enabled and disabled text ctrls.

If you put the control in a panel and disable one, tabbing works as expected - the caret moves between the two enabled ctrls.

If you use the sample code below, however, tabbing makes the caret disappear (the wxPanel containing the disabled control gets focus).

Here's the small piece of code:

--------------

from wxPython.wx import *

class myApplication(wxApp):
   def OnInit(self):
       # Create the GUI frames

       self.frame = myFrame(NULL, -1, "Test")
       self.SetTopWindow(self.frame)
       self.frame.Show(true)

       # All done
       return true

class myFrame(wxFrame):
   def __init__(self, parent, id, caption):
       wxFrame.__init__(self, parent, id, caption)
       panel = myPanel(self)

class myPanel(wxPanel):
   def __init__(self, parent):
       wxPanel.__init__(self, parent, -1)
       entry1 = myLabelAndTextCtrl(self, TRUE)
       entry2 = myLabelAndTextCtrl(self, FALSE)
       entry3 = myLabelAndTextCtrl(self, TRUE)

       sizer = wxBoxSizer(wxVERTICAL)
       sizer.Add(entry1, 0)
       sizer.Add(entry2, 0)
       sizer.Add(entry3, 0)

       self.SetAutoLayout(TRUE)
       self.SetSizer(sizer)
       sizer.Fit(self)

class myLabelAndTextCtrl(wxPanel):
   def __init__(self, parent, enabled):
       wxPanel.__init__(self, parent, -1)
       label = wxStaticText(self, -1, "Text")
       textCtrl = wxTextCtrl(self, -1)
       textCtrl.Enable(enabled)

       sizer = wxBoxSizer(wxHORIZONTAL)
       sizer.Add(label, 1, wxEXPAND)
       sizer.Add(textCtrl, 2, wxEXPAND)
       self.SetAutoLayout(TRUE)
       self.SetSizer(sizer)
       sizer.Fit(self)

if __name__ == "__main__":
   app = myApplication()
   app.MainLoop()

-------------------

Any suggestions on how to fix this?

You can add

  EVT_SET_FOCUS(self, self.OnFocus)

to myLabelAndTextCtrl.__init__, and then define self.OnFocus to check if the textCtrl.IsEnabled(). If not, you can set the focus to a different control, or post a NavigationKeyEvent to the parent panel to get it to send the focus on.

The tricky part is knowing whether the focus came from the previous control (e.g. via TAB) or the next control (e.g. via SHIFT-TAB). The following modifications to your myPanel and myLabelAndTextCtrl of your code work (at least with wxPython 2.3.2.1, Python 1.5.2 for Windows).

However, this solution requires you to manually tell each myLabelAndTextCtrl the ID of the previous control in the panel. Also, if the previous control was not a myLabelAndTextCtrl but a wxSomethingElse, you would also have to subclass wxSomethingElse and add a similar EVT_KILL_FOCUS handler.

...

class myPanel(wxPanel):
     def __init__(self, parent):
         wxPanel.__init__(self, parent, -1)
         entry1 = myLabelAndTextCtrl(self, TRUE)
         entry2 = myLabelAndTextCtrl(self, FALSE, entry1.GetId())
         entry3 = myLabelAndTextCtrl(self, TRUE, entry2.GetId())
# note new final argument
  entry1.set_preceding(entry3.GetId())
# unless we manually assign an ID to entry1, we can't set its preceding # control when we create it
  self.most_recent_focus = entry1.GetId()
# don't think this is necessary any more now I figured out how to
# get OnLosingFocus to work

...

class myLabelAndTextCtrl(wxPanel):
     def __init__(self, parent, enabled, preceding_id = None):

...
  self.myText = textCtrl
  self.preceding_id = preceding_id
  EVT_SET_FOCUS(self, self.OnFocus)
  EVT_KILL_FOCUS(self, self.OnLosingFocus)
  EVT_KILL_FOCUS(textCtrl, self.OnLosingFocus)
# catch kill focus for both the textCtrl and the panel
     def set_preceding(self, preceding_id):
  self.preceding_id = preceding_id
     def OnLosingFocus(self, focus_event):
  self.GetParent().most_recent_focus = self.GetId()
     def OnFocus(self, focus_event):
  if not self.myText.IsEnabled():
# I think this condition is redundant. Tabbing never seems
# to give the focus to the panel unless the textCtrl is disabled.
             parent = self.GetParent()
      forward = (parent.most_recent_focus == self.preceding_id)
      n = wxNavigationKeyEvent()
      n.SetDirection(forward)
      n.SetWindowChange(0)
      n.SetEventObject(None)
# really, this should be the event emitter, but its only compared
# against the panel's parent to see if the event is propogating
# downward, so None seems to work
      wxPostEvent(self.GetParent(), n)

Note: if ALL your text controls are disabled, the code about will lead to an infinite loop, so some error checking should be added to avoid that.

Also, the way I send the focus to the next control is a bit of a hack, because wxNavigationKeyEvent is not documented. I had to work out the details by looking at the src/generic/panelg.cpp and src/msw/window.cpp in the wxWindows source code.

David

David C. Fox wrote:

James Shaw wrote:

Sorry about the delay in my reply. I have identified the problem when tabbing through enabled and disabled text ctrls.

If you put the control in a panel and disable one, tabbing works as expected - the caret moves between the two enabled ctrls.

If you use the sample code below, however, tabbing makes the caret disappear (the wxPanel containing the disabled control gets focus).

Here's the small piece of code:

--------------

from wxPython.wx import *

class myApplication(wxApp):
   def OnInit(self):
       # Create the GUI frames

       self.frame = myFrame(NULL, -1, "Test")
       self.SetTopWindow(self.frame)
       self.frame.Show(true)

       # All done
       return true

class myFrame(wxFrame):
   def __init__(self, parent, id, caption):
       wxFrame.__init__(self, parent, id, caption)
       panel = myPanel(self)

class myPanel(wxPanel):
   def __init__(self, parent):
       wxPanel.__init__(self, parent, -1)
       entry1 = myLabelAndTextCtrl(self, TRUE)
       entry2 = myLabelAndTextCtrl(self, FALSE)
       entry3 = myLabelAndTextCtrl(self, TRUE)

       sizer = wxBoxSizer(wxVERTICAL)
       sizer.Add(entry1, 0)
       sizer.Add(entry2, 0)
       sizer.Add(entry3, 0)

       self.SetAutoLayout(TRUE)
       self.SetSizer(sizer)
       sizer.Fit(self)

class myLabelAndTextCtrl(wxPanel):
   def __init__(self, parent, enabled):
       wxPanel.__init__(self, parent, -1)
       label = wxStaticText(self, -1, "Text")
       textCtrl = wxTextCtrl(self, -1)
       textCtrl.Enable(enabled)

       sizer = wxBoxSizer(wxHORIZONTAL)
       sizer.Add(label, 1, wxEXPAND)
       sizer.Add(textCtrl, 2, wxEXPAND)
       self.SetAutoLayout(TRUE)
       self.SetSizer(sizer)
       sizer.Fit(self)

if __name__ == "__main__":
   app = myApplication()
   app.MainLoop()

-------------------

Any suggestions on how to fix this?

You can add

    EVT_SET_FOCUS(self, self.OnFocus)

to myLabelAndTextCtrl.__init__, and then define self.OnFocus to check if the textCtrl.IsEnabled(). If not, you can set the focus to a different control, or post a NavigationKeyEvent to the parent panel to get it to send the focus on.

The tricky part is knowing whether the focus came from the previous control (e.g. via TAB) or the next control (e.g. via SHIFT-TAB). The following modifications to your myPanel and myLabelAndTextCtrl of your code work (at least with wxPython 2.3.2.1, Python 1.5.2 for Windows).

However, this solution requires you to manually tell each myLabelAndTextCtrl the ID of the previous control in the panel. Also, if the previous control was not a myLabelAndTextCtrl but a wxSomethingElse, you would also have to subclass wxSomethingElse and add a similar EVT_KILL_FOCUS handler.

...

Note: if ALL your text controls are disabled, the code about will lead to an infinite loop, so some error checking should be added to avoid that.

Also, the way I send the focus to the next control is a bit of a hack, because wxNavigationKeyEvent is not documented. I had to work out the details by looking at the src/generic/panelg.cpp and src/msw/window.cpp in the wxWindows source code.

Actually, taking a more careful look through the panelg.cpp source, I realized that there was a much simpler and more reliable way. wxPanel.OnNavigationKey, which handles tab processing, checks to see if child.AcceptsFocus(), and skips to the next control if it doesn't. AcceptsFocus just checks if the control IsShown and IsEnabled (see include/wx/window.h in the wxWindows source). So, all you need to do is make sure that the myLabelAndTextCtrl is disabled whenever its underlying text control is disabled.

In your sample code above, you can do this just by adding

         self.Enable(enabled)

to myLabelAndTextCtrl.__init__.

In a more realistic example, you would of course have to make sure that whenever the text control was enabled/disabled, the panel would be enabled/disabled as well.

David

David C. Fox wrote:

James Shaw wrote:

Sorry about the delay in my reply. I have identified the problem when tabbing through enabled and disabled text ctrls.

If you put the control in a panel and disable one, tabbing works as expected - the caret moves between the two enabled ctrls.

If you use the sample code below, however, tabbing makes the caret disappear (the wxPanel containing the disabled control gets focus).

Here's the small piece of code:

--------------

from wxPython.wx import *

class myApplication(wxApp):
   def OnInit(self):
       # Create the GUI frames

       self.frame = myFrame(NULL, -1, "Test")
       self.SetTopWindow(self.frame)
       self.frame.Show(true)

       # All done
       return true

class myFrame(wxFrame):
   def __init__(self, parent, id, caption):
       wxFrame.__init__(self, parent, id, caption)
       panel = myPanel(self)

class myPanel(wxPanel):
   def __init__(self, parent):
       wxPanel.__init__(self, parent, -1)
       entry1 = myLabelAndTextCtrl(self, TRUE)
       entry2 = myLabelAndTextCtrl(self, FALSE)
       entry3 = myLabelAndTextCtrl(self, TRUE)

       sizer = wxBoxSizer(wxVERTICAL)
       sizer.Add(entry1, 0)
       sizer.Add(entry2, 0)
       sizer.Add(entry3, 0)

       self.SetAutoLayout(TRUE)
       self.SetSizer(sizer)
       sizer.Fit(self)

class myLabelAndTextCtrl(wxPanel):
   def __init__(self, parent, enabled):
       wxPanel.__init__(self, parent, -1)
       label = wxStaticText(self, -1, "Text")
       textCtrl = wxTextCtrl(self, -1)
       textCtrl.Enable(enabled)

       sizer = wxBoxSizer(wxHORIZONTAL)
       sizer.Add(label, 1, wxEXPAND)
       sizer.Add(textCtrl, 2, wxEXPAND)
       self.SetAutoLayout(TRUE)
       self.SetSizer(sizer)
       sizer.Fit(self)

if __name__ == "__main__":
   app = myApplication()
   app.MainLoop()

-------------------

Any suggestions on how to fix this?

You can add

    EVT_SET_FOCUS(self, self.OnFocus)

to myLabelAndTextCtrl.__init__, and then define self.OnFocus to check if the textCtrl.IsEnabled(). If not, you can set the focus to a different control, or post a NavigationKeyEvent to the parent panel to get it to send the focus on.

The tricky part is knowing whether the focus came from the previous control (e.g. via TAB) or the next control (e.g. via SHIFT-TAB). The following modifications to your myPanel and myLabelAndTextCtrl of your code work (at least with wxPython 2.3.2.1, Python 1.5.2 for Windows).

However, this solution requires you to manually tell each myLabelAndTextCtrl the ID of the previous control in the panel. Also, if the previous control was not a myLabelAndTextCtrl but a wxSomethingElse, you would also have to subclass wxSomethingElse and add a similar EVT_KILL_FOCUS handler.

...

Note: if ALL your text controls are disabled, the code about will lead to an infinite loop, so some error checking should be added to avoid that.

Also, the way I send the focus to the next control is a bit of a hack, because wxNavigationKeyEvent is not documented. I had to work out the details by looking at the src/generic/panelg.cpp and src/msw/window.cpp in the wxWindows source code.

Actually, taking a more careful look through the panelg.cpp source, I realized that there was a much simpler and more reliable way. wxPanel.OnNavigationKey, which handles tab processing, checks to see if child.AcceptsFocus(), and skips to the next control if it doesn't. AcceptsFocus just checks if the control IsShown and IsEnabled (see include/wx/window.h in the wxWindows source). So, all you need to do is make sure that the myLabelAndTextCtrl is disabled whenever its underlying text control is disabled.

In your sample code above, you can do this just by adding

         self.Enable(enabled)

to myLabelAndTextCtrl.__init__.

In a more realistic example, you would of course have to make sure that whenever the text control was enabled/disabled, the panel would be enabled/disabled as well.

David

Isn't it possible to get a more helpful indication that
a no longer exisiting window is called than "Access
violation"? I'd really see that as an improvement. Now
I sometime feel that wxPython is more like the harsh
and cold world of C++ than the warm and cuddly Python
I've gotten used to... :frowning:

I've been pulling my hair half day here, wondering why
the heck my app crashed with an access violation every
time I updated some objects after I had closed one of my
dialogs.

It turned out that my nifty Publish/Subscribe mechanism
jumped up and hit me in the back of my head. My dialogs
subscribed to certain events and "forgot" to detach themselves
when I closed them. So it's the same kind of problem as with
EVT_KILL_FOCUS calling windows which are no more...

Obviously, a "detach" call in the "attach"ing class in
OnCloseWindow is a Good Thing, but it would be nice if
I could find my stupids mistakes as easily as in ordinary
Python code... :slight_smile: This took a whole load of print
statements, and some serious thinkning before it occurred
to me that I did stuff in __setattr__...

···

--
Magnus Lyckå, Thinkware AB
Älvans väg 99, SE-907 50 UMEÅ
tel: 070-582 80 65, fax: 070-612 80 65
http://www.thinkware.se/ mailto:magnus@thinkware.se

[Magnus Lyckå]

[snip]

It turned out that my nifty Publish/Subscribe mechanism
jumped up and hit me in the back of my head. My dialogs
subscribed to certain events and "forgot" to detach themselves
when I closed them. So it's the same kind of problem as with
EVT_KILL_FOCUS calling windows which are no more...

Obviously, a "detach" call in the "attach"ing class in
OnCloseWindow is a Good Thing, but it would be nice if
I could find my stupids mistakes as easily as in ordinary
Python code... :slight_smile: This took a whole load of print
statements, and some serious thinkning before it occurred
to me that I did stuff in __setattr__...

Would weak reference callbacks that automatically did the detaching help
you? Take a look at this Publish/Subscribe recipe. It might save you some
headaches.

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/87056

···

---
Patrick K. O'Brien
Orbtech

I don't know. I had a feeling that I couldn't divine whether
a window was still alive via its object without touching it
in a way that will blow up if it's not. Does wek references
get around that? I thought you'd have to solve that on the
C++/SWIG level.

···

At 13:57 2002-03-28 -0600, Patrick K. O'Brien wrote:

Would weak reference callbacks that automatically did the detaching help
you? Take a look at this Publish/Subscribe recipe. It might save you some
headaches.

http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/87056

--
Magnus Lycka, Thinkware AB
Alvans vag 99, SE-907 50 UMEA, SWEDEN
phone: int+46 70 582 80 65, fax: int+46 70 612 80 65
http://www.thinkware.se/ mailto:magnus@thinkware.se

[Magnus Lyckå]

>Would weak reference callbacks that automatically did the detaching help
>you? Take a look at this Publish/Subscribe recipe. It might save you some
>headaches.
>
>http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/87056

I don't know. I had a feeling that I couldn't divine whether
a window was still alive via its object without touching it
in a way that will blow up if it's not. Does wek references
get around that? I thought you'd have to solve that on the
C++/SWIG level.

I'm not sure. I actually haven't had the time to do much with my dispatcher
after I wrote it. But I know there are a few people/projects using it.
Perhaps they can chime in. Or you could just try it and see. Are any of the
dispatcher users using weak references of wxPython objects?

···

At 13:57 2002-03-28 -0600, Patrick K. O'Brien wrote:

---
Patrick K. O'Brien
Orbtech

Isn't it possible to get a more helpful indication that
a no longer exisiting window is called than "Access
violation"? I'd really see that as an improvement. Now
I sometime feel that wxPython is more like the harsh
and cold world of C++ than the warm and cuddly Python
I've gotten used to... :frowning:

But when you wrap up the harsh cold world up with something warm and cuddly
it's real difficult to prevent a bit of cold from leaking out here and
there... <wink>

To answer your question I havn't thought of a way to do it that didn't have
a lot of overhead. But as you've seen, if you take a little care youcan
easily avoid the cold spots.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

> I don't know. I had a feeling that I couldn't divine whether
> a window was still alive via its object without touching it
> in a way that will blow up if it's not. Does wek references
> get around that? I thought you'd have to solve that on the
> C++/SWIG level.

I'm not sure. I actually haven't had the time to do much with my

dispatcher

after I wrote it. But I know there are a few people/projects using it.
Perhaps they can chime in. Or you could just try it and see. Are any of

the

dispatcher users using weak references of wxPython objects?

Weak references should work with anything derived from wxEvtHandler,
wxSizer, or wxShape, and as long there are no extra strong references held
to it. These classes are all OOR enabled which means that the C++ object
will hold a reference to the Python object and when the C++ object is
destroyed then that strong reference is removed, so if the only thing left
is a weakref then the Python object will be properly cleaned up.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

But when you wrap up the harsh cold world up with something warm and cuddly
it's real difficult to prevent a bit of cold from leaking out here and
there... <wink>

Or leaking in rather... The harsh cold world is out there... I know,
I've lived close to the polar circle for five years now...

To answer your question I havn't thought of a way to do it that didn't have
a lot of overhead. But as you've seen, if you take a little care youcan
easily avoid the cold spots.

And even if I'm semi-sloppy it won't occur often enough for me to get
used to the problem in six months...so it can't be such a big deal. :slight_smile:
(If the crash had occured just when I closed the window, and not later,
when I did something seemingly unrelated, I think I would have cought up
sooner.) But I managed to understand what the problem was before I made
a complete fool of myself by mailing the list about the silly theories
I had before I looked in __setattr__... (I refuse to tell you about
them--but I was certainly confused...)

Anyway, in the large majority of bugs I run into with Python and
wxPython there will be a stack trace that will tell me almost
exactly where the problems lie. I've almost forgotten how to use
a debugger... This time it would probably have been a help...

Many thanks for all your work Robin. And for your patient replies.

/Magnus

···

At 10:59 2002-03-29 -0800, Robin wrote:

--
Magnus Lycka, Thinkware AB
Alvans vag 99, SE-907 50 UMEA, SWEDEN
phone: int+46 70 582 80 65, fax: int+46 70 612 80 65
http://www.thinkware.se/ mailto:magnus@thinkware.se

All-in-all, I spend probably 20 or 30% of my wxPython time tracking down these types of errors, so I'm willing to bat around ideas for a while :slight_smile: .

What about having the C++ destructor make the instance an instance of a class that just reports an error on every attempt to access the object. If I understand OOR correctly, that might be added to an existing destructor template that's releasing the reference to the Python object. [e.g.]

>>> class Deleted:
... def __repr__( self ):
... return 'wxPython wrapper for deleted object!!! Programming logic error'
... def __getattr__( self, *arguments ):
... raise ValueError( """Attempt to access attribute of a deleted wxPython object""" )
...
>>> class X:
... def f(self):
... print 'yes'
...
>>> x = X()
>>> x.__dict__.clear()
>>> x.__class__ = Deleted
>>> x.f
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 5, in __getattr__
ValueError: Attempt to access attribute of a deleted wxPython object
>>> x.r
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 5, in __getattr__
ValueError: Attempt to access attribute of a deleted wxPython object
>>> x
wxPython wrapper for deleted object!!! Programming logic error
>>>

Now, realistically, you'd still want the object to know what the original class of the object was, so you'd likely want to make that an instance attribute (after the .clear() call), but that's just details. This is going to slow down this version of wxPython, but I wouldn't think it would be too noticable (an extra couple of assignments, a clear of a dict that should be cleared anyway when the object goes out of scope).

Making it an easy-to-trigger functionality (sep. wxPython install?) would be hard I suppose, but might be worth it.

Okay, I'll let it go :slight_smile: ,
Mike

Robin Dunn wrote:
...

To answer your question I havn't thought of a way to do it that didn't have
a lot of overhead. But as you've seen, if you take a little care youcan
easily avoid the cold spots.

...

All-in-all, I spend probably 20 or 30% of my wxPython time tracking down
these types of errors, so I'm willing to bat around ideas for a while :slight_smile: .

What about having the C++ destructor make the instance an instance of a
class that just reports an error on every attempt to access the object.
  If I understand OOR correctly, that might be added to an existing
destructor template that's releasing the reference to the Python object.

A good idea but I don't think it's possible. Yes I can change the object
that the C++ object refers to but not the one that all the other variables
refer to. I don't think there is a safe way to change a Python object's
type "in-place" is there?

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

Well, certainly in Python all you'd need to do is set the instance.__class__ member to point to the stubbed Python class. Don't know whether you'd consider that "safe", but it certainly seems to work with Python 2.2. Given that wxPython is using wrapper Python classes (as opposed to object inheriting from built-in types), it would seem workable.

i.e. the C equivalent of

setattr( my_Python_instance, '__class__', StubClassPointer )

But I might be missing something...

Enjoy,
Mike

Robin Dunn wrote:
...

What about having the C++ destructor make the instance an instance of a
class that just reports an error on every attempt to access the object.
If I understand OOR correctly, that might be added to an existing
destructor template that's releasing the reference to the Python object.

A good idea but I don't think it's possible. Yes I can change the object
that the C++ object refers to but not the one that all the other variables
refer to. I don't think there is a safe way to change a Python object's
type "in-place" is there?

...

Well, certainly in Python all you'd need to do is set the
instance.__class__ member to point to the stubbed Python class. Don't
know whether you'd consider that "safe", but it certainly seems to work
with Python 2.2. Given that wxPython is using wrapper Python classes
(as opposed to object inheriting from built-in types), it would seem
workable.

i.e. the C equivalent of

setattr( my_Python_instance, '__class__', StubClassPointer )

But I might be missing something...

Right, not sure why I didn't think of __class__... I'll give it a try.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

What about having the C++ destructor make the instance an instance of a
class that just reports an error on every attempt to access the object.
  If I understand OOR correctly, that might be added to an existing
destructor template that's releasing the reference to the Python object.

How's this:

Python 2.2 (#28, Jan 2 2002, 13:40:06) [MSC 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.

from wxPython.wx import *
app = wxPySimpleApp()
f = wxFrame(None, -1, "Hello", size=(300,250))
p = wxPanel(f, -1)
f.Show()

1

print f

<C wxFrame instance at _103f138_wxFrame_p>

f.SetTitle("New Title")
app.MainLoop()
print f

wxPython wrapper for deleted wxFrame object!!! Programming logic error

print p

wxPython wrapper for deleted wxPanel object!!! Programming logic error

f.SetTitle("Another Title")

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "..\wxPython\wx.py", line 1574, in __getattr__
    raise ValueError, 'Attempt to access attribute of a deleted %s object' %
self._name
ValueError: Attempt to access attribute of a deleted wxFrame object

It should work for any class that is managed by OOR, which currenlty is
anything derived from wxEvtHandler, wxSizer, or OGL's wxShape.

The implementation was surprisingly easy. Thanks for the idea Mike!

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

This is very good. Will it be part of 2.3.3 final?

By the way, I'm not pathologically attached to the implementation of the "stub" class I posted. In particular, if you feel like changing the messages, raising different errors etc. that would be cool with me. I was thinking that adding a __nonzero__ method to the class would let the developer test for "if f: doSomething" without needing to catch an attribute error. Making the raised error a subclass of the standard error might also allow for catching just those errors associated with a deleted object (while still allowing the generic error to serve as a catch-all).

Thanks Robin, this is really great news.
Mike

Robin Dunn wrote:
...

f.SetTitle("Another Title")

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "..\wxPython\wx.py", line 1574, in __getattr__
    raise ValueError, 'Attempt to access attribute of a deleted %s object' %
self._name
ValueError: Attempt to access attribute of a deleted wxFrame object

It should work for any class that is managed by OOR, which currenlty is
anything derived from wxEvtHandler, wxSizer, or OGL's wxShape.

...

Many thanks to both of you. I think this will both lead to
better wxPython programs and to a more developer friendly
wxPython, particularly for beginners.

There are certainly many situations where it's much more
convenient to fix problems as they occur instead of
eliminating them in advance. Why else would we have exceptions
at all?

The idea about __nonzero__ seems like a good idea. (Returning 0
I presume?) Checking the windows with "if window" was certainly
my first attempt when I ran into the problems with possibly dead
windows.

Very Pythonesque!

···

At 14:21 2002-04-09 -0700, Robin Dunn wrote:

It should work for any class that is managed by OOR, which currenlty is
anything derived from wxEvtHandler, wxSizer, or OGL's wxShape.

The implementation was surprisingly easy. Thanks for the idea Mike!

--
Magnus Lycka, Thinkware AB
Alvans vag 99, SE-907 50 UMEA, SWEDEN
phone: int+46 70 582 80 65, fax: int+46 70 612 80 65
http://www.thinkware.se/ mailto:magnus@thinkware.se

This is very good. Will it be part of 2.3.3 final?

Yes. I'll also do another preview (at least one) that has it in there so
people can test it.

By the way, I'm not pathologically attached to the implementation of the
"stub" class I posted. In particular, if you feel like changing the
messages, raising different errors etc. that would be cool with me.

Yep. It's a pure Python class so it's easy to change. You can even replace
it with your own class by assigning something to wx._wxPyDeadObject.

I
was thinking that adding a __nonzero__ method to the class would let the
developer test for "if f: doSomething" without needing to catch an
attribute error.

This is a good idea.

Here is the class as it is currently:

class wxPyDeadObjectError(AttributeError):
    pass

class _wxPyDeadObject:
    """
    Instances of wx objects that are OOR capable will have their __class__
    changed to this class when the C++ object is deleted. This should help
    prevent crashes due to referencing a bogus C++ pointer.
    """
    reprStr = "wxPython wrapper for DELETED %s object! (The C++ object no
longer exists.)"
    attrStr = "The C++ %s object has been deleted, attribute access no
longer allowed."

    def __repr__( self ):
        if not hasattr(self, "_name"):
            self._name = "[unknown]"
        return self.reprStr % self._name

    def __getattr__( self, *args ):
        if not hasattr(self, "_name"):
            self._name = "[unknown]"
        raise wxPyDeadObjectError( self.attrStr % self._name )

    def __nonzero__(self):
        return 0

Any suggestions? BTW, self._name is set in the C++ code when the __class__
of the instance is replaced with _wxPyDeadObject.

···

--
Robin Dunn
Software Craftsman
robin@AllDunn.com Java give you jitters?
http://wxPython.org Relax with wxPython!

Not much comes to mind as improvements.

Thinking ahead, I'll probably use another version of _wxPyDeadObject during development where instead of raising an error the debugger (pdb) is started when the condition occurs. Makes the wx._wxPyDeadObject setting very handy.

Enjoy, and thanks again, :slight_smile: ,
Mike

Robin Dunn wrote:
...

Any suggestions? BTW, self._name is set in the C++ code when the __class__
of the instance is replaced with _wxPyDeadObject.

...