none
Data Binding - A Better Way to Implement Data Binding in .NET RRS feed

  • General discussion

  • Data binding can be a powerful   approach for developing UIs in the Microsoft .NET Framework: It makes it   easier to separate view logic from business logic, and easier to test the   resulting code. However, despite the various toolkits that aim to help, implementing   data binding is typically painful and tedious. Mark Sowul shows you a better   way.

    Read this article in the July issue of MSDN Magazine

    Friday, July 1, 2016 6:51 PM
    Owner

All replies

  • This is exactly the type of "clever" code that we should be moving away from. Aside from playing with new language features, what's the value proposition here? You save a few lines of repetitive boiler plate code but you end up with layers of indirection which destroys the readability of the code.  One look at the sample code below and my first reaction is "Are you freaking kidding me?".  The gradual turn towards simplicity and readability has begun to occur whether developers realize it or not. I predict that we as an industry will soon look back at the current Rube Goldberg style of development and see it for what it is....Anti-Patterns created by code addicts who rarely, if ever, designed, developed, deployed and supported a LOB application from soup to nuts BY THEMSELVES. Just because you occasionally do your own maintenance on your automobile doesn't mean you are now a fully qualified custom car designer.  If you want to see what quality industrial grade code looks like just browse the .NET framework source code.  You won't find any code in .NET that looks like the mess below. 

    static DerivedNotifyProperty<TDerived> CreateDerivedNotifyProperty   <T1, T2, TDerived>(this IRaisePropertyChanged owner,   string propertyName, IProperty<T1> property1, IProperty<T2> property2,   Func<T1, T2, TDerived> derivedValueFunction)

    Wednesday, July 13, 2016 8:23 PM
  • Multiple generic types tied together always look ugly.  The "Enumerable" extension methods are similarly ugly -- The .NET framework's source, for example, contains similar code, for example: http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,2b8d0f02389aab71

    The definition is ugly, but the caller only needs to do

    M()
    {
      IProperty<String> firstName ...;
      IProperty<String> lastName ...;
      IProperty<String> fullName = this.CreateDerivedNotifyProperty(firstName, lastName, GetFullName); //or an anonymous function
    }
    
    string GetFullName(string firstName, string lastName)
    {
      return String.Concat(firstName, " ", lastName);
    }

    I've worked on many projects from top to bottom -- I developed this exactly because it's very difficult to maintain code that implements INotifyPropertyChanged manually.  When the logic changes, or dependencies change, manually wiring these up leads to subtle bugs.  This is a bit more code, but the property changes just work.  The sample project shows this a bit better.



    Wednesday, July 13, 2016 10:02 PM
  • I usually use the PropertyChanged.Fody to do this... If you haven't had a chance to check it out, I'd say it's worth a look. While I usually try to avoid "magic code", I find this keeps my code clean, easy to maintain, and overall I find it far too valuable to my productivity to give up.

    Edit: Link to NuGet package: https://www.nuget.org/packages/PropertyChanged.Fody

    • Edited by - O_K - Thursday, July 14, 2016 2:01 AM Added link
    Thursday, July 14, 2016 2:01 AM
  • Yes, this was another option I looked at, and mentioned in the article.  Incidentally, a few of the other features enabled by making the properties first-class objects are that there's control over the change notifications (e.g. only when the value has actually changed, according to the given comparer), programmatic access to the property name, and a ValueChanged event on the property itself (for scenarios outside data binding).
    Thursday, July 14, 2016 1:09 PM
  • Adding additional layer of indirection is the right way to solve complex problems. We kept repeating this sentence in the CS graduate school classroom. I see many actual cases for it. This is one of them as well. Readability issue can only be addressed by applying the right abstraction. You can not read code well without understanding abstractions and design patterns, etc.
    • Edited by gureumi Wednesday, July 27, 2016 4:38 AM
    Wednesday, July 27, 2016 4:37 AM
  • I just want to add my bit to the `INotifyPropertyChanged` implemenation. I've been using all approaches from the article on real world projects, but in most cases this worked best for me:

    /// <summary>
    /// calls PropertyChanged and returns true if value changed
    /// </summary>
    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { ... }

    //simple property
    
    public string NickName
    {
         get { return _nickName; }
         set { SetProperty(ref _nickName, value); }
    }

    //dependent properties
    
    public string FirstName
    {
         get { return _firstName; }
         set { if (SetProperty(ref _firstName, value)) OnPropertyChanged(nameof(FullName)); }
    }
    
    public string LastName
    {
         get { return _lastName; }
         set { if (SetProperty(ref _lastName, value)) OnPropertyChanged(nameof(FullName)); }
    }
    
    public string FullName { get { return $"{FirstName} {LastName}"; }

    alternative approach I used for dependent properties was attaching to PropertyChanged in ctor:

    WhenPropertyChanges(nameof(FirstName)).Notify(nameof(FullName));

    this become even nicer, when I used Reactive Extentions in the the project:

    ObservableFromPropertyChanged()
       .Where(propertyName => propertyName == nameof(FirstName) || propertyName == nameof(LastName))
       .Subscribe(_ => OnPropertyChanged(nameof(FullName)));
    kind of cool, but comparing to original simple solution a bit of overengineered.


    Additional Benefits of generic SetProperty approach:

    I define virtual SetProperty method in BindableBase and override in derived ValidatableBase, which implements INotifyDataErrorInfo. When ViewModel is derived from ValidatableBase, I can do following:

    [Required, MinLength(4)]
    public string NickName
    {
    
         get { return _nickName; }
         set { SetProperty(ref _nickName, value); }
    }


    Scenarios when default EqualityComparer won't work in SetProperty method are quite rare actually. In such situation, why not just write custom setter, or override SetProperty method?


    I've used also the properties defined as first class citizen, but from different reasons: I needed dynamically add or remove properties, or I needed to handle validation of complex properties, like Address, or I needed to pass properties around for some reasons. @GabeCruz: what about DependencyProperties? They are event more messy, but they works well.



    Friday, August 5, 2016 7:12 AM
  • You have a next binding in your article:

    <StackPanel Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}"/>

    How is it possible? "IsBusy" property is meant to be a kind of "NotifyProperty<bool>" but you assign a bool to it.

    Should there be "{Binding IsBusy.Value...}"? But in this case property changed notification won't work (name is "Value", it's not "IsBusy" anymore). Without an example of usage it's a bit confusing.

    Moreover why doesn't NotifyProperty implement INotifyPropertyChanged? In this case you don't need an owner and a property name because NotifyProperty itself can raise the event with a name "Value"?

    Thanks.

    Tuesday, August 9, 2016 5:28 PM
  • One issue with this approach, generally, is that the dependency code has to be in "two places" that are far away.  In the first case, the definition of FullName is where you depend on FirstName and LastName -- but you have to also put that dependency inside the FirstName and LastName properties themselves.  The reactive extensions approach has the same issue.  Any time you have one piece of information (Full name depends on first name and last name) in two places, it is a recipe for getting out of sync -- this is, in large part, the origin of DRY (Don't Repeat Yourself)
    Thursday, August 11, 2016 3:43 AM
  • Hi Vitaly -- IsBusy is a boolean property on the view model.  Its backing store is a NotifyProperty.  For example

    class ViewModel
    {
      private NotifyProperty<bool> m_isBusyProperty;
      public bool IsBusy
      {
        get
       {
          return m_isBusyProperty.Value;
        }
      }
    }

    The "DataContext" of the StackPanel is still the ViewModel.

    I suggest checking out the online code sample to see the real example (https://msdn.microsoft.com/magazine/0716magcode)

    --

    Your approach is also viable (have the NotifyProperty implement INotifyPropertyChanged), and has the benefits you describe.  However, the result is that now the ViewModel is unnatural: it doesn't expose actual properties of type string, int, etc, it exposes NotifyProperty<string>, NotifyProperty<int>, etc, and as you point out, now the binding would have to be not IsBusy, but IsBusy.Value; not FullName, but FullName.Value, etc. 

    This is up to taste -- however it definitely breaks encapsulation / leaks implementation details to do it this way.  Ideally the view model should appear to the outside world like any other object/view model with normal-style properties, and how it's implemented (NotifyProperty, Fody, manual code) is hidden inside that class itself -- not part of the public API contract.


    Thursday, August 11, 2016 3:49 AM
  • This is exactly the type of "clever" code that we should be moving away from. Aside from playing with new language features, what's the value proposition here? ...

    "Are you freaking kidding me?"...

    ....Anti-Patterns created by code addicts who rarely, if ever, ...

    static DerivedNotifyProperty<TDerived> CreateDerivedNotifyProperty   <T1, T2, TDerived>(this IRaisePropertyChanged owner,   string propertyName, IProperty<T1> property1, IProperty<T2> property2,   Func<T1, T2, TDerived> derivedValueFunction)

    Thank you for naming my disease.  It helps...


    Tuesday, November 8, 2016 2:20 PM