Why do you need BindingHub?

Before diving into concrete use cases and implementation details, let’s see what is missing in the current WPF implementation.

WPF is supposed to be declarative programming, so all code must go into CodeBehind / ViewModel. Unfortunately, any non-trivial ViewModel quickly becomes Spaghetti with Meatballs (see Wikipedia if you don’t know what that means), littered with a multitude of unnecessary little property getters and setters, with hidden dependencies and gotchas.

You need to display something when IsClientActive == True, then you need to display another thing when IsClientActive == False, then you need IsClientActive && IsAccountOpen, then you need IsClientActive || not IsAccountOpen, et cetera… The number of ViewModel properties is growing like a snow ball, they depend on each other in complex ways, and every time you need to display / hide / collapse / change color / whatever, you have to create more and more properties and to recompile and re-test your ViewModel.

An alternative would be to use Triggers, but they are only allowed inside DataTemplates or ControlTemplates. Besides, you can’t reuse the same Trigger in other places, so you have to copy the whole list of Bindings with matching logic. Anyway, very often you just can’t express the necessary logic with Triggers (for instance, IsClientActive || OrderCount > 0).

Another alternative would be to use ValueConverters with MultiBindings, but MultiBindings are very inconvenient to use: you can’t define them inline, you can’t reuse the same MultiBinding in other places, you need to create another ValueConverter every time, and it is very error-prone as well. There are helpers like ConverterChain (which can combine multiple converters), et cetera, but they do not eliminate all aforementioned problems.

Very often you need to bind a DependencyProperty to multiple controls and / or multiple ViewModel properties. For instance, Slider.Value can be bound to the Zoom property of a Chart control, but you also want to display the same value in a TextBlock somewhere, and record that value in your ViewModel.Zoom property as well. Tough luck, because Slider.Value property can only have a single Binding, so you got to jump through hoops and create workarounds (using Tag properties, hidden fields, whatever)…

Sometimes you need a property to be updated conditionally, or when another property triggers the update, or you need to switch updates on and off…

Remember how many times you desperately needed that extra Binding, that extra DependencyProperty, that extra Trigger, that extra logical expression…

BindingHub to the rescue

Let’s take the bird’s eye view of the functionality offered by BindingHub.

electric-socket-single.JPG power-strip-connected.JPG
Before BindingHub: Single Binding per DependencyProperty BindingHub as PowerStrip: Attaching Multiple Bindings to the same DependencyProperty (OneWay, TwoWay, Using Converters if Necessary)
phones.JPG
Before BindingHub: Single Binding per DependencyProperty
switch-board.JPG
BindingHub as Telephone SwitchBoard: Routing, Connecting, Multiplexing, Polling Data, Pushing Data, Converting Data
spaghetti.JPG circuit-board.JPG
Before BindingHub: Spaghetti Code in CodeBehind / ViewModel BindingHub as Electronic Circuit Board: Wiring Connections between Sockets, Attaching / Detaching Prefabricated Components

Usage in ViewModels

Very often you need to perform some calculations when either of the variables change

OrderQty = FilledQty + RemainingQty;

Or maybe to validate some property when it changes

if (TextLength == 0)
     ErrorMsg = "Text cannot be empty";

Or maybe you need to calculate the count of words

WordCount = CalculateWordCount(Text);

Maybe you need to dispose of some object when some property changes, or to populate a collection when another property changes, or to attach / detach something, or to coerce MinValue when MaxValue is changed… Animation would be nice, styles and skins would be nice as well…

All those operations can be easily performed with DependencyProperties, so if we could simply inherit our ViewModels from DependencyObject, it would take no time to implement any kind of coercion or property change handling.

Unfortunately, you rarely have the luxury of picking an arbitrary base class for your ViewModel (usually there is a base class already that you have to use), so inheriting from DependencyObject is out of the question. You have to implement all coercion and property change handling in your getters and setters, complexity and hidden dependencies quickly get out of hand, and you get your Spaghetti with Meatballs in no time.

Well, BindingHub to the rescue.

You can create the ViewModel class with simple getters, setters, and NotifyPropertyChanged, like this:

public class ViewModel : INotifyPropertyChanged
{
    private string _text = "Hello, world";
    public string Text
    {
        get { return _text; }
        set { _text = value; OnPropertyChanged("Text"); }
    }
    private int _textLength;
    public int TextLength
    {
        get { return _textLength; }
        set { _textLength = value; OnPropertyChanged("TextLength"); }
    }
    private int _wordCount;
    public int WordCount
    {
        get { return _wordCount; }
        set { _wordCount = value; OnPropertyChanged("WordCount"); }
    }
    private string _errorMsg;
    public string ErrorMsg
    {
        get { return _errorMsg; }
        set { _errorMsg = value; OnPropertyChanged("ErrorMsg"); }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
}

That’s all, folks. Now, moving on to more interesting stuff.

At runtime you can attach one or more predefined Strategies, Validators, and Calculators created using BindingHubs, and voila, all your properties will magically start behaving as though they were DependencyProperties all along. You will get the ability to use Bindings, MultiBindings, OneWay, TwoWay, OneWayToSource, ValueConverters, coercion, property change handlers (with OldValue and NewValue so you can properly dispose or detach unused components), et cetera. You can declare custom OnCoerce and OnChange handlers and attach them to corresponding BindingHub events as well.

By attaching / detaching different predefined Strategies and Validators (or as the last resort, by rewiring and rebinding sockets inside the BindingHub and attaching OnChange / OnCoerce event handlers) you can instantly change the behavior of your ViewModel on the fly. Software patterns, here we come.

By separating validation, coercion, and state change logic into separate components, you can eliminate the dreaded Spaghetti with Meatballs code, hidden dependencies and gotchas sprinkled throughout numerous getters and setters in your ViewModel.

CodeBehind / ViewModel programming will become more declarative, more like its WPF counterpart and less of a quagmire it is today.

Use Cases

Multiplexor

multiplexor.GIF
<bh:BindingHub Name="Multiplexor" 
    bh:BindingHub.Socket1="{Binding Text, ElementName=Input}" 
    bh:BindingHub.Socket2="{Binding PlainText, Mode=OneWayToSource}" 
    bh:BindingHub.Socket3="{Binding WordCount, 
            Converter={StaticResource WordCountConverter}, Mode=OneWayToSource}" 
    bh:BindingHub.Socket4="{Binding SpellCheck, 
            Converter={StaticResource SpellCheckConverter}, Mode=OneWayToSource}" 
    bh:BindingHub.Connect="(1 in, 2 out, 3 out, 4 out)" >
</bh:BindingHub>

Validator

public class Validator : BindingHub
{
    public Validator()
    {
        SetBinding(Socket1Property, new Binding("TextLength")
            { Mode = BindingMode.OneWay });
        SetBinding(Socket2Property, new Binding("ErrorMsg") 
            { Mode = BindingMode.OneWayToSource });
        Socket1Changed += (s, e) =>
        {
            Socket2 = (int)e.NewValue == 0 ? "Text cannot be empty" : "";
        };
    }
}

Comment: You attach Validator to your ViewModel simply by setting its DataContext, and voila: your properties are being magically validated.

Calculator

calculator.GIF
<bh:BindingHub Name="Calculator" 
    bh:BindingHub.Socket1="{Binding Text, ElementName=Input_1}" 
    bh:BindingHub.Socket2="{Binding Text, ElementName=Input_2}" 
    bh:BindingHub.Socket3="{Binding Text, ElementName=Output}" 
    bh:BindingHub.Connect="(4 in, 3 out)" >
    <bh:BindingHub.Socket4>
        <MultiBinding Converter="{StaticResource AddConverter}">
            <Binding RelativeSource="{RelativeSource Self}" Path="Socket1"/>
            <Binding RelativeSource="{RelativeSource Self}" Path="Socket2"/>
        </MultiBinding>
    </bh:BindingHub.Socket4>
</bh:BindingHub>

Comment: You can calculate totals, angles (for Analog Clock display, for instance), ratios… You are limited only by your imagination.

To Do: Use Python scripts for ValueConverters.

Trigger Property

trigger.GIF

Comment: Set Trigger = true, and Input will be copied to Output.

To Do: Use Python scripts for conditional Copy operations (to eliminate the need for custom OnCoerce handlers).

Conditional Bindings

conditional.GIF

Comment: Again, you are limited only by your imagination.

To Do: Use Python scripts for conditional Copy operations (to eliminate the need for custom OnChange handlers).

Attach / Detach / Allocate / Dispose Pattern

public class Helper : BindingHub
{
   public Helper()
   {
      SetBinding(Socket1Property, new Binding("SomeResource")
              { Mode = BindingMode.OneWay });

      Socket1Changed += (s, e) =>
      {
          if (e.OldValue != null)
          {
              ((Resource)e.OldValue).Dispose(); // Or parent.Detach(e.OldValue);
          }
          if (e.NewValue != null)
          {
              ((Resource)e.NewValue).Allocate(); // Or parent.Attach(e.NewValue);
          }
      };
   }
}

Strategy Pattern

strategy.GIF

Comment: Detach Strategy1 by setting DataContext = null, attach Strategy2.

Polling / Pushing Data

polling.GIF

To Do: Implement Timed Update internally in BindingHub.

Switchboard / Circuit Board / Scratch Pad

<!--used as a scratch pad to keep various converted/calculated properties-->
<bh:BindingHub Name="ScratchPad"
    bh:BindingHub.Socket1="{Binding IsClientActive, 
                  Converter={StaticResource NotConverter}}" 
    bh:BindingHub.Socket2="{Binding Text}" 
    bh:BindingHub.Socket3="{Binding TextLength}" 
    bh:BindingHub.Socket4="{Binding ErrorMsg}" 
    bh:BindingHub.Socket5="{Binding Socket3, ElementName=Calculator1, Mode=OneWay}" 
    bh:BindingHub.Socket6="{Binding ElementName=TheTextBox, Mode=OneWay}"
    bh:BindingHub.Socket7="{Binding TextBoxItself, Mode=OneWayToSource}" 
    bh:BindingHub.Socket8="{Binding Text, ElementName=TheTextBox}" 
    bh:BindingHub.Socket9="{Binding Title, ElementName=Main, Mode=OneWayToSource}" 
    bh:BindingHub.Connect="(6 in, 7 out),(8 in, 9 out)" >
</bh:BindingHub>

Source Code

Project is published to SourceForge under Eclipse Public License where you can download the complete source code and examples of some use cases:

http://sourceforge.net/projects/bindinghub/

You are welcome to contribute to it with examples as well as new ideas.

Here are some ideas unrealized yet:

  • Script binding
  • Script blocks
  • Script extension
  • Script converter
  • Read-only property and NeedActual trigger
  • Timer-activated binding
  • Binding group with converter
  • BindingHubExtension (create hub and bind it to property)
  • MultiBinding with Trigger property

The BindingHub source code is kinda long and boring (the implementation was trivial, the most interesting part was to start thinking outside of the box and to come up with this brilliant idea), so just download the project and play with it.

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"