1
\$\begingroup\$

I have an UserControl where I declare two DependencyProperties

int? Level;
CardBase SelectedCardBase;

A non-null CardBase object will always have 2 read-only integers: MinLevel and MaxLevel.

The UserControl allows the user to change as he desires both DPs, and the internal logic must ensure that both DPs have valid values following these rules:

  • If SelectedCardBase is null, then Level must be null.
  • If SelectedCardBase is not null, then Level must be between MinLevel and MaxLevel.
  • When SelectedCardBase changes to anything but null, Level is set to MaxLevel.

This is the code:

    public CardBase SelectedCardBase
    {
        get { return (CardBase)GetValue(SelectedCardBaseProperty); }
        set { SetValue(SelectedCardBaseProperty, value); }
    }

    public static readonly DependencyProperty SelectedCardBaseProperty = 
         DependencyProperty.Register("SelectedCardBase", typeof(CardBase), typeof(MyUserControl), 
         new PropertyMetadata(null, new PropertyChangedCallback(SelectedCardBaseChanged)));        

    private static void SelectedCardBaseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue == null)
            d.SetValue(LevelProperty, null);
        else
            d.SetValue(LevelProperty, ((CardBase)e.NewValue).MaxLevel);
    }

    public int? Level
    {
        get { return (int?)GetValue(LevelProperty); }
        set { SetValue(LevelProperty, value); }
    }
    public static readonly DependencyProperty LevelProperty =
        DependencyProperty.Register("Level", typeof(int?), typeof(MyUserControl), 
        new PropertyMetadata(null, new PropertyChangedCallback(LevelChanged)));
    private static void LevelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CardBase card = (CardBase)d.GetValue(SelectedCardBaseProperty);

        if (card == null)
        {
            if (e.NewValue != null)
                d.SetValue(LevelProperty, null);
        }
        else if (e.NewValue == null)
        {
            d.SetValue(LevelProperty, e.OldValue);
        }
        else
        {
            int level = (int)e.NewValue;

            if (level < card.MinLevel)
                d.SetValue(LevelProperty, card.MinLevel);
            else if (level > card.MaxLevel)
                d.SetValue(LevelProperty, card.MaxLevel);
        }
    }

I don't fully get how CoerceValueCallbacks work, and I've read (or misunderstood) that both Coerce and Changed callbacks can be overwritten because they are metadata, so my questions are:

  • What problems (if they exist) will I have when something subscribes to the OnValueChanged of one of those DP?
  • Should I use CoerceValueCallbacks instead of this?
  • If so, what is the correct way of doing this using CoerceValueCallbacks?
  • And of course, with the given rules at the beginning, there is any mistake on the code that would cause strange behaviour, or anything that could be improved?
\$\endgroup\$
2
  • \$\begingroup\$ Why are you using ChangedCallback for validation when DependencyPropertyManager.Register has an explicit override for providing a Validation callback? msdn.microsoft.com/en-us/library/ms597501(v=vs.110).aspx \$\endgroup\$
    – Nick Udell
    Jan 29, 2015 at 12:47
  • \$\begingroup\$ @NickUdell precisely because I'm still learning the ropes of the DP system and some details get out of my knowledge. What should I do then, register them on the constructor of the usercontrol? \$\endgroup\$ Jan 29, 2015 at 12:57

1 Answer 1

1
\$\begingroup\$

Yes, you should use CoerceValueCallback instead to coerce your Level property (what you are trying to do is not "validation" strictly speaking).

private static object CoerceLevel(DependencyObject d, object value)
{
    var control = (MyUserControl)d;
    if (control.SelectedCard == null) return null;
    var level = (int?)value;
    if (level.HasValue) 
    { 
        return Math.Min(Math.Max(level.Value, control.SelectedCard.MinLevel), control.SelectedCard.MaxLevel);
    }
    else
    {
        return control.Level;
        //or 
        //return DependencyProperty.UnsetValue;
    }
}
\$\endgroup\$
5
  • \$\begingroup\$ Will this be called automatically whenever the Level is set? \$\endgroup\$ Jan 29, 2015 at 13:42
  • \$\begingroup\$ @GuillermoMestre, yes, if you add this calllback to your dependency property declaration. \$\endgroup\$
    – Nikita B
    Jan 29, 2015 at 13:50
  • \$\begingroup\$ And the CardChangedCallback should remain as it is, right? \$\endgroup\$ Jan 29, 2015 at 13:55
  • \$\begingroup\$ @GuillermoMestre, pretty much, yes. You should use PropertyChangedCallback, when you want to change other proprties, and CoerceValueCallback when you want to alter the changed property itself. You might want to cast first argument to MyUserControl and use regular properties though. d.SetValue calls do not look pretty. \$\endgroup\$
    – Nikita B
    Jan 29, 2015 at 14:01
  • \$\begingroup\$ @GuillermoMestre, you can read more on msdn msdn.microsoft.com/en-us/library/ms745795%28v=vs.110%29.aspx \$\endgroup\$
    – Nikita B
    Jan 29, 2015 at 14:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.