Wednesday, October 04, 2006

Suspending WithEvents Handlers

Sometimes you want to call methods on a WinForms control (for example) but not have it raise events, e.g. to prevent recursive calls. For handlers you've added yourself with AddHandler, the answer is obvious: remove the handler and re-add it when you've done your thing. However, in VB you've usually got automatically generated event handlers hooked up to WithEvents fields with the Handles keyword. What do you do about these? Is it OK to call Remove/AddHandler in the usual way?

(BTW I think the alternative technique of setting an "ignore events" flag is to be discouraged because it has to be tested in all of the handlers; by using Add/RemoveHandler you're keeping the code close to the point that requires it.)

The WithEvents section in the Language Spec is quite revealing on this point.

Basically, if you declare a WithEvents variable called x, what you actually get is a private variable called _x and a property called x that looks like this:

Friend Overridable Property x() As TextBox


        Return _x

    End Get

    Set(ByVal value As TextBox)

        If _x IsNot Nothing Then

            'Remove handlers from _x

        End If

        _x = value

        If _x IsNot Nothing Then

            'Add handlers to _x

        End If

    End Set

End Property

The list of handlers to add and remove is generated from all the methods that have a "Handles" clause for an event of this instance. If a WithEvents field has no handlers, the wrapper property is still generated but its setter simply sets the value of the hidden field.

Knowing this, you can temporarily suspend all event handling (other than via handlers you explicitly added yourself) with code like this:

Dim temp As TextBox = x

x = Nothing

temp.AppendText()   'Anything that would normally raise an event

x = temp

Caveat: the code between setting x to Nothing and restoring it can't call any methods that need to access x; also, this code should be wrapped in a Try/Finally to make sure the field is always reset to its original value.

No comments: