navigation
 Sunday, July 20, 2008

When your WPF project gets large with a well structured class diagram, you may run into a case where you need to add a dependency property to a class that it is not convenient to inherit from a dependency object. In that case you need a little help from a helper class. I recently ran into this little problem and came up with this class.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Data; using System.Threading; using System.Windows.Threading; namespace WpfDPHelperTest { /// <summary> /// Gives you a place to host a DependencyProperty without having the owner /// inherit from DependencyObject /// </summary> /// <typeparam name="T">The type of DependencyProperty</typeparam> public class DependencyPropertyHelper<T> : DependencyObject { #region AddBinding public static void AddBinding(string path, object source, BindingMode mode, DependencyObject target, DependencyProperty property) { AddBinding(path, source, mode, target, property, null); } public static void AddBinding(string path, object source, BindingMode mode, DependencyObject target, DependencyProperty property, IValueConverter converter) { Binding binding = new Binding(path); binding.Source = source; binding.Mode = mode; binding.Converter = converter; BindingOperations.SetBinding(target, property, binding); } #endregion #region ValueChanged Event Handler private static void valueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DependencyPropertyHelper<T> helper = (d as DependencyPropertyHelper<T>); if (helper.ValueChanged != null) helper.ValueChanged(d, e); } #endregion #region ValueChanged Event /// <summary> /// This event is tied to the DependencyProperty Value changed event. /// </summary> public event DependencyPropertyChangedEventHandler ValueChanged; #endregion #region Value public readonly static DependencyProperty ValueProperty = DependencyProperty.Register( "Value", typeof(T), typeof(DependencyPropertyHelper<T>), new PropertyMetadata(new PropertyChangedCallback(valueChanged))); /// <summary> /// This is a DependencyProperty /// </summary> public T Value { get { try { if (!this.Dispatcher.CheckAccess()) return (T)Application.Current.Dispatcher.Invoke( System.Windows.Threading.DispatcherPriority.Background, (DispatcherOperationCallback)delegate { return GetValue(ValueProperty); }, ValueProperty); else return (T)GetValue(ValueProperty); } catch { return (T)ValueProperty.DefaultMetadata.DefaultValue; } } set { if (!this.Dispatcher.CheckAccess()) Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate { SetValue(ValueProperty, value); }, value); else SetValue(ValueProperty, value); } } #endregion } }

What this class does is give you a quick and easy way to add a dependency property to any class without having to worry about making your class inherit from the DependencyObject class. The first thing you should notice about this helper class is that it is itself a DependencyObject, which relieves you from having to make the class it's hosted in from being a DependencyObject. The second thing you should notice is that it uses generics to generate the type of the Value DependencyProperty that you need. Other features are that the DependencyProperty is thread safe, it includes a Value changed event, and there are a couple of static functions that will save you the time of having to type all of those code behind binding code lines. In fact you can use these static functions to bind normal dependency properties.

So how can you use this class? Lets start off with a simple WPF window with two sliders and a progress bar:

<Window x:Class="YourNS.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Grid> <ProgressBar Margin="24,81,22,0" Name="progressBar1" Height="28" VerticalAlignment="Top" Maximum="{Binding ElementName=slider1, Path=Maximum, Mode=OneWay}" /> <Slider Height="23" Margin="24,23,26,0" Name="slider1" VerticalAlignment="Top" Maximum="100" /> <Slider Height="23" Margin="24,154,26,0" Name="slider2" VerticalAlignment="Top" Maximum="{Binding ElementName=slider1, Path=Maximum, Mode=OneWay}"/> </Grid> </Window>

In the code behind add the following code:

#region Offset DependencyPropertyHelper<double> _Offset; public DependencyPropertyHelper<double> Offset { get { if (_Offset == null) _Offset = new DependencyPropertyHelper<double>(); return _Offset; } } #endregion

To simplify the example, we are adding the DependencyPropertyHelper instance to the main Window class (which already is a DependencyObject). The main advantage of the DependencyPropertyHelper is that you can add it to a non-DependencyObject class. Notice that this code behind creates an instance of a DependencyPropertyHelper with the Value DependencyProperty cast as a double. Now for the constructor and the Load event handler:

public Window1() { InitializeComponent(); Loaded += new RoutedEventHandler(Window1_Loaded); } void Window1_Loaded(object sender, RoutedEventArgs e) { DependencyPropertyHelper<double>.AddBinding("Value", Offset, BindingMode.TwoWay,
      slider1, Slider.ValueProperty); DependencyPropertyHelper<double>.AddBinding("Value", Offset, BindingMode.TwoWay,
      slider2, Slider.ValueProperty); DependencyPropertyHelper<double>.AddBinding("Value", Offset, BindingMode.OneWay,
      progressBar1, ProgressBar.ValueProperty); }

Notice that we are using our static functions to do the binding, and for each binding we are using our DependencyPropertyHelper.Value as the source. Most importantly, we only have three lines of binding code! Now we can run the project.

DPHelper1

Because we set up the BindingMode as TwoWay for both of the sliders, we can move either one and the change propagates to the other slide as well as the ProgressBar.

DPHelper2

 

When you have multiple bindings to a dependency property like this, it is important to remember to have a clear source to target logic between dependency properties. In this case the DependencyPropertyHelper.Value is the source for all other dependency properties. If the source/targets of one of the three bindings were reversed, then that binding would break. When laying out the source/target logic, just think of it as a hierarchical diagram.

Using a class like the DependencyPropertyHelper can save you a great amount of time in dealing with custom dependency properties and binding operations. In part two we will look into adding converters into the mix.

 |  |  | 
Name
E-mail
Home page

Comment (Some html is allowed: a@href@title, i, strike, u) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview