Rockford Lhotka

 Friday, September 23, 2005
« Global exception logging | Main | More on the Windows Forms data binding i... »

 

I recently discovered an issue with Windows Forms data binding when a form is bound against a smart data container like a DataTable with partial class code or a CSLA .NET –style object. The issue is pretty subtle, but nasty – and my current workaround doesn’t thrill me. So after reading this if you have a better work around I’m all ears – otherwise this could end up in the books I’m current writing, since both are impacted… :(

 

So here’s the problem:

 

Create a smart data source – defined as one that includes business logic such as validation and manipulation of data. In this case, it is the manipulation part that’s the issue. For instance, maybe you have a business rule that a given text field must always be upper case, and so you properly implement this behavior in your business object or partial class of your DataTable to keep that business logic out of the UI.

 

In a business object for instance, this code would be in the property set block – perhaps something like this in a CSLA .NET 2.0 style class:

 

  Public Property LastName() As String

    Get

      VerifyGetProperty("LastName")

      Return mLastName

    End Get

    Set(ByVal value As String)

      VerifySetProperty("LastName")

      If mLastName <> value Then

        mLastName = value.ToUpper

        PropertyHasChanged("LastName")

      End If

    End Set

  End Property

 

(in CSLA .NET 2.0 the PropertyHasChanged method raises the appropriate PropertyChanged event via the INotifyPropertyChanged interface and also calls MarkDirty)

 

Or a simpler class that implements INotifyPropertyChanged:

 

  Public Property LastName() As String

    Get

      Return mLastName

    End Get

    Set(ByVal value As String)

      mLastName = value.ToUpper

      RaiseEvent PropertyChanged(Me, _

        New PropertyChangedEventArgs("LastName"))

    End Set

  End Property

 

Specifically notice that the inbound value is changed to upper case with a ToUpper call in the set block.

 

Now add that data container to the Data Sources window and drag it onto your form in Details mode. Visual Studio nicely creates a set of controls reflecting your properties/columns (damn I love this feature!!) and sets up all the data binding for you. So far so good.

 

Now run the application and enter a lower case value into the Last Name text box and tab off that control. Data binding automatically puts the new value into the object and runs the code in the set block. This means the object now contains an upper case version of the value you entered.

 

Notice that the TextBox control does not reflect the upper case value. The value from the object was never refreshed in the control.

 

Now change another value in a different control and tab out. Notice that the Last Name TextBox control is now updated with the upper case value.

 

So that’s the problem. Data binding updates all controls on a form when a PropertyChanged event is raised, except the current control. You can put a breakpoint in the property get block and you’ll see that the value isn’t retrieved until some other control triggers the data binding refresh.

 

I did report this bug to Microsoft, but it has been marked as postponed - meaning that it won't get fixed for release. An unfortunate side-issue is that this issue makes data binding in 2.0 work differently than in .NET 1.x, so any .NET 1.x code that binds against a smart data container like a business object will likely quit working right under 2.0.

 

Now to my workaround (of which I’m not overly proud, but which does work). My friend Ed Ferron should get a serious kick out of this – feel free to laugh all you’d like Ed!

 

The problem is that data binding isn’t refreshing whatever control is currently being edited when the PropertyChanged event is raised. The obvious question then is how to get a PropertyChanged event raised after the property set block has completed. Because at that point data binding will actually refresh the control.

 

The easiest way to get some action to occur “asynchronously” without actually using multi-threading (which would be serious overkill) is to use the System.Windows.Forms.Timer control. That control runs on your current thread, but provides a simulation of asynchronous behavior. (On the VAX this was called an asynchronous trap, so the concept has been around for a while!)

 

Putting the Timer control into your data object is problematic. The Timer control implements IDisposable, so your object would also need to implement IDisposable – and then any objects using your object would need to implement IDisposable. It gets seriously messy.

 

Additionally, a real application may have dozens or hundreds of objects. They can’t each have a timer – that’d be nuts! And the OS would run out of resources of course.

 

But there’s a solution. Use a shared Timer control in a central location. Like this one:

 

Public Class Notifier

 

#Region " Request class "

 

  Private Class Request

 

    Public Method As Notify

    Public PropertyName As String

 

    Public Sub New(ByVal method As Notify, _

      ByVal propertyName As String)

 

      Me.Method = method

      Me.PropertyName = propertyName

 

    End Sub

 

  End Class

 

#End Region

 

  Public Delegate Sub Notify(ByVal state As String)

 

  Private Shared mObjects As New Queue(Of Request)

  Private Shared WithEvents mTimer As New System.Windows.Forms.Timer

 

  Public Shared Sub RequestNotification(ByVal method As Notify, _

    ByVal propertyName As String)

 

    mObjects.Enqueue(New Request(method, propertyName))

    mTimer.Enabled = True

 

  End Sub

 

  Shared Sub New()

 

    mTimer.Enabled = False

    mTimer.Interval = 1

 

  End Sub

 

  Private Shared Sub mTimer_Tick(ByVal sender As Object, _

    ByVal e As System.EventArgs) Handles mTimer.Tick

 

    mTimer.Enabled = False

    While mObjects.Count > 0

      Dim request As Request = mObjects.Dequeue

      request.Method.Invoke(request.PropertyName)

    End While

 

  End Sub

 

End Class

 

This could alternately be implemented as a singleton object, but the usage syntax for this is quite nice.

 

This Notifier class really implements the RequestNotification method, allowing an object to request that the Notifier call the object back in 1 tick of the clock (about 100 nanoseconds) on a specific method.

 

The reason the class maintains a list of objects to notify is because even in a single threaded application you can easily envision a scenario where multiple notification requests are registered within 100 nanoseconds – all you need is programmatic loading of data into an object.

 

Now your data object must implement a method matching the Notify delegate and register for the callback. For instance, here’s the updated code from above:

 

  Public Property LastName() As String

    Get

      Return mLastName

    End Get

    Set(ByVal value As String)

      mLastName = value.ToUpper

      Notifier.RequestNotification(AddressOf Notify, "LastName")

    End Set

  End Property

 

  Public Sub Notify(ByVal propertyName As String)

    RaiseEvent PropertyChanged(Me, _

     New PropertyChangedEventArgs(propertyName))

  End Sub

 

Rather than raising the event directly, the property set block now asks the Notifier to call the Notify method in 1 tick. So 1 tick later – after the property set block is complete and data binding has moved on – the Notify method is called and the appropriate PropertyChanged event is raised.

 

With this change (hack) data binding will now refresh the Last Name TextBox control as you tab out of it. Though there’s a 100 nanosecond delay in there, it isn’t something the user can actually see and so you can argue it doesn’t matter.

 

I can hear Ed laughing already…