Is there a * pure * way to make a read-only dependency property reflect the value of another property?

The code below is my current solution.

If you need a couple of minutes to figure out what this code does, I hear ya .

It's an ugly mess if it ever was. I would kill to see an alternative (but don't let this stop you from answering ... :-). Oh god, I even abbreviated it (significantly) by deleting the converter code, and I still feel dyslexic when I look at this code.

A great example of what I'm trying to simulate is the FrameworkElement.ActualWidth property. Do you know how the ActualWidth property is computed and reassigned when the Width property changes or when the control is redrawn or ever? ------

From a developer's point of view, it just looks like data binding to work.
But ActualWidth is a read-only dependency property. Does Microsoft really have to go through this giant trial version of the code to make this work? Or is there an easier way to use the existing data binding system functionality?

public class foo : FrameworkElement { [ValueConversion(typeof(string), typeof(int))] public class fooConverter : IValueConverter { public object Convert( object value, Type targetType, object parameter, CultureInfo culture) { ... } public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture) { ... } } private static readonly fooConverter fooConv = new fooConverter(); private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey = DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int), typeof(foo), null); public int ReadOnlyInt { get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); } } public static readonly DependencyProperty ReadWriteStrProperty = DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo), new PropertyMetadata(ReadWriteStr_Changed)); public string ReadWriteStr { get { return (string)GetValue(ReadWriteStrProperty); } set { SetValue(ReadWriteStrProperty, value); } } private static void ReadWriteStr_Changed( DependencyObject d, DependencyPropertyChangedEventArgs e) { try { if (d is foo) { foo f = d as foo; f.SetValue( ReadOnlyIntPropertyKey, fooConv.Convert(f.ReadWriteStr, typeof(int), null, CultureInfo.CurrentCulture)); } } catch { } } } 
+4
source share
3 answers

Unfortunately, you will need most of what you have. In this case, IValueConverter is not required, so you can simplify it to:

 public class foo : FrameworkElement { private static readonly DependencyPropertyKey ReadOnlyIntPropertyKey = DependencyProperty.RegisterReadOnly( "ReadOnlyInt", typeof(int), typeof(foo), null); public int ReadOnlyInt { get { return (int)GetValue(ReadOnlyIntPropertyKey.DependencyProperty); } } public static readonly DependencyProperty ReadWriteStrProperty = DependencyProperty.Register( "ReadWriteStr", typeof(string), typeof(foo), new PropertyMetadata(ReadWriteStr_Changed)); public string ReadWriteStr { get { return (string)GetValue(ReadWriteStrProperty); } set { SetValue(ReadWriteStrProperty, value); } } private static void ReadWriteStr_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e) { foo f = d as foo; if (f != null) { int iVal; if (int.TryParse(f.ReadWriteStr, out iVal)) f.SetValue( ReadOnlyIntPropertyKey, iVal); } } } 
+4
source

This is not as bad as you suggest, IMHO ...

You can get rid of the converter: IValueConverter intended for bindings, you do not need it for conversions in encoding. Also, I don't understand how you could make it more concise ...

+1
source

Yes, there is a clean way of “making read-only DependencyProperty reflect the value of another property,” but this may require a fairly fundamental shift in the general property programming model of your application. In short, instead of using DependencyPropertyKey to push values ​​in a property, each readable DependencyProperty can have a CoerceValue callback, which builds its own value to stretch all the source values ​​on which it depends.

In this approach, the value parameter passed to CoerceValue is ignored. Instead, each DP CoerceValue function recalculates its value from scratch, selecting the desired values ​​from the DependencyObject instance passed to CoerceValue (you can use dobj.GetValue(...) to do this if you want to avoid casting to the owner instance type).

Try to suppress any suspicions that ignoring the value specified in CoerceValue can lead to waste. If you stick to this model, these values ​​will never be useful, and the overall work will be the same or less than the "push" model, since the unchanged initial values ​​are, as always, cached by the DP system. All that has changed is the one who is responsible for the calculation and where he did it. What is nice here is that the calculation of each DP value is always centralized in one place and specifically related to that DP, and not scattered throughout the application.

You can throw away DependencyPropertyKey in this model because you will never need it. Instead, to update the value of any read-only DP, you simply call CoerceValue or InvalidateValue on the owner instance, specifying the desired DP. This works because these two functions do not require a DP key. instead, they use the public DependencyProperty identifier, and they are public functions, so any code can call them from anywhere.

As for when and where to put these calls to CoerceValue / InvalidateValue , there are two options:

  • Waiting: Place an InvalidateValue call for the (target) DP in the PropertyChangedCallback each (source) DP that is mentioned in the (target) DP function CoerceValueCallback ,
    -OR, -
  • Lazy: Always call CoerceValue on a DP just before retrieving its value.

It is true that this method is not so convenient for XAML, but this was not a requirement for the OP question. Considering, however, that in this approach you don’t need to retrieve or save the DependencyPropertyKey , it seems that this may be one of the most successful ways if you can restore your application around the pull semantics.


In a completely separate vein, there is another solution that could be even simpler:

Open INotifyPropertyChanged on DependencyObject and use the CLR properties for read-only properties that will now have a simple support field. Yes, the WPF binding system will correctly detect and control both mechanisms - DependencyProperty and INotifyPropertyChanged - in one instance of the class. To change this read-only property, it is recommended that you use a setter, both private and different, and this setter should check the support field to detect empty (redundant) changes, otherwise this could lead to an old-style CLR PropertyChanged event.

To bind to this read-only property, use either OnPropertyChanged owner OnPropertyChanged (for self-learning) to make changes from DP, or to bind to arbitrary external properties, use System.ComponentModel.DependencyPropertyDescriptor.FromProperty to get the DependencyPropertyDescriptor for the corresponding DP addresses and use its AddValueChanged method to set a handler that clicks on the new values.

Of course, for non-DP properties or instances other than DependencyObject , you can simply subscribe to their INotifyPropertyChanged event to track changes that might affect your read-only property. In any case, no matter how you insert the changes into the read-only property, the event generated by its setter ensures that changes to the read-only properties are propagated correctly to any additional dependent properties, be it WPF / DP, CLR , data-related or otherwise.

+1
source

All Articles