The blog of dlaa.me

Sometimes you just gotta do the best you can [Tip: Read-only custom DependencyProperties don't exist in Silverlight, but can be closely approximated]

Tip

Read-only custom DependencyProperties don't exist in Silverlight, but can be closely approximated

Explanation

My last tip discussed a special case of creating a Silverlight/WPF DependencyProperty where it's necessary to create a read-only property. (Aside: Read-only DependencyProperties are read-only outside the owning class, but can be changed by the class itself at any time.) This task is quite simple on WPF where a single call to RegisterReadOnly does it all. However, Silverlight (as of version 4) does not support the RegisterReadOnly method, so if you want a read-only DependencyProperty on that platform, you'll need to do some extra work. Unfortunately, I don't think it's possible to do a perfect job - but you can get fairly close with something like the code below. The basic principle is to catch illegal attempts to change the property's value (i.e., those coming from outside the owning class) and undo those changes as quickly and silently as possible. For convenience, the CLR wrapper's setter hides the details from users of the class (in a notable exception to one of the earlier tips). The code looks a little more complicated than it really is because it tries to be resilient to exceptions and because it uses two state variables to avoid calling the class's virtual OnPropertyChanged method when recovering from a bogus change. For consistency, the exception that's thrown after an invalid attempt to change the property's value is similar to the corresponding exception on WPF. And while this approach can't prevent bound values from seeing the property "twitch" briefly, I also don't know of a way to avoid that (recall that the DependencyProperty itself must be public so other code can create Bindings to it). Like I said above, this isn't perfect - but it's pretty close. :)

Good Example

public int MyReadOnly
{
    get { return (int)GetValue(MyReadOnlyProperty); }
    protected set
    {
        try
        {
            _changingMyReadOnly = true;
            SetValue(MyReadOnlyProperty, value);
        }
        finally
        {
            _changingMyReadOnly = false;
        }
    }
}
private bool _changingMyReadOnly;
private bool _restoringMyReadOnly;
public static readonly DependencyProperty MyReadOnlyProperty = DependencyProperty.Register(
    "MyReadOnly", typeof(int), typeof(MyControl), new PropertyMetadata(0, OnMyReadOnlyChanged));
private static void OnMyReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    MyControl myControl = (MyControl)d;
    if (myControl._changingMyReadOnly)
    {
        if (!myControl._restoringMyReadOnly)
        {
            myControl.OnMyReadOnlyChanged((int)e.OldValue, (int)e.NewValue);
        }
    }
    else
    {
        try
        {
            myControl._restoringMyReadOnly = true;
            myControl.MyReadOnly = (int)e.OldValue;
        }
        finally
        {
            myControl._restoringMyReadOnly = false;
        }
        throw new InvalidOperationException("'MyReadOnly' property is read-only and cannot be modified.");
    }
}
protected virtual void OnMyReadOnlyChanged(int oldValue, int newValue)
{
    // TODO: Handle property change
}

More information