views:

446

answers:

2

How can one register a dependency property whose value is calculated using the value of another dependency property?

Because the .NET property wrappers are bypassed by WPF at run-time, one should not include logic in the getters and setters. The solution to that is typically to use PropertyChangedCallbacks. But those are declared static.

For example, what is the proper way to accomplish this contrived task:

public bool TestBool
{
  get { return (bool)GetValue(TestBoolProperty); }
  set 
  { 
    SetValue(TestBoolProperty, value);
    TestDouble = ((value)?(100.0):(200.0)); // HERE IS THE DEPENDENCY
  }
}
public static readonly DependencyProperty TestBoolProperty =
  DependencyProperty.Register("TestBool", typeof(bool), typeof(ViewModel));

public double TestDouble
{
  get { return ((double)GetValue(TestDoubleProperty)); }
  set { SetValue(TestDoubleProperty, value); }
}
public static readonly DependencyProperty TestDoubleProperty =
  DependencyProperty.Register("TestDouble", typeof(double), typeof(ViewModel));

As long as the dependency is not circular, is there a proper means to accomplish this?

+2  A: 

You're actually correct, you should use PropertyChangedCallback. Here's how:

public bool TestBool
{
  get { return (bool)GetValue(TestBoolProperty); }
  set 
  { 
    SetValue(TestBoolProperty, value);
  }
}
public static readonly DependencyProperty TestBoolProperty =
  DependencyProperty.Register("TestBool", typeof(bool), typeof(ViewModel),
  new PropertyMetadata(false, new PropertyChangedCallback(OnTestBoolChanged)));

private static void OnTestBoolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
  ViewModel vm = d as ViewModel;
  vm.TestDouble = value ? 100.0 : 200.0;
}

public double TestDouble
{
  get { return ((double)GetValue(TestDoubleProperty)); }
  set { SetValue(TestDoubleProperty, value); }
}
public static readonly DependencyProperty TestDoubleProperty =
  DependencyProperty.Register("TestDouble", typeof(double), typeof(ViewModel));
opedog
Thank you opedog. In my investigation, I foolishly failed to examine what was passed into the `PropertyChangedCallback`. I adjusted my test project to use this method and it's working. I'm going to try Anvaka's solution next.
Greg
+5  A: 

Hmmm... I think you'd better look at dependency properties value coercion. Here is an example with coercion:

public class ViewModel : DependencyObject
{
  public bool TestBool
  {
    get { return (bool)GetValue(TestBoolProperty); }
    set { SetValue(TestBoolProperty, value); }
  }
  public static readonly DependencyProperty TestBoolProperty =
    DependencyProperty.Register("TestBool", typeof(bool), typeof(ViewModel), new PropertyMetadata(false, OnTestBoolPropertyChanged));

  private static void OnTestBoolPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    var vm = (ViewModel)d;
    vm.CoerceValue(TestDoubleProperty);
  }

  public double TestDouble
  {
    get { return ((double)GetValue(TestDoubleProperty)); }
    set { SetValue(TestDoubleProperty, value); }
  }
  public static readonly DependencyProperty TestDoubleProperty =
    DependencyProperty.Register("TestDouble", typeof(double), typeof(ViewModel), new PropertyMetadata(0.0, null, OnCoerceTestDouble));

  private static object OnCoerceTestDouble(DependencyObject d, object baseValue)
  {
    var vm = (ViewModel) d;
    var testBool = vm.TestBool;
    return ((testBool) ? (100.0) : (200.0));
  }
}
Anvaka
What is the advantage of using a `CoerceValueCallback` as you have done versus directly changing a dependency property from within another dependency property's `PropertyChangedCallback` as opedog did? I gather from the documentation you linked that yours is the more proper method, but I am curious about the practical difference.
Greg
Well, to name couple: it doesn't break bindings to this property (i.e. if this property is a target of binding expression it will work after coercing but will be lost after explicit set); it has higher priority in dependency property value resolution (i.e. if you say PropA = "Something" that doesn't mean that PropA == "Something", since coercion could ignore that assignment); It remembers the old value of your property (i.e. next time you call CoerceValue() you will get original value of TestDouble, not the one that was set locally)
Anvaka
I understand. Thank you for your answer and the followup information. I've adjusted my code to use this method and it's working perfectly.
Greg
Glad I could help :). Cheers
Anvaka