WPF Dependency Property Based on another dependency property [duplicate] - c#

The code below is my current solution.
A great example of what I am trying to mimic would be the FrameworkElement.ActualWidth property. You know how the ActualWidth property is calculated and reassigned, whenever the Width property changes, or whenever the control is redrawn, or whenever else? ------
From the developer's perspective, it just looks like data-binding hard-at-work.
But ActualWidth is a read-only dependency-property. Does Microsoft really have to go through this gigantic trash-hole of code to make that work? Or is there a simpler way that utilizes the existing functionality of the data-binding system?
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 { }
}
}

Unfortunately, you'll need most of what you have. The IValueConverter isn't required in this case, so you could simplify it down to just:
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);
}
}
}

It's not as bad as you suggest, IMHO...
You could get rid of the converter : IValueConverter is for bindings, you don't need it for conversions in code-behind. Apart from that, I don't see how you could make it more concise...

Yes, there is a clean way to "make a read-only DependencyProperty reflect the value of another property," but it may require a pretty fundamental shift in the overall property programming model of your app. In short, instead of using the DependencyPropertyKey to push values into the property, every read-only DependencyProperty can have a CoerceValue callback which builds its own value by pulling all the source values it depends on.
In this approach, the 'value' parameter that's passed in to CoerceValue is ignored. Instead, each DP's CoerceValue function recalculates its value "from scratch" by directly fetching whatever values it needs from the DependencyObject instance passed in to CoerceValue (you can use dobj.GetValue(...) for this if you want to avoid casting to the owner instance type).
Try to suppress any suspicion that ignoring the value supplied to CoerceValue may be wasting something. If you adhere to this model, those values will never be useful and the overall work is the same or less than a "push" model because source values that haven't changed are, as always, cached by the DP system. All that's changed is who's responsible for the calculation and where it's done. What's nice here is that calculation of each DP value is always centralized in one place and specifically associated with that DP, rather than strewn across the app.
You can throw away the DependencyPropertyKey in this model because you'll never need it. Instead, to update the value of any read-only DP you just call CoerceValue or InvalidateValue on the owner instance, indicating the desired DP. This works because those two functions don't require the DP key, they use the public DependencyProperty identifier instead, and they're public functions, so any code can call them from anywhere.
As for when and where to put these CoerceValue/InvalidateValue calls, there are two options:
Eager: Put an InvalidateValue call for the (target) DP in the PropertyChangedCallback of every (source) DP that's mentioned in the (target) DP's CoerceValueCallback function, --or--
Lazy: Always call CoerceValue on the DP immediately prior to fetching its value.
It's true that this method is not so XAML-friendly, but that wasn't a requirement of the OPs question. Considering, however, that in this approach you don't ever even need to fetch or retain the DependencyPropertyKey at all, it seems like it might one of the sleekest ways to go, if you're able to reconceive your app around the "pull" semantics.
In a completely separate vein, there's yet another solution that may be even simpler:
Expose INotifyPropertyChanged on your DependencyObject and use CLR properties for the read-only properties, which will now have a simple backing field. Yes, the WPF binding system will correctly detect and monitor both mechanisms--DependencyProperty and INotifyPropertyChanged--on the same class instance. A setter, private or otherwise, is recommended for pushing changes to this read-only property, and this setter should check the backing field to detect vacuous (redundant) changes, otherwise raising the old-style CLR PropertyChanged event.
For binding to this read-only property, either use the owner's OnPropertyChanged overload (for self-binding) to push in the changes from DPs, or, for binding from arbitrary external properties, use System.ComponentModel.DependencyPropertyDescriptor.FromProperty to get a DependencyPropertyDescriptor for the relevant souce DPs, and use its AddValueChanged method to set a handler which pushes in new values.
Of course for non-DP properties or non-DependencyObject instances, you can just subscribe to their INotifyPropertyChanged event to monitor changes that might affect your read-only property. In any case, no matter which way you push changes into the read-only property, the event raised by its setter ensures that changes to the read-only property correctly propagate onwards to any further dependent properties, whether WPF/DP, CLR, data-bound or otherwise.

Related

List DependencyProperty always returning null

I have a custom control "ToolbarMenuButton" with the following DependencyProperty:
public ObservableCollection<object> TbMenuItems
{
get { return (ObservableCollection<object>)GetValue(TbMenuItemsProperty); }
set { SetValue(TbMenuItemsProperty, value); }
}
public static readonly DependencyProperty TbMenuItemsProperty =
DependencyProperty.Register("TbMenuItems", typeof(ObservableCollection<object>), typeof(ToolbarMenuButton), new PropertyMetadata(null));
I set it like so:
<customs:ToolbarMenuButton TbText="By Flight" TbIcon="PlaneRotated45"
TbMenuItems="{Binding Flights}"
TbItemCommand="{Binding FlightSelect}">
And it shows up, no problem. Now, there's a click handler for this button in the custom control that makes sure the context menu is set, and if it isn't, it creates a new one based on the dependencyproperty "TbMenuItems" shown above.
The Error:
This property is always null (I get a null exception in runtime when I click the button). I've been through about 40 stackoverflow answers regarding this they are either N/A or didn't fix it. As I understand, get/set on dependency properties don't get called, but I'm not sure how then I'm ever supposed to get the data from it.
What I've Tried:
I've tried notifying property changed when Flights is set. I've ensured Flights is set by putting one of them in a textblock right next to the button (so I also know the datacontext and path, etc are all correct). I've changed this to an observable collection (was originally a list) to see if that helped. The other dependency properties all seem to work just fine (of course, they are also bound to the data template in the style, not sure if that matters). I'm not sure where to go, now.
The problem is that you're using a too specific type for your collection-type property, which is not assignment compatible with the value produced by the data binding.
You should instead use the most generic collection type that can possibly be used, usually IEnumerable:
public IEnumerable TbMenuItems
{
get { return (IEnumerable)GetValue(TbMenuItemsProperty); }
set { SetValue(TbMenuItemsProperty, value); }
}
public static readonly DependencyProperty TbMenuItemsProperty = DependencyProperty.Register(
nameof(TbMenuItems), typeof(IEnumerable), typeof(ToolbarMenuButton));

Updating a secondary property from a DependencyProperty

I have a WPF control that is based on the TextBox control:
public class DecimalTextBox : TextBox
I have a dependency property that is bound to, which manages the numeric value, and is responsible for setting the Text property:
public decimal NumericValue
{
get { return (decimal)GetValue(NumericValueProperty); }
set
{
if (NumericValue != value)
{
SetValue(NumericValueProperty, value);
SetValue(TextProperty, NumericValue.ToString());
System.Diagnostics.Debug.WriteLine($"NumericValue Set to: {value}, formatted: {Text}");
}
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (decimal.TryParse(Text, out decimal num))
{
SetValue(NumericValueProperty, num);
}
}
This works well when entering a value into the textbox itself (it updates the underlying values, etc...). However, when the bound property of NumericValue is changed, despite updating the NumericValue DP, the Text property is not updated. In the tests that I've done, it would appear that the reason for this is that the set method above is not called when the bound value is updated. The binding in question looks like this:
<myControls:DecimalTextBox NumericValue="{Binding Path=MyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Can anyone point me in the right direction as to why this property setter is not firing, or is there a better way to approach this?
As explained in Custom Dependency Properties and XAML Loading and Dependency Properties, you should not call anything else than GetValue and SetValue in the CLR wrapper of a dependency property:
Because the current WPF implementation of the XAML processor behavior for property setting bypasses the wrappers entirely, you should not put any additional logic into the set definitions of the wrapper for your custom dependency property. If you put such logic in the set definition, then the logic will not be executed when the property is set in XAML rather than in code.
In order to get notified about value changes, you'll have to register a PropertyChangedCallback with the dependency property metadata.
public static readonly DependencyProperty NumericValueProperty =
DependencyProperty.Register(
"NumericValue", typeof(decimal), typeof(DecimalTextBox),
new PropertyMetadata(NumericValuePropertyChanged));
public decimal NumericValue
{
get { return (decimal)GetValue(NumericValueProperty); }
set { SetValue(NumericValueProperty, value); }
}
private static void NumericValuePropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var textBox = (DecimalTextBox)obj;
textBox.Text = e.NewValue.ToString();
}
The WPF binding is not actually using your getter and setter, but instead directly interacts with the dependency property NumericValueProperty. In order to update the text, subscribe to the PropertyChanged event of the NumericValueProperty instead of trying to do anything special in the setter.
Subscribe to the change in your DependencyProperty definition, similar to the following:
// Using a DependencyProperty as the backing store for NumericValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NumericValueProperty =
DependencyProperty.Register("NumericValue", typeof(decimal), typeof(DecimalTextBox), new FrameworkPropertyMetadata(0.0m, new PropertyChangedCallback(OnNumericValueChanged)));
private static void OnNumericValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as DecimalTextBox;
// if the new numeric value is different from the text value, update the text
}

Dependency property in custom control is sharing memory/values unexpectedly

