Click here to Skip to main content
15,860,844 members
Articles / Desktop Programming / WPF

BindingHub - A new component and Design Pattern, very useful in WPF as well as in ViewModels

Rate me:
Please Sign up or sign in to vote.
4.78/5 (28 votes)
14 Jan 2011Eclipse7 min read 81.7K   501   75   26
BindingHub is the best thing since sliced bread. After you finish reading this article, you will start wondering how you could survive for so long without BindingHub. I did wonder the same thing after creating it.

Why do you need BindingHub?

Before diving into the 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 Code-Behind / 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 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 the 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 power strip: Attaching multiple Bindings to the same DependencyProperty (OneWay, TwoWay, using Converters if necessary)

phones.JPG

switch-board.JPG

Before BindingHub: Single Binding per DependencyProperty
BindingHub as telephone switch board: routing, connecting, multiplexing, polling data, pushing data, converting data

spaghetti.JPG

circuit-board.JPG

Before BindingHub: Spaghetti code in the code-behind / 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:

C#
OrderQty = FilledQty + RemainingQty;

Or maybe to validate some property when it changes:

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

Or maybe you need to calculate the count of words:

C#
WordCount = CalculateWordCount(Text);

Maybe you need to dispose of some object when some property changes, or populate a collection when another property changes, or attach / detach something, or 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 a 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:

C#
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, and hidden dependencies and gotchas sprinkled throughout numerous getters and setters in your ViewModel.

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

Use Cases

Connector

connector.GIF

XML
<bh:BindingHub Name="Connector"
      Socket1="{Binding SelectedItems, ElementName=Grid, Mode=OneWay}" 
      Socket2="{Binding SelectedItems, Mode=OneWayToSource}" 
      Connect="(1 in, 2 out)" >
</bh:BindingHub>

Comment: Does it look trivial? Yes, of course, because it is trivial, but try to bind XamDataGrid.SelectedItems (not a DependencyProperty) to ViewModel.SelectedItems (not a DependencyProperty). Oops, only a DependencyProperty can be the target of binding.

Multiplexor

multiplexor.GIF

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

Validator

C#
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

XML
<bh:BindingHub Name="Calculator" 
        Socket1="{Binding Text, ElementName=Input_1}" 
        Socket2="{Binding Text, ElementName=Input_2}" 
        Socket3="{Binding Text, ElementName=Output}" 
        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

C#
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

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

Chaining BindingHubs

XML
<!-- if 16 sockets are not enough, you can chain BindingHubs -->
<bh:BindingHub Name="FirstHub"
    Socket1="1" 
    Socket2="2" 
    Socket3="{Binding Extension.Socket3,
        RelativeSource={RelativeSource Self}}">
        <bh:BindingHub Name="SecondHub"
            Socket1="{Binding Parent.Socket1,
                RelativeSource={RelativeSource Self}}"
            Socket2="2" 
            Socket3="{Binding Extension.Socket3, 
                RelativeSource={RelativeSource Self}}">
                <bh:BindingHub Name="ThirdHub"
                    Socket1="{Binding Parent.Socket1,
                        RelativeSource={RelativeSource Self}}"
                    Socket2="2" 
                    Socket3="3">
                </bh:BindingHub>
        </bh:BindingHub>
</bh:BindingHub>

BindingHub Attached Property

XML
<Window Name="MainWindow">
    <bh:BindingHub.BindingHub>
        <bh:BindingHub 
            Socket1="{Binding ABC}"
            Socket2="{Binding Title, ElementName=MainWindow}"
            Socket3="Some string" />
    </bh:BindingHub.BindingHub>

    <!-- the whole bunch of elements can go there -->

    <TextBox Text="{Binding (bh:BindingHub.BindingHub).Socket1, 
            RelativeSource={RelativeSource Self}}"/>
    <TextBox Text="{Binding (bh:BindingHub.BindingHub).Socket2, 
            RelativeSource={RelativeSource Self}}"/>
</Window>

Comment: Think about the BindingHub property as an extension of the DataContext idea. Like DataContext, it is inherited, so you can set it on the parent, and all children and grandchildren will get it.

Caveat: when the BindingHub is attached to an element (Window in this example), it is not connected to the logical tree as usual (it is a virtual branch of the logical tree). In order to use the ElementName Binding, I had to bind the NameScope property of the BindingHub to the NameScope property of the parent element (DataConext is bound as well). The parent must have Name (or x:Name) set, otherwise its NameScope will remain empty, and the ElementName Binding won't work.

To Do: Create the HubBinding extension, so instead of:

C#
{Binding (bh:BindingHub.BindingHub).Socket2, RelativeSource={RelativeSource Self}}

you will be able to simply say:

C#
{bh:HubBinding Socket2}

Animator

animator.GIF

XML
<!-- used to animate both ViewModel and WPF properties -->
<bh:BindingHub Name="Animator"
      Socket1="{Binding WordCount, Mode=OneWay}" 
      Socket1Changed="Animator_Socket1Changed"
      Socket2="{Binding Background, ElementName=MainWindow, 
               Converter={StaticResource BrushConverter}, 
               Mode=OneWayToSource}"
      Connect="(Color in, Socket2 out)" >
                
      <bh:BindingHub.Resources>
            <Storyboard x:Key="Animation" >
                <!-- fancy background effect -->
                <ColorAnimation
                     Storyboard.TargetName="Animator"
                     Storyboard.TargetProperty="Color"
                     Duration="0:0:1" From="White" To="Plum"
                     AutoReverse="True" FillBehavior="HoldEnd" />
            </Storyboard>
      </bh:BindingHub.Resources>
</bh:BindingHub>
C#
private void Animator_Socket1Changed(DependencyObject d,
        DependencyPropertyChangedEventArgs e)
{
    var animation = Animator.Resources["Animation"] as Storyboard;
    animation.Begin();
}

Comment: Yes, using BindingHub, you can animate ViewModel properties, and x:Static variables, and DependencyProperties using Converters, and anything you can imagine. Typical tasks you can solve only with BindingHub: animate ColumnDefinition.Width or RowDefinition.Height using Int32Animation with GridLengthValueConverter, animate Background using ColorAnimation with BrushValueConverter, animate ViewModel.Progress property (not a DependencyProperty!) using Int32Animation, et cetera.

To Do: Create EventTriggers and DataTriggers (optionally using Python script) to eliminate the need for SocketxxxChanged handlers.

Source Code

The 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 kind of 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.

Revision History

  • Dec. 27, 2010 - Created the original article.
  • Jan. 13, 2011 - Added Animations to source code and article.

License

This article, along with any associated source code and files, is licensed under The Eclipse Public License 1.0


Written By
Software Developer (Senior) Liquidnet
United States United States
Michael is a software developer who still remembers punch cards, computers with 4 Kbytes RAM, and 3270s. His personal computers were Apple IIe, Commodore, and PC XT (with the whole 640 Kbytes RAM and 2 floppy drives!!!). Wow, that was a powerhouse.

Fast forward 32 years through FORTRAN, PL-I, Algol, Pascal, Prolog, LISP, C, Basic, Clipper, Assembly, FoxPro, DHTML, JavaScript, C++, you name it, to C# 4.0.

Of course, real men use machine code to write software, but what a difference a few years make! No more mallocs and callocs, GC magically collects unused objects, dynamic objects magically call IUnknown::QueryInterface, Reflection magically gives you metadata and even generates help files, WPF magically binds stuff together...

Read some of Michael's articles here.

BindingHub (a WPF component and a design pattern) [^].

Notifying parent of changes to children's properties [^].

Point-In-Time database (coming soon)

Composite Menus and other ItemsControls (coming soon)

Adorners framework (coming soon)

Drag-n-drop data transfer framework (coming soon)

Converters and MarkupExtensions (coming soon)

Download complete WPF library [^].

Comments and Discussions

 
QuestionGood article Pin
Bill_Hallahan12-Dec-11 12:11
Bill_Hallahan12-Dec-11 12:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.