Monday, 12 March 2012

Dependency Properties in User Control

In order to be able to expose custom properties that take advantage of xaml data binding, you need to use dependency properties.

In this simple example we create a user control exposing four dependency properties, which will allow our developers to be able to create the following instance:

player-control

Our User Control

class-diagram

For each property you’ll need a dependency property field which gets exposed through a normal .NET property wrapper.  This is to enable Xaml to make use of the data binding system:

PlayerControl.xaml.cs

using System.ComponentModel;
using System.Windows;
using System.Windows.Media;

namespace WpfCustomUserControl
{
public partial class PlayerControl
{
#region Dependency Properties

public static readonly DependencyProperty ShirtNumberProperty =
DependencyProperty.Register("ShirtNumber",
typeof(int),
typeof(PlayerControl),
new PropertyMetadata(0));

[Bindable(true)]
public int ShirtNumber
{
get { return (int)this.GetValue(ShirtNumberProperty); }
set { this.SetValue(ShirtNumberProperty, value); }
}

public static readonly DependencyProperty KitColorProperty =
DependencyProperty.Register("KitColor",
typeof(Brush),
typeof(PlayerControl),
new PropertyMetadata(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0))));

[Bindable(true)]
public Brush KitColor
{
get { return (Brush)this.GetValue(KitColorProperty); }
set { this.SetValue(KitColorProperty, value); }
}

public static readonly DependencyProperty SurnameProperty =
DependencyProperty.Register("Surname",
typeof(string),
typeof(PlayerControl),
new PropertyMetadata("Not set"));

[Bindable(true)]
public string Surname
{
get { return (string)this.GetValue(SurnameProperty); }
set { this.SetValue(SurnameProperty, value); }
}

public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position",
typeof(string),
typeof(PlayerControl),
new PropertyMetadata("Not set"));

[Bindable(true)]
public string Position
{
get { return (string)this.GetValue(PositionProperty); }
set { this.SetValue(PositionProperty, value); }
}

#endregion

public PlayerControl()
{
InitializeComponent();
}
}
}

Note: I've included a bindable attribute to the dependency properties that is not required but I add as a best practice.


The xaml for our user control can then hook up the data binding.


PlayerControl.xaml

<UserControl x:Class="WpfCustomUserControl.PlayerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="CustomPlayer"
Width="120"
Height="90"
mc:Ignorable="d">
<Grid>
<Button>
<StackPanel>
<Ellipse Width="30"
Height="30"
Fill="{Binding Path=KitColor,
ElementName=CustomPlayer,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Margin="0,-25"
HorizontalAlignment="Center"
FontSize="16"
Foreground="White"
Text="{Binding Path=ShirtNumber,
ElementName=CustomPlayer,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Margin="0,10,0,5"
HorizontalAlignment="Center"
FontSize="15"
Text="{Binding Path=Surname,
ElementName=CustomPlayer,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />

<TextBlock HorizontalAlignment="Center"
Text="{Binding Path=Position,
ElementName=CustomPlayer,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Button>
</Grid>
</UserControl>


Note: No data binding is set on the user control as this will be set at runtime by the developer using the usercontrol. The element name within the usercontrols controls points to itself to enable data binding to work.


We can now easily reuse our user control setting the properties for each instance.


England-team


MainWindow.xaml


<Window x:Class="WpfCustomUserControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomUserControl"
Title="England Footy Team"
Icon="Football.ico"
Height="500" Width="600">
<Grid x:Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="100"/>
<RowDefinition Height="120"/>
<RowDefinition Height="120"/>
<RowDefinition Height="120"/>
<RowDefinition Height="10"/>
</Grid.RowDefinitions>

<Grid.Resources>
<Style TargetType="WrapPanel">
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
</Grid.Resources>

<WrapPanel Name="panelGoalKeeper" Grid.Column="0" Grid.Row="1">
<local:PlayerControl x:Name="btnGK"
ShirtNumber="1"
KitColor="Green"
Surname="Hart"
Position="Keeper" />
</WrapPanel>

<WrapPanel Name="panelDefenders" Grid.Column="0" Grid.Row="2">
<local:PlayerControl x:Name="btnDF1"
ShirtNumber="2"
KitColor="Crimson"
Surname="Richards"
Position="Strong as an Ox"
Margin="0,25,25,0"/>
<local:PlayerControl x:Name="btnDF2"
ShirtNumber="5"
KitColor="Crimson"
Surname="Cahill"
Position="Ball playing stopper"
Margin="0,0,5,0"/>
<local:PlayerControl x:Name="btnDF3"
ShirtNumber="6"
KitColor="Crimson"
Surname="Jones"
Position="Marauding Sweeper"
Margin="5,0,0,0"/>
<local:PlayerControl x:Name="btnDF4"
ShirtNumber="3"
KitColor="Crimson"
Surname="Baines"
Position="Freekick specialist"
Margin="25,25,0,0"/>

</WrapPanel>

<WrapPanel Name="panelMidfielders" Grid.Column="0" Grid.Row="3">

<local:PlayerControl x:Name="btnMD1"
ShirtNumber="7"
KitColor="Crimson"
Surname="Wilshire"
Position="Playmaker"
Margin="0,25,25,0"/>
<local:PlayerControl x:Name="btnMD2"
ShirtNumber="4"
KitColor="Crimson"
Surname="Parker"
Position="Battler" />
<local:PlayerControl x:Name="btnMD3"
ShirtNumber="8"
KitColor="Crimson"
Surname="Gerrard"
Position="Attacking Midfielder"
Margin="25,25,0,0"/>
</WrapPanel>

<WrapPanel Name="panelStrikers" Grid.Column="0" Grid.Row="4">
<local:PlayerControl x:Name="btnST1"
ShirtNumber="9"
KitColor="Crimson"
Surname="Sturridge"
Position="The Future is bright"
Margin="0,25,25,0"/>
<local:PlayerControl x:Name="btnST2"
ShirtNumber="10"
KitColor="Crimson"
Surname="Rooney"
Position="Number 10" />
<local:PlayerControl x:Name="btnST3"
ShirtNumber="11"
KitColor="Crimson"
Surname="Welbeck"
Position="Striker"
Margin="25,25,0,0"/>
</WrapPanel>
</Grid>
</Window>

Source code:  http://stevenhollidge.com/blog-source-code/WpfCustomUserControl.zip

8 comments:

  1. nice example, thanks

    ReplyDelete
  2. Thanks for sharing this article. It helped me. :)

    ReplyDelete
  3. This Property Grid control is able to display the properties of any object in a user-friendly way and allows the end users of your applications edit the properties of the object.

    ReplyDelete
  4. You set the usercontrol's xName to x:Name="CustomPlayer" and then again to PlayerControl x:Name="btnMD1", why don't they conflict?

    ReplyDelete
  5. Thank you so much for taking the time to create this easy-to-understand post !

    ReplyDelete
  6. Clear and very helpful, thank you!

    ReplyDelete
  7. THANK You SO MUCH for this CLEAN and nicely explained post.

    I do have a (some what philosophical) question about the PlayerControll Object. I was wondering what are your views with regards to using dependency properties vs the normal CLR properties?

    Of-course there is always too many ways to skin a cat ;) so I was thinking about the alternate implementation of of creating PlayerControllView backed by PlayerControllViewModel where the PlayerControllViewModel holds normal CLR properties that Implement INotifyPropertyChanged

    Just wanted to get your take on it.
    Thanks

    ReplyDelete
  8. Great article ..best article on DP available on the web

    ReplyDelete