I must admit that the title of this post is not entirely clear, however I couldn't find a way to sum up the content in one short sentence, so we'll dive straight into an example. Let’s say for example, you have developed a funky little business-card as illustrated above, using the simple XAML below:
<Border BorderBrush="LightGray" Background="White"
BorderThickness="1.5" CornerRadius="20"
Width="220" Height="120">
<Border Margin="5" BorderBrush="LightGray"
BorderThickness="1.5" CornerRadius="15">
<Border.Background>
<ImageBrush>
<ImageBrush.ImageSource>
<BitmapImage UriSource="back.jpg" />
</ImageBrush.ImageSource>
</ImageBrush>
</Border.Background>
<StackPanel Orientation="Vertical" Margin="10">
<TextBlock Text="{Binding Name}"
FontSize="15" FontWeight="Bold" />
<Rectangle Stroke="DarkGray" HorizontalAlignment="Stretch"
StrokeThickness="0.5" Fill="Black" Height="1"/>
<TextBlock Margin="0,10,0,0" Text="{Binding Company}"/>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="e: " FontSize="10"/>
<TextBlock Text="{Binding Email}" FontSize="10"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="w: " FontSize="10"/>
<HyperlinkButton Content="{Binding Web}" FontSize="10"
NavigateUri="{Binding Web}"/>
</StackPanel>
</StackPanel>
</Border>
</Border>
The above XAML creates a simple visual layout that binds to the Name
, Email
, Web
and Company
properties of the data object which is presented to the inherited DataContext
. Nice and simple.
Now, let’s say you want to re-use this XAML markup in a number of places across your application. The obvious solution is to package it within a UserControl
. However, elsewhere within the application, contact details are stored in different types, with different properties, perhaps the property Web is used in some places, whereas the property URL is used elsewhere. If our UserControl
simply contains the XAML markup given above, the binding paths are hard-coded and inflexible. So, how do we package the above markup for re-use whilst maintaining flexibility?
In UI frameworks which do not support databinding, the obvious choice would be to define an interface
IContactDetails
, which defines a compile-time contract that clients of the control must implement on their business objects. This would allow us to enforce the presence of properties with specific names. However, with WPF / Silverlight, the binding framework removes the need for controls to demand the presence of a certain interface
and life is now much better as a result!
So … what we really want here is to allow the clients of our ContactDetails
user control to provide bindings for the various framework elements within the user control itself.
Bindings can be constructed in code-behind by invoking the SetBinding
method defined on DependencyObject
as shown in the example below:
Binding binding = new Binding();
binding.Source = myClass;
binding.Path = new PropertyPath("WindowTitle");
button.SetBinding(Button.ContentProperty, binding);
Therefore, if we were to expose the various Bindings as properties of the control (hence this blog post’s title), we could set the name, email, web elements binding accordingly using the Setbinding
method.
The first time I tried this, I added a NameBinding
dependency property of type Binding
to my UserControl
, so that the client XAML looked as follows:
<local:ContactDetailsControl
NameBinding="{Binding Name}"/>
However, this didn't quite have the desired effect. When the XAML reader processes the above markup, it creates the Binding
object that we are after, however it does not set the NameBinding
property value to this binding. Instead, it uses it to bind the NameBinding
property.
The solution to the problem is to make NameBinding
a standard CLR property. This time, when the XAML reader encounters the above markup, it once again creates the Binding
object, but since NameBinding
is not a dependency property, it does not use it to bind the property, it simply sets the property value.
The code-behind of our ContactDetails
user control looks like this:
public partial class ContactDetailsControl : UserControl
{
public Binding NameBinding { get; set; }
public Binding CompanyBinding { get; set; }
public Binding WebBinding { get; set; }
public Binding EmailBinding { get; set; }
public ContactDetailsControl()
{
InitializeComponent();
}
private void Border_Loaded(object sender, RoutedEventArgs e)
{
nameText.SetBinding(TextBlock.TextProperty, NameBinding);
webText.SetBinding(HyperlinkButton.ContentProperty, WebBinding);
webText.SetBinding(HyperlinkButton.NavigateUriProperty, WebBinding);
companyText.SetBinding(TextBlock.TextProperty, CompanyBinding);
emailText.SetBinding(TextBlock.TextProperty, EmailBinding);
}
}
The four bindings are exposed as CLR properties. When the UI is Loaded
, we use them to bind the various elements within the control. The net result is that we now have a re-useable control whilst maintaining the flexibility provided by the binding framework, as illustrated by its usage below:
<local:ContactDetailsControl
NameBinding="{Binding FullName}"
WebBinding="{Binding Website}"
CompanyBinding="{Binding Company}"
EmailBinding="{Binding Email}"/>
Now for another example and a bit more fun …
(CodeProject does not support embedded Silverlight content .... see it in action on my blog.)
The above Silverlight application shows a number of particles exhibiting Brownian motion.
This application creates 50 instances of a Particle
object within an ObservableCollection
:
class Particle : INotifyPropertyChanged
{
public double XLocation { ... }
public double YLocation { ... }
public double Excitement { ... }
}
The particles are rendered by a ScatterControl
, which is a UserControl
:
<Border BorderBrush="LightGray" BorderThickness="1">
<local:ScatterControl x:Name="scatterControl"
XValueBinding="{Binding Path=XLocation}"
YValueBinding="{Binding Path=YLocation}"
FillBinding="{Binding Path=Excitement,
Converter={StaticResource FillConverter}}"/>
</Border>
This control has an ItemsSource
property of type IEnumerable
, and exposes three other properties of type Binding
.
The markup for this control is given below:
<UserControl ... >
<ItemsControl x:Name="itemsControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<Ellipse Width="10" Height="10" Fill="Red"
Loaded="Ellipse_Loaded"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
The particles are rendered using an ItemsControl
, which creates an instance of an Ellipse
for each bound particle and places it within a Canvas
(as defined by the ItemsControl.ItemsPanel
property). As each ellipse is loaded, we set the bindings in code-behind:
private void Ellipse_Loaded(object sender, RoutedEventArgs e)
{
Ellipse ellipse = sender as Ellipse;
ellipse.SetBinding(Canvas., XValueBinding);
ellipse.SetBinding(Canvas., YValueBinding);
ellipse.SetBinding(Ellipse.FillProperty, FillBinding);
}
Again, by exposing Binding
s as properties, this user control is just as flexible as if its markup was included in the page directly. This example also illustrates another interesting point, the Binding
s that the client provided have been re-used for each Ellipse
that was created by the ItemsControl
. This is made possible because when SetBinding
is invoked on our Ellipse
, the Binding
is not associated with the Ellipse
, rather, it is used to create an instance of a BindingExpression. Binding
is used to declare a binding, whereas BindingExpression
is the implementation, i.e., it does the heavy-lifting of copying data to-and-from source to target (and vice-versa).
You can download the full source for both examples here.
Regards,
Colin E.