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.