I have the following set up:
Custom WPF Control (Base class), deriving from Canvas
Implementation of that base class
An ObservableCollection<T> dependency property on that implementation
I have a test app that displays three unique instances of my custom control (e.g. <custom:MyControl x:Name="Test1" />, Test2, Test3, and so on). When I run and debug the app, the contents of the ObservableCollection<T> are the same for all three instances of the control. Why is this?
Chart:
[ContentProperty("DataGroups")]
public abstract class Chart : Canvas
{
static Chart()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Chart), new FrameworkPropertyMetadata(typeof(Chart)));
}
public ObservableCollection<ChartData> DataGroups
{
get { return (ObservableCollection<ChartData>)GetValue(DataGroupsProperty); }
set { SetValue(DataGroupsProperty, value); }
}
public static readonly DependencyProperty DataGroupsProperty =
DependencyProperty.Register("DataGroups", typeof(ObservableCollection<ChartData>), typeof(Chart), new FrameworkPropertyMetadata(new ObservableCollection<ChartData>(), FrameworkPropertyMetadataOptions.AffectsArrange));
public abstract void Refresh();
}
ChartData:
[ContentProperty("Points")]
public class ChartData : FrameworkElement
{
public ObservableCollection<Point> Points
{
get { return (ObservableCollection<Point>)GetValue(PointsProperty); }
set { SetValue(PointsProperty, value); }
}
public static readonly DependencyProperty PointsProperty =
DependencyProperty.Register("Points", typeof(ObservableCollection<Point>), typeof(ChartData), new PropertyMetadata(new ObservableCollection<Point>()));
}
One way I modify the chart data is (assuming multiple data groups), for example:
MyChart.DataGroups[index].Points.Add(new Point() { Y = someNumber });
MyChart.Refresh();
But every instance inside DataGroups[] is identical.
The same thing is happening if I define my collection(s) via XAML, like so:
<c:Chart x:Name="ChartA">
<c:ChartData x:Name="DataGroup1" />
<c:ChartData x:Name="DataGroup2" />
</c:Chart>
Then, in code, I would access the defined collections:
ChartA.DataGroups[0].Points.Add(new Point() { Y = someNumber });
ChartA.Refresh();
You havent done anything wrong. It is by design. It shall work that way. Just set your value in constructor instead and you will not have a singleton.
http://msdn.microsoft.com/en-us/library/aa970563.aspx
Initializing the Collection Beyond the Default Value
When you create a dependency property, you do not specify the property default value as the initial field value. Instead, you specify the default value through the dependency property metadata. If your property is a reference type, the default value specified in dependency property metadata is not a default value per instance; instead it is a default value that applies to all instances of the type. Therefore you must be careful to not use the singular static collection defined by the collection property metadata as the working default value for newly created instances of your type. Instead, you must make sure that you deliberately set the collection value to a unique (instance) collection as part of your class constructor logic. Otherwise you will have created an unintentional singleton class.
PointsProperty is a static value that you initialize with a default value of new ObservableCollection<Point>(). This static initializer creates a single ObservableCollection and uses that as the default value for Points on any object of type ChartData that you create. It is not a factory that creates new ObservableCollections for every instance that needs a default value; it simply uses the same ObservableCollection for each.
I'm guessing that you never explicitly assign a value to Points, thus always relying on the default value, which is shared across all instances. That's why each instance has the same collection of points.

C# function called after dependency property have been set

My code currently looks like this:
private Foo myFoo;
public Foo CurrentFoo
{
get { return myFoo; }
set { SetFoo(value); }
}
private void SetFoo(Foo newFoo)
{
// Do stuff
// Here be dragons
myFoo = newFoo;
}
To be able to bind it in XAML/WPF I need to turn Foo into a dependency property:
public static DependencyProperty CurrentFooProperty =
DependencyProperty.Register("CurrentFoo", typeof(Foo),
typeof(FooHandler), new PropertyMetadata(false));
public Foo CurrentFoo
{
get { return (Foo)GetValue(CurrentFooProperty); }
set { SetValue(CurrentFooProperty, value); }
}
Ive heard that you shouldnt do magic inside the actual C# property set {}, since it might not be called but the value is written directly to the dependency property. If this is false, let me know, it seems like the most obvious and simple route to take.
I know I can add a validation function to the dependency property but I assume that it shouldnt be used for this? I need to communicate the change to legacy systems that cannot yet be bound in XAML.
Whats the best way to approach this problem?
public static DependencyProperty CurrentFooProperty = DependencyProperty.Register(
"CurrentFoo",
typeof(Foo),
typeof(FooHandler),
new PropertyMetadata(OnCurrentFooChanged));
private static void OnCurrentFooChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var currentFoo = (Foo) e.NewValue;
// Use
}
This should be handled by a callback assigned to the Dependency Property. The proper place for this depends on what is actually happening here:
// Do stuff
// Here be dragons
There are three callbacks that can be assigned to a Dependency Property - If you just need to notify the legacy systems, then I recommend adding a Property Changed callback, and adding the notification there.
If, however, this is performing something that amounts to validation via the Legacy system, it may be more appropriate to put these dragons into the ValidationCallback.
I doubt that the CoerceValue callback is appropriate, given your scenario.

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

