Dependency property in custom control is sharing memory/values unexpectedly - c#

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.

Related

WPF Dependency Property Based on another dependency property [duplicate]

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.

ObservableCollection is Null when I try to add TextBlocks through XAML to a custom control

In my WPF Project, i am trying to create an ObservalbeCollection dependency property inside a custom control. I am not sure if i am doing it the right way but i am using the below code :
public static readonly DependencyProperty MenuOptionsDependency = DependencyProperty.Register("MenuOptions", typeof(ObservableCollection<TextBlock>), typeof(DropDownButton));
public ObservableCollection<TextBlock> MenuOptions
{
get
{
return (ObservableCollection<TextBlock>)GetValue(MenuOptionsDependency);
}
set
{
SetValue(MenuOptionsDependency, value);
}
}
The problem arises when i try to add TextBlocks through XAML to this control as follows :
<local:CustomControl1 x:Name="cmm">
<local:CustomControl1.MenuOptions>
<TextBlock/>
</local:CustomControl1.MenuOptions>
</local:CustomControl1>
This throws a design time error saying :
Collection 'CustomControl1'.'MenuOptions' is null
After going through the accepted answer on this SO post, i understand ObservableCollection, being a reference type, will be null as it will be default value. I read through the answer but i am still unclear/unsure on how to implement the solution in my situation.
I need to be able to add objects to the collection through XAML(and also through C#). I would really appreciate if someone points out where i am missing/what i am doing wrong.
You must never set the default value of a collection type dependency property to something else than null. When you assign a non-null default value by (static) property metadata, all instances of your control will use the same collection object.
Instead, set a default value in the control's constructor by SetCurrentValue:
public DropDownButton()
{
SetCurrentValue(MenuOptionsDependency, new ObservableCollection<TextBlock>());
}
Besides that, there is a strict naming convention, according to which the dependency property identifier field must be named as the property with a Property suffix. So your MenuOptionsDependency should actually be named MenuOptionsProperty.
It's also unclear whether the property type really needs to be ObservableCollection. You don't seem to register a CollectionChanged event handler anywhere, which indicates that your control is not supposed to react on such changes.
Consider a property declaration like this:
public DropDownButton()
{
SetCurrentValue(MenuOptionsProperty, new List<TextBlock>());
}
public static readonly DependencyProperty MenuOptionsProperty =
DependencyProperty.Register(
nameof(MenuOptions), typeof(IEnumerable<TextBlock>), typeof(DropDownButton));
public IEnumerable<TextBlock> MenuOptions
{
get { return (IEnumerable<TextBlock>)GetValue(MenuOptionsProperty); }
set { SetValue(MenuOptionsProperty, value); }
}

Custom DependencyProperty set from DataTemplate

I'm working with a custom control that has several user-defined dependency properties. I'm running into the same issue described in this question.
My control is setting the default value of a custom dependency property in its constructor. When I use the control in a DataTemplate, the value set in the constructor is always used, even if I try to set it in XAML.
The answer to the linked question explains that the value set in the C# code has a higher priority, and a better approach would be to specify the default value in the dependency property's metadata.
In my case, I can't specify a default because the dependency property doesn't have a single default value that applies in all cases. The default values depend on another property, so I must look them up when the control is created and not when the property is registered.
Here's some code to help illustrate my problem:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty MyProperty =
DependencyProperty.Register(
"MyProperty",
typeof(int),
typeof(MyControl),
new FrameworkPropertyMetadata(
int.MinValue,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback("OnMyPropertyChanged")));
public MyControl() : base()
{
InitializeComponent();
this.MyProperty = GetDefaultPropertyValue();
}
public int MyProperty
{
get { return (int)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
private int GetDefaultPropertyValue()
{
// look up the appropriate default based on some other criteria
return 42;
// (in reality, the default value for "MyProperty"
// depends on the value of a "Mode" custom DependencyProperty.
// this is just hard coded for testing)
}
}
The XAML usage looks something like this:
<!-- View displays 4 (desired) -->
<local:MyControl MyProperty="4" />
<!-- View displays default of 42 (desired) -->
<local:MyControl />
<!-- View displays default of 42 (wanted 4) -->
<DataTemplate x:Key="MyTemplate">
<local:MyControl MyProperty="4"/>
</DataTemplate>
To summarize:
The desired behavior is that the value from XAML is used first. If the value is not specified in the XAML, then I would like to fallback to the default value set in the control's constructor.
If I just include the control directly in a view, I get the expected behavior. If the control is used inside a DataTemplate, then I always get the default set in the constructor (even when the data template explicitly sets another value).
Is there any other way to specify the default value when the control is used in a template? The only option I can think of is to break the control up into several separate but similar controls, each of which uses a default value that is registered with the dependency property (which removes the need to have the default set based on the a Mode property).
Setting the default value in OnApplyTemplate while adding a small check should solve this:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Only set the default value if no value is set.
if (MyProperty == (int)MyPropertyProperty.DefaultMetadata.DefaultValue)
{
this.MyProperty = GetDefaultPropertyValue();
}
}
Please note that although this will work, it's not ideal since setting the property's value by code will essentially clear any data bindings for this property. For example, the following binding will no longer work once you call MyProperty = 42 in code:
<local:MyControl MyProperty="{Binding SomeProperty}" />
It should be possible to set the value while maintaining any bindings by using SetCurrentValue(MyPropertyProperty, GetDefaultPropertyValue()); to modify the property instead of MyProperty = GetDefaultPropertyValue(), but I'm not sure I like that too much either.
A better solution
What I would do is introduce a new read-only property in addition to the existing one, which will act as a calculated property. For example:
private static readonly DependencyPropertyKey MyCalculatedPropertyPropertyKey =
DependencyProperty.RegisterReadOnly("MyCalculatedProperty", typeof(int), typeof(MyControl),
new PropertyMetadata(int.MinValue));
public static readonly DependencyProperty MyCalculatedPropertyProperty = MyCalculatedPropertyPropertyKey.DependencyProperty;
public int MyCalculatedProperty
{
get { return (int)GetValue(MyCalculatedPropertyProperty); }
private set { SetValue(MyCalculatedPropertyPropertyKey, value); }
}
private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyControl)d).MyCalculatedProperty = (int)e.NewValue;
}
public MyControl()
: base()
{
InitializeComponent();
MyCalculatedProperty = GetDefaultPropertyValue();
}

Global Scope Attached Dependency Property

I would like to do something like a static variable in normal programming, only in XAML using Dependency Properties.
Meaning I would like the property to :
Be one instance
Be visible by every element
Be bindable
How do I do that ?
It sounds like you want an attached property that always applies to every element. I think the easiest way to make that work would be through the CoerceValueCallback of the dependency property, where you could force it to always return the static value regardless of the element's local value (you would update the static value in the PropertyChangedCallback).
This seems like an odd way to use the dependency property system, though. Maybe you just need a central binding source? You can bind to a static instance by assigning Binding.Source using x:Static:
{Binding Source={x:Static Member=global:GlobalObject.SharedInstance},
Path=SharedValue}
Note that SharedValue isn't a static property; it's a property of an instance accessed from the static SharedInstance property:
public class GlobalObject {
private static readonly GlobalObject _instance = new GlobalObject();
public static GlobalObject SharedInstance { get { return _instance; } }
public object SharedValue { get; set; }
}
Easy.
Create an attached DependencyProperty on the DependencyObject Type.
public static readonly DependencyProperty DerpProperty =
DependencyProperty.RegisterAttached(
"Derp",
typeof(DependencyObject),
typeof(Herp),
new FrameworkPropertyMetadata());
public static void SetDerp(DependencyObject element, Herp value)
{
element.SetValue(DerpProperty, value);
}
public static Herp GetDerp(DependencyObject element)
{
return (Herp)element.GetValue(DerpProperty);
}
Defined on any type, it can be used on any type as well. In this example, it creates a new property called Derp on all DependencyObject instances that gets/sets an associated Herp value.
Assuming this is defined in a type called LolKThx in the namespace WpfFtw, you might use it in this way...
<Textblock
xmlns:lol="clr-namespace:WpfFtw"
lol:LolKThx.Derp="There's an implicit conversion for string -> Herp, btw" />
You can specify callbacks in your FrameworkPropertyMetadata to perform any action you need on setting/getting values.

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