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…


Friday, September 23, 2005 6:44:00 PM (Central Standard Time, UTC-06:00)
Hey rocky see my post:
http://www.shoddycoder.net/PermaLink,guid,e1874343-ac5c-4b72-b38e-b4866f6a05fd.aspx
Friday, September 23, 2005 11:18:40 PM (Central Standard Time, UTC-06:00)
Shoddy, I'm merely trying to address the problem using standard data binding. It sounds like you are doing keypress-level binding, which has its own set of problems...

Certainly waiting until data access occurs is a viable solution in some cases. In my case however, I'm using my SmartDate class, which translates things like "t" into today's date and so leaving the "t" in the field is very confusing to the user.

And really I find the whole idea of deferring validation or manipulation of data until later to be an unfortunate solution. If I want to give the user a block-mode experience I'll use the web :)
Saturday, September 24, 2005 3:25:36 AM (Central Standard Time, UTC-06:00)
Hey Rocky.

I'm not laughing at your solution. I'm crying. Not at what your hack was, but the fact you had to write a hack at all. That's honestly truly disgusting that it's been postponed. I can understand why it's been postponed given how late we are in the cycle, but it defeats a large portion of the reason for introducing the INotifyPropertyChanged interface in the first place!

Saturday, September 24, 2005 9:11:10 AM (Central Standard Time, UTC-06:00)
Rocky, I'm not sure what you mean by key-press binding versus "standard" data binding. I might mention that we are using CSLA, be it a somewhat modified version as it is... that said, it seems that adding a binding to a control would be fairly standard; if you are talking about the "new" standard of using the binding datasource and having the designer build out your form / controls based on your business object; then I guess we're not standard. While I like that feature, beta 2 simply wasn't stable enough for us to use that feature as our data sources would disappear, controls would disappear and we'd be left with references to controls that no longer exist and couldn't add them back in until after we removed the referneces (designer code). Among other issues... we've quite elegantly (imo) handled all the issues of binding that just couldn't be elegantly handled in previous versions of VS. Microsoft clearly missed the mark on this one issue, and so we trade off functionality... I hardly think our application to be block mode or give even close to an experience of that on the web (and you have seen it, but maybe you forgot ; - ) We do field level validation, and it is done as they type, type an "A" in a currency field and the error provider will immediately flash since that is invalid. Break a business rule and the object's broken rules will reflect that. So I hardly consider that block mode...
The date example that you gave, can easily be handled using the parse / format methods in binding... be it "non standard" as it may be :)

I might also mention, that with the modifications we've made, our problem is not one that the current textbox doesn't get updated when you change the case or trim a space, the problem is that MSFT didn't design the framework to expect a change to what you are entering such as this... so in our framework, were you to do this, the textbox DOES get updated with the UPPER case, however the cursor position gets set to 0. So if you could type the word sdrowkcab (backwords), you'd be good to go :)

I should also mention one other reason for us adding our bindings is that we do handle the translation of our null representation (returning 0 for null is unacceptable to our users, since null means not set and 0 means set intentionally and it should have zero value.... so not set could mean brokenrule) between the display and the business object. Microsoft finally address this as a real business need in backfilling the "non standard" databinding with the DataSourceNullValue property which imo is one of the best features added to databinding in this release. It enables the theoretical to work.

Shoddy
Saturday, September 24, 2005 9:38:09 AM (Central Standard Time, UTC-06:00)
Apparently I totally misunderstood your blog entry, what you are saying now is more clear.

My primary goals are (I think) different from yours. My goals are to fully exploit the drag-and-drop data binding in Windows Forms 2.0, and to minimize code in the UI. I view all code that is in a form as being bad. Obviously some code is required, but the less the better imo.

Barring the need to format/parse specific fields (which of course happens from time to time), I have it now so a typical form only requires less than 10 lines of code (including exception handling). I am contemplating ways to shrink that code as well, but I'm not sure I can go further, as the exception handling is often custom per-form...

Operations such as upper casing input are business logic - assuming the value really has to be upper case for the system to function properly. In my world view, business logic ABSOLUTELY must not be in the form, but rather must be in a non-UI-specific business layer. In the case of CSLA that is business objects. In the case of DataSets it would be the partial class of your DataTable.

And today in 1.x that is totally possible, but in 2.0 they changed the behavior so the current field isn't updated from the data source - effectively preventing you from putting business logic anywhere but in the UI... To me that is entirely unacceptable, as it is a violation of what I consider the most important best practice in application design.

