Very often I want my UserControl to be bound to itself. It allows to have more control under data presentation and formatting. To bind the UserControl to itself declaratively we need to add DataContext initialization to the xaml-file:
<UserControl x:Class="SomeNamespace.ProductDispalyControl"
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"
d:DesignHeight="480" d:DesignWidth="480"
DataContext = "{Binding RelativeSource={RelativeSource Self}}">
<StackPanel x:Name="LayoutRoot">
<TextBlock Text="{Binding ProductName}" />
<TextBlock Text="{Binding ProductPrice}" />
</StackPanel>
</UserControl>
This works great, if we provide the UserControl with an ability to receive somehow data, which should be rendered. Let’s take a look at example:
/// <summary>
/// Represents data to render
/// </summary>
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
/// <summary>
/// Renders information about product
/// </summary>
public partial class ProductDispalyControl : UserControl
{
protected Product _product = null;
public Product CurrentProduct
{
get { return _product; }
set { _product = value; }
}
public string ProductName
{
get { return string.Format("<{0}>", CurrentProduct.Name); }
}
public string ProductPrice
{
get { return string.Format("${0}", CurrentProduct.Price); }
}
public ProductDispalyControl(Product product)
{
_product = product;
InitializeComponent();
}
public ProductDispalyControl()
{
InitializeComponent();
}
}
Product (data to render) can be passed into the UserControl through the constructor, if the control is created dynamically, for example,
public ParentControl()
{
InitializeComponent();
ProductDispalyControl productCtrl = new ProductDispalyControl(product); // passing data through the constructor
placeHolder.Children.Add(productCtrl);
}
or can be set through the property CurrentProduct inside the constructor of parent-control right after the UserControl is created, if the control is declared in xaml-file of parent control
public ParentControl()
{
InitializeComponent();
productCtrl.CurrentProduct = Product; // passing data through the property
}
If you are devoted to declarative way, it’s not convenient every time to add some additional initialization code in code-behind. But in the same time we can’t bind DataContext property of our UserControl to the data in xaml-file, because we need the UserControl to be bound to itself. Of course, you still can try binding CurrentProduct to the data. For this you need to transform CurrentProduct property to DependencyProperty to support binding. However, in some cases it doesn’t help, for example,
<ItemsControl ItemsSource="{Binding Path=ProductCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<someNamespacePrefix:ProductDispalyControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In this case every Product from ProductCollection will be passed to the certain ProductDispalyControl through DataContext property by default, however it conflicts with our main idea to have the UserControl bound to itself. To overcome this problem I’ve developed a simple class
namespace MyControls
{
public class BoundToSelfControl : UserControl
{
protected object _source = null;
public BoundToSelfControl() : base()
{
Loaded += new RoutedEventHandler(BoundToSelfControl_Loaded);
}
public virtual void OnLoad()
{
// NOTE: When we navigate from the current PhoneApplicationPage,
// the state of the page is persisted (including the states of controls the page contains).
// It's so called Tombstoning. If we get back to the persisted page,
// BoundToSelfControl's Loaded-handler is called again (as opposed to the control constructor).
// When it happens, DataContext already refers to the control itself, and there is no need to do anything else.
// Of course, it's topical for windows phone 7 only
if (_source == null)
{
_source = DataContext; // save data passed through DataContext in the field _source
DataContext = this; // bind to itself
}
}
void BoundToSelfControl_Loaded(object sender, RoutedEventArgs e)
{
OnLoad();
}
}
}
In a Loaded-handler BoundToSelfControl gets Product passed through DataContext and saves it in the field _source, then binds itself to itself. Now we need to derive ProductDispalyControl from BoundToSelfControl:
public partial class ProductDispalyControl : BoundToSelfControl
{
protected Product _product = null;
public Product CurrentProduct
{
get
{
if (_product == null)
_product = (Product)_source;
return _product;
}
}
public string ProductName
{
get { return string.Format("<{0}>", CurrentProduct.Name); }
}
public string ProductPrice
{
get { return string.Format("${0}", CurrentProduct.Price); }
}
public ProductDispalyControl()
{
InitializeComponent();
}
}
The xaml-file of ProductDispalyControl will be the following:
<myControls:BoundToSelfControl x:Class="SomeNamespace.ProductDispalyControl"
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"
xmlns:myControls="clr-namespace:MyControls"
d:DesignHeight="480" d:DesignWidth="480"
DataContext = "{Binding RelativeSource={RelativeSource Self}}">
<StackPanel x:Name="LayoutRoot">
<TextBlock Text="{Binding ProductName}" />
<TextBlock Text="{Binding ProductPrice}" />
</StackPanel>
</myControls:BoundToSelfControl>
Do not forget to add xmlns:myControls=”clr-namespace:MyControls” to declaration. After this an auto generated part of ProductDispalyControl will be successfully created as well.
I use BoundToSelfControl every time when I create a new UserControl. I hope it will be useful for someone else.
ps I utilize BoundToSelfControl in an Silverlight Windows Phone 7 application, but I think it will work with usual Silverlight with no problems.
See Continuation of the topic
Related posts: