Archive

Archive for the ‘Silverlight’ Category

Custom Markup Extension with bindable properties

April 12th, 2012 No comments

Download Source Code

Microsoft added custom markup extensions to SL5, but they did not provide built-in support for binding to the properties of CME’s. If you’re using CME’s to manipulate your UI, trying to make the UI react to changes in your View Model after the UI has loaded will not work.

When the UI loads, the runtime finds your CME’s and calls the ProvideValue method of your CME to get the value that should be applied to the UI element. The problem is that there’s no way to force the runtime to call ProvideValue again when something has changed in your View Model, so the UI will not react to changes in your VM.

Below is an example of how you could use a CME (called ConvertBooleanToVisibility) to toggle the visibility of a UI element based on the state of two properties in your View Model. Doing this without using a CME would require you to create a third “aggregate” property in your View Model to combine the values of the two main properties, bind to that aggregated property and use a ValueConverter to convert from boolean to Visibility.

<Rectangle Visibility="{local:ConvertBooleanToVisibility Value1={Binding Path=IsVisible}, Value2={Binding Path=IsVisible2}}"/>

As you can see, I’ve used a CME with two properties, Value1 and Value2, that binds to two properties in the View Model. When these values change, the Visibility property of the Rectangle is changed.

The trick to make this work is the following:

  • Instead of inheriting from MarkupExtension, you need to inherit from DependencyObject and implement the IMarkupExtension<object> interface
  • Add dependency properties to the CME, so that you can use bindings. The DP’s must have a property changed callback.
  • In ProvideValue, get a reference to the UI element and property that the CME is applied to. You’ll need these references to manually update the UI element when your View Model property changes. You’ll also need to subscribe to the DataContextChanged event for the UI element so you’ll know when a View Model has been attached. This is necessary because at the time the runtime calls ProvideValue the first time, the View Model may not yet be attached, so you need to modify the binding to point to the correct source once you have a View Model in scope.
  • When the View Model is attached, the DataContextChanged event will be raised. This will allow you to get the binding expressions for your dependency properties. To get the binding expressions for your properties instead of the values, use the ReadLocalValue method instead of GetValue(…). Once you have the binding expressions, copy the binding and remap the binding source so that it points to the attached View Model. Then apply the new bindings to the dependency properties of the CME.
  • Once the bindings are in place, this will make the property changed callbacks to get called when the dependency properties are changed. In the callbacks, call a method that calculates the composite value of the the two DPs and use the UI element and property reference obtained in ProvideValue (step 2) to update the UI element.

Below is a complete listing of the implementation of this Custom Markup Extension.

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Xaml;

namespace CMEWithBinding
{
    public class ConvertBooleanToVisibilityExtension : DependencyObject, IMarkupExtension<object>
    {
        private object _target;
        private object _targetProperty; // Can be PropertyInfo or Binding

        #region Value1
                
        public static readonly DependencyProperty Value1Property =
            DependencyProperty.Register("Value1", typeof(bool), typeof(ConvertBooleanToVisibilityExtension),
                new PropertyMetadata((bool)false,
                    new PropertyChangedCallback(OnValue1Changed)));
        

        public bool Value1
        {
            get { return (bool)GetValue(Value1Property); }
            set { SetValue(Value1Property, value); }
        }
                
        private static void OnValue1Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ConvertBooleanToVisibilityExtension)d).OnValue1Changed(e);
        }

        protected virtual void OnValue1Changed(DependencyPropertyChangedEventArgs e)
        {            
            UpdateTarget();
        }

        #endregion

        #region Value2

        /// <summary>
        /// Value2 Dependency Property
        /// </summary>
        public static readonly DependencyProperty Value2Property =
            DependencyProperty.Register("Value2", typeof(bool), typeof(ConvertBooleanToVisibilityExtension),
                new PropertyMetadata((bool)false,
                    new PropertyChangedCallback(OnValue2Changed)));

        /// <summary>
        /// Gets or sets the Value2 property.  This dependency property 
        /// indicates ....
        /// </summary>
        public bool Value2
        {
            get { return (bool)GetValue(Value2Property); }
            set { SetValue(Value2Property, value); }
        }

        /// <summary>
        /// Handles changes to the Value2 property.
        /// </summary>
        private static void OnValue2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ConvertBooleanToVisibilityExtension)d).OnValue2Changed(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the Value2 property.
        /// </summary>
        protected virtual void OnValue2Changed(DependencyPropertyChangedEventArgs e)
        {
            UpdateTarget();
        }

        #endregion



        #region Properties
        
        private bool _value1CompareTo = true;
        public bool Value1CompareTo
        {
            get { return this._value1CompareTo; }
            set { this._value1CompareTo = value; }
        }

        private bool _value2CompareTo = true;

        public bool Value2CompareTo
        {
            get { return this._value2CompareTo; }
            set { this._value2CompareTo = value; }
        }



        #endregion

        public ConvertBooleanToVisibilityExtension()
        {
                        
        }
        
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            IProvideValueTarget ipValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                        
            if (this._target == null)
            {
                if (ipValueTarget.TargetObject is FrameworkElement)
                {
                    FrameworkElement target = (FrameworkElement)ipValueTarget.TargetObject;
                    target.DataContextChanged += new DependencyPropertyChangedEventHandler(target_DataContextChanged);
                }

                this._target = ipValueTarget.TargetObject;
                this._targetProperty = ipValueTarget.TargetProperty;
            }

            Visibility vis = CalcCompositeValue();
            return vis;
        }
        

        void target_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            BindingExpression value1BEX = ReadLocalValue(ConvertBooleanToVisibilityExtension.Value1Property) as BindingExpression;
            BindingExpression value2BEX = ReadLocalValue(ConvertBooleanToVisibilityExtension.Value2Property) as BindingExpression;
            
            if (value1BEX != null && BindingSourceNotSet(value1BEX.ParentBinding))
            {
                Binding old = value1BEX.ParentBinding;
                ClearValue(ConvertBooleanToVisibilityExtension.Value1Property);
                Binding newBinding = CopyBinding(ref e, old);
                BindingOperations.SetBinding(this, ConvertBooleanToVisibilityExtension.Value1Property, newBinding);
            }

            if (value2BEX != null && BindingSourceNotSet(value2BEX.ParentBinding))
            {
                Binding old = value2BEX.ParentBinding;
                ClearValue(ConvertBooleanToVisibilityExtension.Value2Property);
                Binding newBinding = CopyBinding(ref e, old);
                BindingOperations.SetBinding(this, ConvertBooleanToVisibilityExtension.Value2Property, newBinding);
            }
        }
        

        private bool BindingSourceNotSet(Binding binding)
        {
            return binding.Source == null && binding.RelativeSource == null;
        }

        private static Binding CopyBinding(ref DependencyPropertyChangedEventArgs e, Binding old)
        {
            Binding b = new Binding();
            b.BindsDirectlyToSource = old.BindsDirectlyToSource;
            b.Converter = old.Converter;
            b.ConverterCulture = old.ConverterCulture;
            b.ConverterParameter = old.ConverterParameter;
            b.FallbackValue = old.FallbackValue;
            b.Mode = old.Mode;
            b.NotifyOnValidationError = old.NotifyOnValidationError;
            b.Path = old.Path;
            if (old.RelativeSource != null)
            {
                b.RelativeSource = old.RelativeSource;
            }

            if (old.ElementName != null)
            {
                b.ElementName = null;
            }
            
            if (old.ElementName == null && b.RelativeSource == null)
            {
                b.Source = e.NewValue;
            }

            b.StringFormat = old.StringFormat;
            b.TargetNullValue = old.TargetNullValue;
            b.UpdateSourceTrigger = old.UpdateSourceTrigger;
            b.ValidatesOnDataErrors = old.ValidatesOnDataErrors;
            b.ValidatesOnExceptions = old.ValidatesOnExceptions;
            b.ValidatesOnNotifyDataErrors = old.ValidatesOnNotifyDataErrors;
            
            return b;
        }
        

        private void UpdateTarget()
        {
            Visibility vis = CalcCompositeValue();
            if (this._target != null && this._targetProperty != null && !(this._target is Binding))
            {
                if (this._targetProperty is PropertyInfo)
                {
                    PropertyInfo pi = (PropertyInfo)this._targetProperty;
                    pi.SetValue(this._target, vis, null);
                }
            }
        }        

        private Visibility CalcCompositeValue()
        {
            bool b = (this.Value1 == this._value1CompareTo && this.Value2 == this._value2CompareTo);
            Visibility vis = b ? Visibility.Visible : Visibility.Collapsed;
            return vis;
        }
    } 

}

 

Download Source Code

Categories: .NET General, Silverlight Tags:

Change tracking in object graph

April 10th, 2012 No comments

Download Source Code from here

On this project I was working on, I had to monitor objects and object graphs for changes so I could display this information to the user. Having looked around the internet for solutions, I could not find any single solution that I felt suited my needs perfectly, so I decided to take a stab at the problem myself.

The requirements for my change tracker was the following:

  • Very easy to use, preferably a single line of code to enable and disable change tracking for an object and its entire object graph
  • The solution had to support an unknown object graph depth and automatically adapt to  changes in the graph depth (attach to new objects added and detach from objects removed, including items added to and removed from collections)
  • Automatically listen for changes to all properties of objects, but support adding exceptions for properties that should not be tracked (if any)
  • Support explicitly defining which properties that should be change tracked
  • Except for implementing the INotifyPropertyChanged and INotifyCollectionChanged interfaces, there should be no other requirements imposed on the objects for enabling change tracking
  • Change notifications should be easy to subscribe to and contain data about the changes that had been made

To jump to the conclusion, what I ended up with was the following, which attaches to an item and starts listening for changes to the item and its entire object graph:

var observer = ObjectGraphObserver.AttachObserver(this.Item1, 
                (s, e) => DumpChangeTrackingInfo(s, e));

//....and detach when the time comes
ObjectGraphObserver.DetachObserver(observer);

 

The first line of code creates an instance of the ObjectGraphObserver which starts listening for changes made to an object (“Item1”) and its object graph by passing in the object root as the first argument. The second argument passed to the method is the callback that should be called when a change occurs. In this case I call a method which dumps information about the change that occurs, but you could also do all other sorts of things like (conditionally) raising a dirty flag to indicate to the user that a change has occurred.

The DumpChangeTrackingInfo method looks like this. You can see I’m using the data passed along from the observer to provide some “useful” information to the user.

private void DumpChangeTrackingInfo(object sender, ObservedPropertyChangedEventArgs e)
{
  if (e is ObjectChangedEventArgs)
  {
      ObjectChangedEventArgs oce = (ObjectChangedEventArgs)e;
      
      StringBuilder sb = new StringBuilder(this._changeTrackingData);
      sb.AppendLine();
      sb.AppendFormat("Property name = '{0}', Sender name = '{2}'", 
          oce.PropertyName, oce.Sender, oce.SenderName);

      this.ChangeTrackingData = sb.ToString();

  }
  else if (e is CollectionChangedEventArgs)
  {
      CollectionChangedEventArgs cce = (CollectionChangedEventArgs)e;
      
      StringBuilder sb = new StringBuilder(this._changeTrackingData);
      sb.AppendLine();
      sb.AppendFormat("Property name = '{0}', Sender name = '{2}', Action = '{3}'", 
          cce.PropertyName, cce.Sender, cce.SenderName, cce.Action);

      if (cce.Action == NotifyCollectionChangedAction.Add)
      {
          for (int i = 0; i < cce.NewItems.Count; i++)
          {
              Level1Item lvlItem = (Level1Item)cce.NewItems[i];
              sb.AppendLine();
              sb.AppendFormat("\t Name = '{0}'", lvlItem.Name);
          }
      }

      this.ChangeTrackingData = sb.ToString();                
  }
}

The AttachObserver and DetachObserver methods looks like this:

public static ObjectGraphObserver AttachObserver(object target, 
	Action<object, ObservedPropertyChangedEventArgs> handler)
{
  ObjectGraphObserver observer = new ObjectGraphObserver(target);
  observer.ChangeDetected += new EventHandler<ObservedPropertyChangedEventArgs>(handler);
  return observer;
}

public static void DetachObserver(ObjectGraphObserver observer)
{
  if (observer != null)
  {
      observer.Detach();
      observer._changeDetectedHandler = null;
      observer = null;
  }
}

When an ObjectGraphObserver is created by passing the root object to the constructor, reflection is used to walk the object graph and setting up the change monitoring. By using reflection, you all know that this solution will not be extremely fast for setting up the change tracking for very large object graphs, but for my needs it was more than fast enough. I usually make sure to create the observers on a separate thread to not block the UI or other processes. The ObjectGraphObserver also has constructor overloads that lets you specify a max depth at which the observer stops tracking changes.

The ObjectGraphObserver passes two kinds of event args to the callback when a change occurs; the ObjectChangedEventArgs for changes to properties of an object, and the CollectionChangedEventArgs when a collection changes (items being added or removed). Both event args contains information about the object that was changed and which property of the object that was affected. The CollectionChangedEventArgs also contains information about the item that was added to or removed from the collection.

To prevent an object in the object graph that implements INotifyPropertyChanged or INotifyCollectionChanged from being change tracked, you can decorate a property with the NonChangeTrackableAttribute attribute. This will make the ObjectGraphObserver ignore that property when it walks the object graph. All properties of a type that implements INotifyPropertyChanged / INotifyCollectionChanged not tagged by the NonChangeTrackableAttribute attribute will be tracked (unless an explicit list of types and properties are specified, which I’ll demonstrate shortly).

The code snippet below shows an example of using the NonChangeTrackableAttribute attribute.

public class Level2ItemWithNonChangeTrackableProp : INPC
{
   private string _name;
   public string Name
   {
       get { return _name; }
       set { SetProperty(ref this._name, value, () => this.Name); }
   }

   // Updating the Child properties will not trigger a change notification 
   // through the ObjectGraphObserver 
   private Level2Item _child;
   [NonChangeTrackable]  
   public Level2Item Child
   {
       get { return _child; }
       set { SetProperty(ref this._child, value, () => this.Child); }
   }
}

The above example shows how to set up exceptions for properties that should NOT be tracked. I also needed to support the opposite approach; explicitly specifying only the types and properties that should be tracked. Using this approach, only the types and properties explicitly defined in a mapping list are being tracked, all the other types and properties are being ignored.

The following example shows how to create an observer which listens for and notifies about changes made to the “Name” and “Count” properties of “Level2Item” instances. I’ve also set the max depth to 10, meaning the observer will not monitor objects deeper than 10 levels from the root object.

 

var observer = ObjectGraphObserver.AttachObserver(this.Item3, 
                GetItem3Properties(), 10, 
                (s, e) => DumpChangeTrackingInfo(s, e));

private Dictionary<Type, List<string>> GetItem3Properties()
{
  Dictionary<Type, List<string>> propMap = new Dictionary<Type, List<string>>();
  propMap.Add(typeof(Level2Item), new List<string> { "Name", "Count" });
  return propMap;
}

I’m not saying this is an ideal solution to the problem, but it works for good for me. There are issues regarding circular references that I do not handle. Also, the fact that I’m using reflection to set things up makes it “slow”, so if you’re planning on using the same approach, be aware of the implications of attaching to a large object (graph).

Categories: .NET General, Silverlight Tags:

Xml namespace simplified in Silverlight 4.0

May 16th, 2010 No comments

 

A feature added to Silverlight 4 that has not received much attention is the introduced support for the XmlnsDefinition and XmlnsPrefix attributes in custom assemblies. These attributes has been supported in WPF since day one, and has finally made their way into Silverlight as well.

The XmlnsDefinitionAttribute is used for mapping one or more CLR namespaces to one or more XML namespaces. The benefit of doing so is that
1) The user does not have to think or know about the CLR namespaces or assembly names when adding xmlns imports in XAML files
2) Because you can map several CLR namepaces to a single XML namespace, you can get away with a single XML namespace in your XAML files instead of one pr. CLR namespace when referencing components in your custom assemblies.

You typically define the XmlnsDefinitionAttribute in the AssemblyInfo.cs-file like so:

[assembly: XmlnsDefinition("http://schemas.mycompany.com/silverlight","MyCompany.Windows.Controls")]
[assembly: XmlnsDefinition("http://schemas.mycompany.com/silverlight", "MyCompany.Windows.Behaviors")]

Then, in a XAML file, you can simply add an xmlns import like this;

<UserControl x:Class="SilverlightApplication2.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
    xmlns:myCompany="http://schemas.mycompany.com/silverlight"

and now you can access the types specified in MyCompany.Windows.Controls and MyCompany.Windows.Behaviors throught the myCompany xmlns prefix.

Further, you can use XmlnsPrefixAttribute to specify a default prefix associated with a XML namespace. This prefix is used by the Visual Studio and Blend designer when you drag/drop custom components from the toolbox onto the designer surface.

[assembly: XmlnsPrefix("http://schemas.mycompany.com/silverlight","myCompany")]
[assembly: XmlnsDefinition("http://schemas.mycompany.com/silverlight","MyCompany.Windows.Controls")]
[assembly: XmlnsDefinition("http://schemas.mycompany.com/silverlight", "MyCompany.Windows.Behaviors")]


 

 
Categories: Silverlight Tags:

Object-To-Object Binding / ViewModel-To-ViewModel Binding

May 9th, 2010 No comments

 

There may be times when you need to pass data back and forth between ViewModels in a loosly coupled way, without having to handle this in your ViewModels or code behind. Consider a parent-child ViewModel scenario, where your parent ViewModel may contain data resulting in the creation of multiple child ViewModels, for example the case when your parent ViewModel has a collection of business objects, and you need to enable editing each business object separately unsing their own View-ViewModels.

The XAML code below has an ItemsControl which binds to the Items collection of the ViewModel set as the DataContext of the parent View.
This will create an instance of the ChildView for each member of the Items collection.

<
ItemsControlItemsSource="{BindingPath=Items}"HorizontalAlignment="Center"VerticalAlignment="Center">
            <
ItemsControl.ItemTemplate>
                <
DataTemplate>
                   
<local:ChildView>
                  
</local:ChildView>
                </DataTemplate>
            </
ItemsControl.ItemTemplate>
        </
ItemsControl>

What we would like, is to pass each item in the Items collection to the ViewModel of each created ChildView so that an action can be taken based on that information.
In order to do this, we can create a binding which binds to a property of the ChildView ViewModel, and one binding that binds to the item from the parent ViewModel Items collection. Then, we need to tie those tow bindings together so that when one of the bound items changes, the other one gets updated as well.

A solution to this may look like this;

<ItemsControl ItemsSource="{Binding Path=Items}" HorizontalAlignment="Center" VerticalAlignment="Center">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding}" Visibility="Collapsed" x:Name="HiddenTextBlock"/>
                        <local:ChildView>
                            <local:BindingBehaviors.ObjectToObjectBinding>
                                <local:ObjectToObjectBinding Source="{Binding Path=Text, ElementName=HiddenTextBlock}" Target="{Binding Path=MyProperty, Mode=TwoWay}"/>
                            </local:BindingBehaviors.ObjectToObjectBinding>
                        </local:ChildView>
                    </Grid>                    
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

What we have done here is to create a binding behavior that ties a Source and Target binding together so that when the Source changes, the Target (MyProperty) gets updated as well. For this example to work, we needed to do a little “hack” by defining the Source binding to bind to a TextBlock which holds on to a member of the Items collection from the parent ViewModel (as the Items collection in this case happens to contain strings. If you need to store an Object, change the TextBlock to a ContentPresenter, for example, and bind to the Content property instead). The Target binding defines a binding to the MyProperty property of the ChildView ViewModel, so that MyProperty will get the value of the item from the Items collection when the ChildView is loaded.

Besides the BindingBehavior class that defines the ObjectToObjectBinding attached property, we need a class for our Source and Target bindings, and also a class that handles / relays the data updates between the Source and Target.

To hold on to our Source and Target bindings, we need a simple ObjectToObjectBinding class with only two properties;

public class ObjectToObjectBinding
    {
        public Binding Source { get; set; }
        public Binding Target { get; set; }
    }

The class that handles exchanging data between the Source and Target is also as simple as it gets; All it does is copy the value of the Source property to the value of the Target propery, and vica verca, when a change occurs.

public class DataRelay : DependencyObject
    {
        #region Source
        
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(object), typeof(DataRelay),
                new PropertyMetadata(null, new PropertyChangedCallback(OnSourceChanged)));
       
        public object Source
        {
            get { return (object)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }
        
        private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((DataRelay)d).OnSourceChanged(e);
        }
        
        protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e)
        {
            this.Target = e.NewValue;
        }

        #endregion



        #region Target
       
        public static readonly DependencyProperty TargetProperty =
            DependencyProperty.Register("Target", typeof(object), typeof(DataRelay),
                new PropertyMetadata(null, new PropertyChangedCallback(OnTargetChanged)));
     
        public object Target
        {
            get { return (object)GetValue(TargetProperty); }
            set { SetValue(TargetProperty, value); }
        }
        
        private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((DataRelay)d).OnTargetChanged(e);
        }
        
        protected virtual void OnTargetChanged(DependencyPropertyChangedEventArgs e)
        {
            this.Source = e.NewValue;
        }

        #endregion        
    }


The code needed to tie the two bindings together is pretty simple;

private static void OnObjectToObjectBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ObjectToObjectBinding binding = e.NewValue as ObjectToObjectBinding;
            if (binding != null)
            {
                if (binding.Source == null)
                    throw new ArgumentNullException("binding.Source");
                if (binding.Target == null)
                    throw new ArgumentNullException("binding.Target");
                
                DataRelay dataRelay = new DataRelay();

                BindingBehaviors.SetObjectToObjectBindingRelay(d, dataRelay);

                BindingOperations.SetBinding(dataRelay, DataRelay.SourceProperty, binding.Source);
                BindingOperations.SetBinding(dataRelay, DataRelay.TargetProperty, binding.Target);
            }
        }

What we have done here, is created a private attached property called ‘ObjectToObjectBindingRelay’ which allows us to attach an instance of the DataRelay object to a DependencyObject (in this case, our ChildView).

Next, we sets the Source binding to update DataRelay Source property, and the Target binding to update the Target property, so that data is exchanged between the source and target object when data changes.

Download source code

Categories: Silverlight Tags:

Using Bindings in Styles in Silverlight

March 28th, 2010 No comments

 

One of the differences between WPF and Silverlight is that using Bindings when setting properties in styles is still not supported (SL 4). If you try to do so, your application will simply crash.
In WPF, you can do something like this;

<Style TargetType="{x:Type ListBoxItem}">
       <Setter Property="Background" Value="{Binding Path=MyColor}"/>
</Style>

Although not directly supported, you can achieve the same thing in Silverlight using attached properties. The workaround is to specify an Attached Property in the Setter Property, and then use a “proxy” in the Setter Value to set up the binding between the actual property of the UI element and the object from the data binding. This technique can also be used for things like wiring up command to react to user input, for example when a user double clicks a ListBoxItem.



The following example shows how to set the background color of a ListBoxItem through data binding, and also how to react to a double click on the item.

<ListBox x:Name="listBox1">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Path=Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>            
            <ListBox.ItemContainerStyle>                
                <Style TargetType="ListBoxItem">
                    <Setter Property="b:BindingBehavior.PropertyBinding">
                        <Setter.Value>
                            <b:PropertyBinding Binding="{Binding Path=BackgroundBrush}" Property="Background"/>
                        </Setter.Value>
                    </Setter>
                    <Setter Property="b:BindingBehavior.DoubleClickCommandBinding">
                        <Setter.Value>
                            <b:CommandBinding Command="{Binding Path=DataContext.TestCommand, ElementName=LayoutRoot}"
                                              CommandParameter="{Binding}"/>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>

As you can see, both the Setter Properties have Attached Properties defined instead of the actual property names of the ListBoxItem. In the Setter Values, I have added my proxy classes where I have specified the bindings and the name of the ListBoxItem property I want a binding to apply to.

The BindingProperty proxy class is very simple and contains only two properties; one for holding the name of the property you want to bind to and one for the binding itself.

public class PropertyBinding
    {
        public Binding Binding { get; set; }        
        public string Property { get; set; }
    }

The BindingBehavior class is responsible for creating the actual binding based on an instance of the PropertyBinding. It does so using reflection on the ListBoxItem to locate the dependency property specified in the Property attribute of the PropertyBinding proxy instance.

private static void OnPropertyBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement frameworkElement = d as FrameworkElement;
            if (frameworkElement == null)
            {
                throw new ArgumentException("The PropertyBinding behavior can only be applied to FrameworkElement types");
            }

            PropertyBinding styleBinding = e.NewValue as PropertyBinding;
            if (styleBinding != null)
            {
                string depPropName = string.Concat(styleBinding.Property, "Property");
                if (depPropName.IndexOf('.') > -1)
                {
                    int index = depPropName.LastIndexOf('.');
                    depPropName = depPropName.Substring(index);
                }
                FieldInfo dependencyPropertyField = frameworkElement.GetType().GetField(depPropName, BindingFlags.Public 
                    | BindingFlags.Static 
                    | BindingFlags.FlattenHierarchy);
                if (dependencyPropertyField != null)
                {
                    DependencyProperty dependencyProperty = dependencyPropertyField.GetValue(null) as DependencyProperty;
                    if (dependencyProperty != null)
                    {
                        frameworkElement.SetBinding(dependencyProperty, styleBinding.Binding);
                    }
                }
            }
 
        }
For the command binding, a similar approach is used. The CommandBinding proxy class contains a Binding property for setting the Command and CommandParameter, and then the BindingBehavior 
class creates the actual binding in the OnXXXChanged event handler.

public class CommandBinding
    {
        public Binding Command { get; set; }
        public Binding CommandParameter { get; set; }
    }
private static void OnDoubleClickCommandBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = d as FrameworkElement;
            if (element == null)
            {
                throw new InvalidOperationException("This behavior can only be applied to FrameworkElement types");
            }

            CommandBinding styleBinding = e.NewValue as CommandBinding;
            if (styleBinding != null)
            {
                element.SetBinding(BindingBehavior.DoubleClickProperty, styleBinding.Command);
                if (styleBinding.CommandParameter != null)
                {
                    element.SetBinding(BindingBehavior.DoubleClickParameterProperty, styleBinding.CommandParameter);
                }
                else
                {
                    element.ClearValue(BindingBehavior.DoubleClickParameterProperty);
                }
            }
            else
            {
                element.ClearValue(BindingBehavior.DoubleClickProperty);
                element.ClearValue(BindingBehavior.DoubleClickParameterProperty);
            }
        }

 

The source code can be downloaded from here. (VS 2010 Solution)

Categories: Silverlight Tags: