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.
Related
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.
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.
I have the following declaration:
public static readonly DependencyProperty PassColorProperty = DependencyProperty.RegisterAttached("PassColor",
typeof(string),
typeof(ColorMasking),
new PropertyMetadata("#FFCCFF"));
public string PassColor
{
get { return (string)GetValue(PassColorProperty); }
set { SetValue(PassColorProperty, value); }
}
At the moment this code does not compile because I haven't added : DependencyProperty to my class. When I add that code it says that the string PassColor is invalid.
Without the string there at all, the code compiles and I can set read the property from within that class. I cannot set it from my XAML though. It says the property doesn't exist. My xaml is:
<TextBox Grid.Column="1" Grid.Row="8" Margin="3" Width="Auto" Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
b:ColorMasking.Mask=" ... Long Regex Command ... "
b:ColorMasking.PassColor="99FF99" />
The code for setting the Mask works perfectly. I think I have copied all the required stuff too. It is confusing as to why I cannot add another property.
If it matters, this is a variation I've written of this code: How to define TextBox input restrictions?
EDIT:
public class ColorMasking : DependencyObject
{
private static readonly DependencyPropertyKey _maskExpressionPropertyKey = DependencyProperty.RegisterAttachedReadOnly("MaskExpression",
typeof(Regex),
typeof(ColorMasking),
new FrameworkPropertyMetadata());
/// <summary>
/// Identifies the <see cref="Mask"/> dependency property.
/// </summary>
///
public static readonly DependencyProperty PassColorProperty = DependencyProperty.Register("PassColor",
typeof(string),
typeof(ColorMasking),
new PropertyMetadata("#99FF99"));
public static readonly DependencyProperty FailColorProperty = DependencyProperty.Register("FailColor",
typeof(string),
typeof(ColorMasking),
new PropertyMetadata("#FFCCFF"));
public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
typeof(string),
typeof(ColorMasking),
new FrameworkPropertyMetadata(OnMaskChanged));
The code you have posted shows that you are registering an AttachedProperty so the PassColorProperty is not a DependencyPropery of your ColorMasking class. It must be accessed through an object that has that attached property set on it. The attached property will allow you to set that property on other objects and not just
public static void SetPassColor(DependencyObject obj, string passColor)
{
obj.SetValue(PassColorProperty, passColor);
}
public static string GetPassColor(DependencyObject obj)
{
return (string)obj.GetValue(PassColorProperty);
}
This except from MSDN explains the accessors for an attached property:
The Get Accessor
The signature for the GetPropertyName accessor must be:
public static object Get PropertyName (object target )
-The target object can be specified as a more specific type in your
implementation. For example, the DockPanel.GetDock method types the
parameter as UIElement, because the attached property is only intended
to be set on UIElement instances.
-The return value can be specified as a more specific type in your
implementation. For example, the GetDock method types it as Dock,
because the value can only be set to that enumeration.
The Set Accessor
The signature for the SetPropertyName accessor must be:
public static void Set PropertyName (object target , object value )
-The target object can be specified as a more specific type in your
implementation. For example, the SetDock method types it as UIElement,
because the attached property is only intended to be set on UIElement
instances.
-The value object can be specified as a more specific type in your
implementation. For example, the SetDock method types it as Dock,
because the value can only be set to that enumeration. Remember that
the value for this method is the input coming from the XAML loader
when it encounters your attached property in an attached property
usage in markup. That input is the value specified as a XAML attribute
value in markup. Therefore there must be type conversion, value
serializer, or markup extension support for the type you use, such
that the appropriate type can be created from the attribute value
(which is ultimately just a string).
I'm creating Behavior with attached properties. Behavior should attach to Grid:
public class InteractionsBehavior : Behavior<Grid>
{
public static readonly DependencyProperty ContainerProperty =
DependencyProperty.RegisterAttached("Container", typeof(Grid), typeof(Grid), new PropertyMetadata(null));
public static readonly DependencyProperty InteractionsProviderProperty =
DependencyProperty.RegisterAttached("InteractionsProvider", typeof(IInteractionsProvider), typeof(Grid), new PropertyMetadata(null, OnInteractionsProviderPropertyChanged));
public Grid Container
{
get { return GetValue(ContainerProperty) as Grid; }
set { this.SetValue(ContainerProperty, value); }
}
public IInteractionsProvider InteractionsProvider
{
get { return GetValue(InteractionsProviderProperty) as IInteractionsProvider; }
set { this.SetValue(InteractionsProviderProperty, value); }
}
Now when I'm writing XAML like this I get error:
<Grid Background="White" x:Name="LayoutRoot"
Behaviors:InteractionsBehavior.InteractionsProvider="{Binding InteractionsProvider}">
Error 4 The property 'InteractionsProvider' does not exist on the type
'Grid' in the XML namespace
'clr-namespace:Infrastructure.Behaviors;assembly=Infrastructure.SL'. C:\MainPage.xaml 11 11 Controls.SL.Test
Error 1 The attachable property 'InteractionsProvider' was not found
in type
'InteractionsBehavior'. C:\MainPage.xaml 11 11 Controls.SL.Test
You specified that it only should be available to be attached ("owned") by an InteractionsBehavior. If you want to be able to assign this to a grid, change the RegisterAttached line to:
public static readonly DependencyProperty InteractionsProviderProperty =
DependencyProperty.RegisterAttached("InteractionsProvider", typeof(IInteractionsProvider), typeof(Grid), new PropertyMetadata(null, OnInteractionsProviderPropertyChanged));
(Or use some base class in Grid's class hierarchy...)
The problem is in the declaration of your attached property. Attached properties have 4 parts: the name, the type, the owner type, and the property metadata. You're specifying that the InteractionsProvider property is owned (and thus supplied) by the type Grid. That's not actually the case. Change the owner type (third parameter) to typeof(InteractionsBehavior) (the class in which you've declared the attached property), switch to static get/set methods instead of a property (because you're using an attached property, not a dependency property), and it should all work as you expect.
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.