The code below is my current solution.
A great example of what I am trying to mimic would be the FrameworkElement.ActualWidth property. You know how the ActualWidth property is calculated and reassigned, whenever the Width property changes, or whenever the control is redrawn, or whenever else? ------
From the developer's perspective, it just looks like data-binding hard-at-work.
But ActualWidth is a read-only dependency-property. Does Microsoft really have to go through this gigantic trash-hole of code to make that work? Or is there a simpler way that utilizes the existing functionality of the data-binding system?
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 { }
}
}
Unfortunately, you'll need most of what you have. The IValueConverter isn't required in this case, so you could simplify it down to just:
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);
}
}
}
It's not as bad as you suggest, IMHO...
You could get rid of the converter : IValueConverter is for bindings, you don't need it for conversions in code-behind. Apart from that, I don't see how you could make it more concise...
Yes, there is a clean way to "make a read-only DependencyProperty reflect the value of another property," but it may require a pretty fundamental shift in the overall property programming model of your app. In short, instead of using the DependencyPropertyKey to push values into the property, every read-only DependencyProperty can have a CoerceValue callback which builds its own value by pulling all the source values it depends on.
In this approach, the 'value' parameter that's passed in to CoerceValue is ignored. Instead, each DP's CoerceValue function recalculates its value "from scratch" by directly fetching whatever values it needs from the DependencyObject instance passed in to CoerceValue (you can use dobj.GetValue(...) for this if you want to avoid casting to the owner instance type).
Try to suppress any suspicion that ignoring the value supplied to CoerceValue may be wasting something. If you adhere to this model, those values will never be useful and the overall work is the same or less than a "push" model because source values that haven't changed are, as always, cached by the DP system. All that's changed is who's responsible for the calculation and where it's done. What's nice here is that calculation of each DP value is always centralized in one place and specifically associated with that DP, rather than strewn across the app.
You can throw away the DependencyPropertyKey in this model because you'll never need it. Instead, to update the value of any read-only DP you just call CoerceValue or InvalidateValue on the owner instance, indicating the desired DP. This works because those two functions don't require the DP key, they use the public DependencyProperty identifier instead, and they're public functions, so any code can call them from anywhere.
As for when and where to put these CoerceValue/InvalidateValue calls, there are two options:
Eager: Put an InvalidateValue call for the (target) DP in the PropertyChangedCallback of every (source) DP that's mentioned in the (target) DP's CoerceValueCallback function, --or--
Lazy: Always call CoerceValue on the DP immediately prior to fetching its value.
It's true that this method is not so XAML-friendly, but that wasn't a requirement of the OPs question. Considering, however, that in this approach you don't ever even need to fetch or retain the DependencyPropertyKey at all, it seems like it might one of the sleekest ways to go, if you're able to reconceive your app around the "pull" semantics.
In a completely separate vein, there's yet another solution that may be even simpler:
Expose INotifyPropertyChanged on your DependencyObject and use CLR properties for the read-only properties, which will now have a simple backing field. Yes, the WPF binding system will correctly detect and monitor both mechanisms--DependencyProperty and INotifyPropertyChanged--on the same class instance. A setter, private or otherwise, is recommended for pushing changes to this read-only property, and this setter should check the backing field to detect vacuous (redundant) changes, otherwise raising the old-style CLR PropertyChanged event.
For binding to this read-only property, either use the owner's OnPropertyChanged overload (for self-binding) to push in the changes from DPs, or, for binding from arbitrary external properties, use System.ComponentModel.DependencyPropertyDescriptor.FromProperty to get a DependencyPropertyDescriptor for the relevant souce DPs, and use its AddValueChanged method to set a handler which pushes in new values.
Of course for non-DP properties or non-DependencyObject instances, you can just subscribe to their INotifyPropertyChanged event to monitor changes that might affect your read-only property. In any case, no matter which way you push changes into the read-only property, the event raised by its setter ensures that changes to the read-only property correctly propagate onwards to any further dependent properties, whether WPF/DP, CLR, data-bound or otherwise.

Categories

Resources