I'm not pleased with my timer hack because it is so very obviously a hack. However, I've been unable to come up with an alternative that accomplishes my primary goals without adding method calls from the UI into the object or other approaches that bloat the coding effort required on the part of either the UI layer or business layer developers.

The Timer approach, hack though it is, only makes the business layer developer write 3 extra lines of code in each object. In the case of CSLA .NET 2.0 _no_ extra coding will be required because the workaround will be coded entirely into the framework (where that sort of thing belongs).
Saturday, September 24, 2005 3:20:07 PM (Central Standard Time, UTC-06:00)
Wow, this is in deed a pretty lame limitation of the new Windows.Forms databinding. The irony is that it almost seems backwards. In other words, if the only control updated when a property changed was the respective control(s) bound to that property, you would almost be better off. That way, if changing some prop on a BO also causes the BO to internally change some other props, then each of these props could raise their respective property changed events, and all would be good. There is probably something else that would be missed if this were the case, but on the surface it almost seems backwards.

As to the hack as is, there is just something that bothers me about an internal Windows.Forms.Timer inside a BO framework ;-) Especially, when the framework is used as a foundation for Business Objects that can theoretically run in many different host; asp.net, console app, windows service, ES. The problem is really a problem of the Windows.Forms domain, so it seems the hack should be on that side of the fence. I can definitely appreciate your previous comment about not wanting to cause extra UI work just to enable proper usage of the BO. Perhaps, some special BOForms base class that UI developers building windows forms could inherit from that does the extra step to rebind a changing controls value. The nature of the problem however, might just make this impossible.

In your previous versions of CSLA framewok, you created a generic IsDirtyChanging event, and I believe the hack to make Windows Forms binding 1.x work was a IsDirtyChanging control located off the visible area of the form and bound to this property. I know this was a slightly different problem and hack however, after reading this blog I toyed with the idea of similar hack. The idea being that the BO base object’s private PropertyHasChanged method, would raise 2 events, 1 being the property change event for the actual property that changed, and another for a more generic BusinessObjectState property (similar I guess to a generic, IsDirtyChanging).

This would require some minimal work however on each UI Form. After dropping the datasource for a BO on the form, you would need to give the BusinessObjectState field a location of something unseen like (-100,-100); similar to IsDirtyChanging ). Then, some minimal but repeatable work would need to be pasted into each form.vb. Something along the lines of:

Private Delegate Sub RebindThisLameBindingSourceDelegate(ByVal source As BindingSource)

Private Sub BusinessObjectStateTextBox_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles BusinessObjectStateTextBox.TextChanged
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf BeginRebindThisLameBindingSource, DirectCast(sender, Control).DataBindings.Item(0).DataSource)
End Sub

Public Sub BeginRebindThisLameBindingSource(ByVal state As Object)
Dim args(0) As Object
args(0) = state
Me.Invoke(New RebindThisLameBindingSourceDelegate(AddressOf RebindThisLameBindingSource), args)
End Sub

Public Sub RebindThisLameBindingSource(ByVal source As BindingSource)
source.ResetBindings(False)
End Sub

There is probably a way to get this to even less code with a little more thought and refactoring. The real trick would be to somehow, get the wiring up of this queued rebind to take place through some BOFormsBase inheritance, or at least something along those lines.

I guess it’s just the idea of fixing a windows forms problem inside a BO that bothers me some. On the flip side, you’ve effectively done it in a way that causes no code to be written by the UI developer, which is awfully important too. So, maybe I just need time for the Timer to click in; pun intended ;-)
Saturday, September 24, 2005 3:25:32 PM (Central Standard Time, UTC-06:00)
"I view all code that is in a form as being bad. Obviously some code is required, but the less the better imo."

I agree to an extent, but with our uber forms and the functionality that drove us to smart client to begin with (versus the web) not writing code is not a possibility ...yet. That said, we've achieved an 80-90% code reduction in the UI.

Yep, I agree Uppercasing is business logic and I'd never do that in that in the UI...totally unacceptable. On the other hand, a business object converting T to today's date... that could be debated for a while, me personally; I'd rather have a strongly typed object that exposes a date type instead of the immutable string. If I wanted the T conversion, I'd support it through the binding infrastructure in our base form (yeah I said it, I'd put that in my UI :)

Still looking forward to reading the next book... well, the BO one... the dataset one I'm afraid won't make it to my library.

Hacks are hacks, and they are necessary... my only argument is that I usually try to put the hack in the layer that caused my problem. Putting it into the BL because of a UI problem... I'd prefer to see it in the UI since that is where the bug resides... but I'd not defend my position too long on this :|
Saturday, September 24, 2005 3:31:43 PM (Central Standard Time, UTC-06:00)
Scott beat me to posting about the location... with 2 of us on the debate team, this argument might have legs :)
Monday, September 26, 2005 10:37:17 PM (Central Standard Time, UTC-06:00)
I would argue that to change the business logic layer to hack the UI isn't really the way to go.

I would probably argue that doing something similar to what Scott is mentioning would be more stable.

You also had a routine in the 1.1 Framework for rebinding the object to the control, clearing the old binding before rebinding the control. I would probably lean more towards a workaround that would have me add a handler to those values I have bound and handle the leave event. This might be accomplished using reflection but I haven't gotten that far in my tests yet...


A second issue though...
Your Set Code is short but as a pattern leaves something to be desired. Have you thought about going to a slightly different pattern? Maybe as a mention of a different naming scheme.

Instead of
VerifySetProperty("LastName")
If mLastName <> value Then
mLastName = value.ToUpper
PropertyHasChanged("LastName")
End If

You would have
Set(ByVal Value As String)
'Bail Outs
If mLastName.Equals(Value) Then Exit Property

'Any property specific pre-change operations should be done here

Dim args As New PropertyChangeArguments("LastName", mLastName, Value)

'Perform possible pre-change operations (Auditing, Data Validation and Manipulation...)
BeforePropertySet(Me, args)
If args.Cancel Then Exit Property

'Change the value and perform post-change operations (MarkDirty, CheckPropertyRules, NotifyPropertyChange...)
mLastName = Value.ToUpper
AfterPropertySet(Me, args)

'Any property specific post-change operations should be done here
End Set
End Property

Where the BeforePropertySet routine would be in the Business base and look like
Protected Overridable Sub BeforePropertySet(item as BusinessBase, e as PropertyChangeArguments)
VerifySetProperty(e.PropertyName) 'whatever this is doing, do you mean Validate?
End Sub

and
Protected Overridable Sub AfterPropertySet(item as BusinessBase, e as PropertyChangeArguments)
MarkDirty 'Or MarkChanged where IsDirty = IsChanged OrElse IsNew
OnAfterPropertyChanged(item, e)
OnIsDirtyChanged(item, System.EventArgs.Empty)
CheckPropertyRules(e.PropertyName)
'...
End Sub

Wouldn't it make the code a little clearer in the property and divide the functionality up slightly? This would allow the inheriting class much more flexibility and finer graining in what should happen in an individual class.

Just a thought.
Ben Cline
Monday, September 26, 2005 11:55:46 PM (Central Standard Time, UTC-06:00)
Not to be a jerk, but doesn't ANYONE remember PostMessage these days? I haven't done any real WinForms work (I'm an ASP.Net guy and BLL guy), but surely you can post a message to yourself to be handled during the next message loop processing that can do the control updates. Why use a timer?
Tuesday, September 27, 2005 10:00:18 AM (Central Standard Time, UTC-06:00)
PostMessage is a decent solution. You should develop a DataGrid descendant that does the trick, or use some third party grid. Standard Datagrid from .net is purely an abomination....
liviu.u@hotmail.com (Liviu Uba)
Wednesday, September 28, 2005 11:12:43 AM (Central Standard Time, UTC-06:00)
The issue isn't with grids - it is with detail forms. And I worked with the data team at Microsoft to get a much better answer in any case - see the later blog post for details.
Friday, January 20, 2006 1:55:09 PM (Central Standard Time, UTC-06:00)
Try the following solution. Add the 8 lines of code below to the Forms, BusinessObject PropertyChanged Event and that's it.
I unitaily tried using binding.ReadValue() but that did not work, however swapping the FormattingEnabled property value works a treat + It's only 8 lines of code (one line if you create it as a method). If you look at the FormattingEnabled using a reflector you will see that the FormattingEnabled is not a very heavy call (it's eventually calls the same method as ReadValue() does).

If sender IsNot Nothing Then
For Each binding As Binding In Me.ControlCreator1.BindingContext.Item(sender).Bindings
If binding.IsBinding AndAlso binding.BindingMemberInfo.BindingMember = e.PropertyName Then
binding.FormattingEnabled = Not binding.FormattingEnabled
binding.FormattingEnabled = Not binding.FormattingEnabled
End If
Next
End If

Regards
Neal
Neal
Comments are closed.