Silverlight for Windows Phone 7: How to bind UserControl to itself (сontinuation)
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"); } }