Simple Databinding using the Forms Designer

Simple Databinding using the Forms Designer

Old forum URL: forums.lhotka.net/forums/t/446.aspx


BBM posted on Thursday, June 22, 2006

Hi everyone,

Does anyone know how to do Simple Databinding using the Forms Designer in VS?  For example, bind "IsDirty" of a BusinessBase or BusinessListBase BO to the "enabled" property of a button on the form?

Thanks.

BBM

Brian Criswell replied on Thursday, June 22, 2006

You cannot.  The IsDirty property on BusinessBase is marked [Browseable(false)].  For BusinessListBase, databinding will try to bind to IsDirty on one of the objects in the list instead of the list itself.  You need to create a wrapper object that accepts a BusinessBase or BusinessList base and passes through the IsDirty value and Raises a PropertyChanged event whenever IsDirty changes.

RockfordLhotka replied on Thursday, June 22, 2006

IsDirty is marked as Browsable(False), so it isn't directly eligible for data binding. This is true of IsValid and IsSavable as well. What I suggest as a solution is to create a UI helper class like the one below. In your form you create an instance of this object and use it as a binding source for your controls (buttons, etc).

Imports System.ComponentModel

 

Public Class EventObserver

 

  Implements INotifyPropertyChanged

 

  Public Event PropertyChanged( _

    ByVal sender As Object, _

    ByVal e As System.ComponentModel.PropertyChangedEventArgs) _

    Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

 

  Private mObject As Csla.Core.IEditableBusinessObject

  Private WithEvents mNotifyObject As INotifyPropertyChanged

 

  Private mIsValid As Boolean

  Private mIsDirty As Boolean

  Private mIsSavable As Boolean

 

  Public Sub New(ByVal obj As Csla.Core.IEditableBusinessObject)

 

    mObject = obj

    ' init event handling

    mNotifyObject = CType(obj, INotifyPropertyChanged)

    ' init fields

    mIsValid = obj.IsValid

    mIsDirty = obj.IsDirty

    mIsSavable = obj.IsSavable

 

  End Sub

 

  Private Sub mNotifyObject_PropertyChanged( _

    ByVal sender As Object, _

    ByVal e As System.ComponentModel.PropertyChangedEventArgs) _

    Handles mNotifyObject.PropertyChanged

 

    Dim validChanged As Boolean

    Dim dirtyChanged As Boolean

 

    If mIsValid <> mObject.IsValid Then

      mIsValid = mObject.IsValid

      RaiseEvent PropertyChanged( _

        Me, New PropertyChangedEventArgs("IsValid"))

    End If

 

    If mIsDirty <> mObject.IsDirty Then

      mIsDirty = mObject.IsDirty

      RaiseEvent PropertyChanged( _

        Me, New PropertyChangedEventArgs("IsDirty"))

    End If

 

    If (validChanged OrElse dirtyChanged) _

      AndAlso mIsSavable <> mObject.IsSavable Then

 

      mIsSavable = mObject.IsSavable

      RaiseEvent PropertyChanged( _

        Me, New PropertyChangedEventArgs("IsSavable"))

    End If

 

  End Sub

 

  Public ReadOnly Property IsValid() As Boolean

    Get

      Return mObject.IsValid

    End Get

  End Property

 

  Public ReadOnly Property IsDirty() As Boolean

    Get

      Return mObject.IsDirty

    End Get

  End Property

 

  Public ReadOnly Property IsSavable() As Boolean

    Get

      Return mObject.IsSavable

    End Get

  End Property

 

End Class

 

Dave.Erwin replied on Wednesday, July 19, 2006

I've got this implemented but I'm confused about one thing. What sets validChanged and dirtyChanged in mNotifyObject_PropertyChanged?

Thanks,

Dave Erwin

Brian Criswell replied on Wednesday, July 19, 2006

There is only PropertyChanged.  IsValid and IsDirty are computed when needed.

RockfordLhotka replied on Monday, July 31, 2006

I just got off the phone with a friend of mine at Microsoft. We did some testing and determined for certain that binding doesn't work when browsable(false) is on the property. Yes, you all knew this, but he thinks it is actually a bug, so it might get fixed in the future at some point. Browsable(false) is only supposed to surpress the IDE, not the runtime, but it is doing both.

So that doesn't help us really, because even if it got fixed, it wouldn't be for months or longer...

The solution I have on my web site is certainly workable - handling the bindingsource control's CurrentItemChanged event.

http://www.lhotka.net/Article.aspx?area=4&id=5faaee8e-8496-4845-86f7-787c6b64096c

But we got to talking further. My friend thinks that I should take off the browsable(false) attributes on the IsXYZ methods (IsNew, IsDeleted, IsSavable, IsDirty, IsValid) so people can bind to them as desired. And I could do that.

BUT, the reason I made them browsable(false) to start with is that usually you don't want to bind to them. And in that case you have to set the default control to none in the Data Sources window, which is a manual process and can be a serious PITA if you have a lot of business classes.

My friend then pointed out that just getting a lot of business classes into the Data Sources window is a tedious, manual process in the first place. His idea, and I think it may be a good one, is to create a utility that would reflect against a business assembly and create .datasource files for each business class. Such a utility could use a custom attribute so it would know to pre-define which controls should be used for each property. The IsXYZ methods could have this custom attribute to tell the utility that "none" should be defaulted for them. Then they'd be bindable, but they wouldn't bind by default - at least through drag-and-drop.

This utility would also need to modify the project file (presumably for the UI project) to add the references to the .datasource files. The end result is that the Data Sources window would be auto-populated with all your classes, and the IsXYZ properties would be auto-set to use no control.

Taking this idea one step further, this new custom attribute could be abstract. Rather than using it to specify actual control types (Windows.Forms.TextBox, etc.), it could just say "TextBox" and a translation table could be used to translate that to the right control for Windows Forms, and a different control for WPF (when WPF gets equivalent designer support). The attribute would merely suggest the broad control type, and the translation would be used to get the specific control type.

I don't have time to tackle this idea right now, but I did want to put it out here on the forum. That way the idea is available, and anyone wanting to build such a utility could certainly do so - and maybe put it on CSLAcontrib to share with others.

BBM replied on Thursday, June 22, 2006

Hi Brian,

Thanks for the tip.  I was able to get your suggestion to work, but it seems overly complex to me to have to add an additional class for each BO type - just so I can bind to some boolean properties to enable / disable buttons.

Generics may help me here (I guess potentially you could get by with just two wrapper classes, one for BusinessBase BO's and one for BusinessListBase), but I'll need to study some more to make this approach work.  Two extra classes wouldn't be too bad, but it still rubs me the wrong way.

Are there any other approaches to doing this?  

BTW, my "wrapper" class (to bind to a BusinessListBase derived class) follows for anybody interested.  Comments / criticisms are welcomed.

Thanks again.

BBM

Imports System.ComponentModel

Public Class BindableWrapper

   Inherits Csla.Core.BusinessBase

   Private mBO As BusinessListBase(Of DescriptorFamilyTypes, DescriptorFamilyType)

   Private mBindableIsSavable As Boolean = False

   Public Property BindableIsSavable() As Boolean

      ' Bind this property to "Save" and "Cancel" buttons on the UI

         Get

            Return (mBO.IsDirty And mBO.IsValid)

         End Get

         Set(ByVal value As Boolean)

               If value <> mBindableIsSavable Then

                     mBindableIsSavable = value

                     ' Notify Databinding that the property has changed

                     PropertyHasChanged()

               End If

         End Set

End Property

Public Sub New(ByVal wo As BusinessListBase(Of DescriptorFamilyTypes, DescriptorFamilyType))

      mBO = wo

      ' Listen for the ListChanged event

      AddHandler mBO.ListChanged, New ListChangedEventHandler(AddressOf List_PropertyChanged)

End Sub

Private Sub List_PropertyChanged(ByVal sender As Object, _

         ByVal e As System.ComponentModel.ListChangedEventArgs)

         ' If the underlying list has changed update the value of our "Bindable" IsSavable property

         BindableIsSavable = (mBO.IsDirty And mBO.IsValid)

End Sub

End Class

Copyright (c) Marimer LLC