Archive

Posts Tagged ‘DataContext’

Silverlight for Windows Phone 7: How to bind UserControl to itself (сontinuation)

June 20th, 2011 Admin No comments

     In one of my previous posts (How to bind UserControl to itself) I showed an implementation of BoundToSelfControl. It’s time to supplement this implementation with a new ability.

Obviously, if data, that should be rendered, is changed, we need to refresh our page in order that the actual values would be displayed. In other words, if at least one property of the object preserved in _source is changed, we have to be notified somehow about this fact and to refresh the displayed values. A standard approach is an usage of INotifyPropertyChanged, it’s precisely what is used by a standard binding system. Let’s do the same.

First of all, we need to subscribe to PropertyChanged event of our data source object, of course, if the one supports INotifyPropertyChanged. BoundToSelfControl‘s OnLoad method will look like

protected virtual void OnLoad()
{
    if (_source == null)
    {
        _source = DataContext;
        DataContext = this;

        INotifyPropertyChanged iNotifyPropertyChanged = _source as INotifyPropertyChanged;
        if (iNotifyPropertyChanged != null) // check if _source supports INotifyPropertyChanged;
            iNotifyPropertyChanged.PropertyChanged += new PropertyChangedEventHandler(iNotifyPropertyChanged_PropertyChanged); // subscribing to source property changes
    }
}

protected virtual void iNotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}

Secondly, BoundToSelfControl has to implement INotifyPropertyChanged as well in order to let the standard binding system know that it has to retake values of BoundToSelfControl‘s properties.

public class BoundToSelfControl : UserControl, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    // ... the rest of code

    // Raises the PropertyChanged event, passing the source property that is being updated
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Thirdly, in iNotifyPropertyChanged_PropertyChanged of a derived control we need to analyse what property of data source object is changed and then to fire PropertyChanged event with the name of appropriate property, for example, if ‘Price’ property of Product-object is changed, we fire PropertyChanged event with the ‘ProductPrice’ (Product class represents data to render).

public partial class ProductDispalyControl : BoundToSelfControl
{
    // ... the rest of code

    protected override void iNotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if ("price".Equals(e.PropertyName, StringComparison.OrdinalIgnoreCase))
            NotifyPropertyChanged("ProductPrice");
        else
            if ("name".Equals(e.PropertyName, StringComparison.OrdinalIgnoreCase))
                NotifyPropertyChanged("ProductName");
    }
}

And the improved final version of BoundToSelfControl and ProductDispalyControl:

public class BoundToSelfControl : UserControl, INotifyPropertyChanged
{
    protected object _source = null;

    public event PropertyChangedEventHandler PropertyChanged;

    public BoundToSelfControl() : base()
    {
        Loaded += new RoutedEventHandler(BoundToSelfControl_Loaded);
    }

    protected 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

            INotifyPropertyChanged iNotifyPropertyChanged = _source as INotifyPropertyChanged;
            if (iNotifyPropertyChanged != null) // check if _source supports INotifyPropertyChanged;
                iNotifyPropertyChanged.PropertyChanged += new PropertyChangedEventHandler(iNotifyPropertyChanged_PropertyChanged); // subscribing to source property changes
        }
    }

    protected virtual void OnDataSourcePropertyChanged(string propertyName)
    {
    }

    // Raises the PropertyChanged event, passing the source property that is being updated
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    void iNotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        OnDataSourcePropertyChanged(e.PropertyName);
    }    

    void BoundToSelfControl_Loaded(object sender, RoutedEventArgs e)
    {
        OnLoad();
    }
}

public class Product
{
    public string Name  { get; set; }
    public double Price { get; set; }
}

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();
    }

    protected override void OnDataSourcePropertyChanged(string propertyName)
    {
        if ("price".Equals(propertyName, StringComparison.OrdinalIgnoreCase))
            NotifyPropertyChanged("ProductPrice");
        else
            if ("name".Equals(propertyName, StringComparison.OrdinalIgnoreCase))
                NotifyPropertyChanged("ProductName");
    }
}
Related posts:

Silverlight for Windows Phone 7: How to bind UserControl to itself

June 8th, 2011 Admin No comments

     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: