Bindings in WPF are great and can accomplish so many things.  But how many times have you been in the position of trying to do any of the following:

  • Bind simple POCOs together
  • Apply a binding to an object outside of your xaml file
  • Bind the same property more than once (say once with OneWayToSource and again with a different OneWay binding)

I wrote a small utility class that helps these situations.  It's called a "Join".  It simply allows you to specify two bindings and it will "join" the two together.  You place Joins inside a JoinContainer which is a simple ContentControl.  For example:

 <Grid>
        <JoinContainer>
            <JoinContainer.Joins>
                <Join Target="{Binding ElementName=textBox1, Path=Text}" Value="{Binding ElementName=textBox2, Path=Text}" />
            </JoinContainer.Joins>
            <TextBox x:Name="textBox1" />
            <TextBox x:Name="textBox2" />
        <JoinContainer>
<Grid>

This will bind the text of textbox1 to textbox2. It does this without using any bindings on the TextBoxes themselves, which frees you up to apply more bindings.  Joins are purely oneway.

Why would you want to do this?  I give you two cases, but there are more:

1. Inversion of Control

You can use the Binding engine to implement a pseudo dependency injection system.  The main thing Joins give you is the ability to apply a set of bindings to local properties that do not have to be DPs and do not have to be declared on the base Type.

Let’s say you have two UserControls.  One publishes a “MyService” and the other consumes it.  The publish event can happen at any time and for simplicity, we use DataContext to hold the services (you could also use an inherited attached property for this).

Publisher:

<UserControl x:Class="WpfApp1.UserControl1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:WpfApp1"

    x:Name="UserControl1Root"

    Height="173" Width="196">

    <local:JoinContainer>

        <local:JoinContainer.Joins>

            <local:Join Target="{Binding MyService}" Value="{Binding ElementName=UserControl1Root, Path=MyService}" />

        </local:JoinContainer.Joins>

        <Grid>

            <Button Margin="43,70,49,67" Name="button1" Click="button1_Click">Publish</Button>

        </Grid>

    </local:JoinContainer>

</UserControl>

 

The Join highlighted above targets the “MyService” property on the datacontext and will set it from the “MyService” property coming from UserControl1Root.  That is, this sets a value on the Datacontext using local values.

Consumer:

<UserControl x:Class="WpfApp1.UserControl2"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:WpfApp1"

    Height="162" Width="206" x:Name="UserControl2Root">

    <UserControl.Resources>

        <local:NullToBoolConverter x:Key="converter" />

    </UserControl.Resources>

    <local:JoinContainer>

        <local:JoinContainer.Joins>

            <local:Join Target="{Binding ElementName=UserControl2Root, Path=MyService}" Value="{Binding MyService}" />

        </local:JoinContainer.Joins>

 

        <Grid>

            <Button Margin="49,46,29,68" Name="button1" Click="button1_Click"

                IsEnabled="{Binding ElementName=UserControl2Root, Path=MyService, Converter={StaticResource converter}}">Go!</Button>

        </Grid>

    </local:JoinContainer>

</UserControl>

 This Join does the opposite.  It sets the local MyService property on UserControl2Root from the value from the datacontext.  And the great thing is you can use simple properties – though the publisher will need to implement INotifyPropertyChanged.

To declare these in xaml without Joins, you would need to declare MyService as DependencyProperties on a new base Type for both usercontrols. 

2. Binding a complex Model

Say you have a model such as this:

    public class ColorModel :INotifyPropertyChanged {

        Color _c;

 

        public ColorModel() {

            Color = Colors.RosyBrown;

        }

 

        public Color Color {

            get { return _c; }

            set { _c = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Color")); }

        }

 

        #region INotifyPropertyChanged Members

 

        public event PropertyChangedEventHandler PropertyChanged;

 

        #endregion

    }

 

And you wanted to display a visual such as this:

 

The rectangle at the bottom is the resulting color of all three textboxes.  As you think about the solution, you realize that the model given simply won’t work.  The issue is that each textbox needs to be two-way to the underlying ColorModel.  So, when you type a new R value, a new color gets pushed into ColorModel.Color.  But when you go to write the converter for the R binding, you realize you don’t have enough contextual information to build the whole color!  You are missing the values for G and B.  So, it ends up that you can only make those bindings oneway if you use the ColorModel above.

Joins can help in this situation by allowing you to keep the Textbox Bindings one-way and use a Join for the other direction.

<Window x:Class="WpfApp1.MultiBindingsWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:WpfApp1"

    Title="Window1" Height="228" Width="230">

    <Grid>

        <local:JoinContainer>

            <local:JoinContainer.Resources>

                <local:RGBConverter x:Key="rgbconverter" />

            </local:JoinContainer.Resources>

            <local:JoinContainer.Joins>

                <local:Join Target="{Binding Color}">

                    <local:Join.Value>

                        <MultiBinding Converter="{StaticResource rgbconverter}" >

                            <MultiBinding.Bindings>

                                <Binding ElementName="textBox1" Path="Text" />

                                <Binding ElementName="textBox2" Path="Text" />

                                <Binding ElementName="textBox3" Path="Text" />

                            </MultiBinding.Bindings>

                        </MultiBinding>

                    </local:Join.Value>

                </local:Join>

            </local:JoinContainer.Joins>

            <Grid>

                <Grid.Resources>

                    <local:RConverter x:Key="rconverter" />

                    <local:GConverter x:Key="gconverter" />

                    <local:BConverter x:Key="bconverter" />

                </Grid.Resources>

                <TextBox Height="23" Margin="48,12,40,0" Name="textBox1" VerticalAlignment="Top" Text="{Binding Color, Mode=OneWay, Converter={StaticResource rconverter}}" />

                <TextBox Height="23" Margin="48,47,40,0" Name="textBox2" VerticalAlignment="Top" Text="{Binding Color, Mode=OneWay, Converter={StaticResource gconverter}}" />

                <TextBox Height="23" Margin="48,85,40,0" Name="textBox3" VerticalAlignment="Top" Text="{Binding Color, Mode=OneWay, Converter={StaticResource bconverter}}" />

                <Label HorizontalAlignment="Left" Margin="10,12,0,79" Name="label1" Width="30">R</Label>

                <Label HorizontalAlignment="Left" Margin="10,47,0,79" Name="label2" Width="30">G</Label>

                <Label HorizontalAlignment="Left" Margin="10,83,0,79" Name="label3" Width="30">B</Label>

                <Rectangle Height="55" Margin="10,0,8,9" Name="rectangle1" Stroke="Black" VerticalAlignment="Bottom">

                    <Rectangle.Fill>

                        <SolidColorBrush Color="{Binding Color, Mode=OneWay}" />

                    </Rectangle.Fill>

                </Rectangle>

            </Grid>

        </local:JoinContainer>

    </Grid>

</Window>

Of course, the easier approach is to just create a viewmodel with separate values for R, G, and B.

The implementation of Join is pretty simple:

    public class Join : Freezable {

 

        public object Value {

            get { return (object)GetValue(ValueProperty); }

            set { SetValue(ValueProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty ValueProperty =

            DependencyProperty.Register("Value", typeof(object), typeof(Join),

            new UIPropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));

 

        static void OnValueChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

            Join j = target as Join;

            Debug.Assert(j != null);

            j.Target = e.NewValue;

        }

 

        public object Target {

            get { return (object)GetValue(TargetProperty); }

            set { SetValue(TargetProperty, value); }

        }

 

        // Using a DependencyProperty as the backing store for Target.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty TargetProperty =

            DependencyProperty.Register("Target", typeof(object), typeof(Join),

                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnTargetChanged)));

 

        static void OnTargetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

            Join j = target as Join;

            Debug.Assert(j != null);

            j.Target = j.Value;

        }

 

        protected override Freezable CreateInstanceCore() {

            throw new NotImplementedException();

        }

    }

It derives from Freezable to participate in the containers inheritance context.  The source for Joins can be found here: