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).
Related
I am going over the differences between regular dependency properties and attached properties.
Using ILSpy, I had a look at how Register and RegisterAttached are implemented.
Register:
// System.Windows.DependencyProperty
/// <summary>Registers a dependency property with the specified property name, property type, owner type, property metadata, and a value validation callback for the property. </summary>
/// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
/// <param name="name">The name of the dependency property to register.</param>
/// <param name="propertyType">The type of the property.</param>
/// <param name="ownerType">The owner type that is registering the dependency property.</param>
/// <param name="typeMetadata">Property metadata for the dependency property.</param>
/// <param name="validateValueCallback">A reference to a callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
{
DependencyProperty.RegisterParameterValidation(name, propertyType, ownerType);
PropertyMetadata defaultMetadata = null;
if (typeMetadata != null && typeMetadata.DefaultValueWasSet())
{
defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue);
}
DependencyProperty dependencyProperty = DependencyProperty.RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
if (typeMetadata != null)
{
dependencyProperty.OverrideMetadata(ownerType, typeMetadata);
}
return dependencyProperty;
}
RegisterAttached:
// System.Windows.DependencyProperty
/// <summary>Registers an attached property with the specified property type, owner type, property metadata, and value validation callback for the property. </summary>
/// <returns>A dependency property identifier that should be used to set the value of a public static readonly field in your class. That identifier is then used to reference the dependency property later, for operations such as setting its value programmatically or obtaining metadata.</returns>
/// <param name="name">The name of the dependency property to register.</param>
/// <param name="propertyType">The type of the property.</param>
/// <param name="ownerType">The owner type that is registering the dependency property.</param>
/// <param name="defaultMetadata">Property metadata for the dependency property. This can include the default value as well as other characteristics.</param>
/// <param name="validateValueCallback">A reference to a callback that should perform any custom validation of the dependency property value beyond typical type validation.</param>
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
DependencyProperty.RegisterParameterValidation(name, propertyType, ownerType);
return DependencyProperty.RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
}
It looks like the only difference is how the PropertyMetaData is handled. Register has some extra logic using the passed in typeMetaData parameter while RegisterAttached just passes the PropertyMetaData along. Both methods then call RegisterCommon.
When it comes to implementing attached properties, is this the only difference? I looked at the Set/GetValue methods on DependencyObject. They are quite complicated (so I very easily could have missed some key bit of code that would explain things) but I couldn't find anything that would cause different code paths to be taken depending on what Register or RegisterAttached did.
Question:
Can someone explain to me what the difference I described above between Register and RegisterAttached is actually doing and how it allows attached properties to be used as a type of global property that is settable on any object? Maybe the place where the actual work is done is in the Set/GetValue methods on DependencyObject?
TLDR: the differences you see in your code example are indeed the only differences between "attached" and regular dependency properties, and after that code is run they are treated the same by WPF, there is no such concept as "attached" property internally at all (like some flag DependencyProperty.IsAttached or anything like that).
You are right that the difference between "attached" and regular dependency properties is just metadata handling.
First, it's perfectly possible to use regular property as attached one. For example suppose we have following property (note it's not "attached" in a sense RegisterAttached is not used):
public class TestProperties {
public static readonly DependencyProperty TestProperty = DependencyProperty.Register(
"Test",
typeof(Boolean),
typeof(TestProperties)
);
public static void SetTest(UIElement element, Boolean value) {
element.SetValue(TestProperty, value);
}
public static Boolean GetTest(UIElement element) {
return (Boolean) element.GetValue(TestProperty);
}
}
And suppose we have TextBlock like this:
<TextBlock x:Name="tb" local:TestProperties.Test="True" />
Because we have correct GetTest and SetTest methods - it's valid syntax. Now we can get the value of our properties as usual for attached properties:
// this returns true
var value = tb.GetValue(TestProperties.TestProperty);
Note that those GetTest and SetTest are not required either. They are needed only to be able to get\set "attached" property value from xaml. So far as you see regular and "attached" properties are exactly the same, there is no difference whatoever.
When we start to pass metadata - there are some differences though. When you register attached property, metadata you pass will be the default one and will apply to all types.
public class TestProperties {
public static readonly DependencyProperty TestProperty = DependencyProperty.RegisterAttached(
"Test",
typeof(Boolean),
typeof(TestProperties),
new PropertyMetadata(true, OnTestChanged)
);
private static void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
}
public static void SetTest(UIElement element, Boolean value) {
element.SetValue(TestProperty, value);
}
public static Boolean GetTest(UIElement element) {
return (Boolean) element.GetValue(TestProperty);
}
}
If we now do:
var meta = TestProperties.TestProperty.GetMetadata(typeof(TextBlock));
// or any other type
We will see that metadata for any type is the default one you passed, with callback and other stuff. What that means, for example, is your value changed callback will be called whenever value of your attached property changes for any type (like TextBlock in our example above).
When we register property with metadata with regular Register - it behaves differently:
PropertyMetadata defaultMetadata = null;
if (typeMetadata != null && typeMetadata.DefaultValueWasSet())
{
defaultMetadata = new PropertyMetadata(typeMetadata.DefaultValue);
}
DependencyProperty dependencyProperty = DependencyProperty.RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
First it copies the default value (if any) and creates new metadata with only the default value. Then it creates property with that metadata but only default value is part of default metadata now. All callbacks are not part of default metadata. What that means is that now your callback will not be called when value of dependency property changes on arbitrary type (though default value is still preserved).
if (typeMetadata != null)
{
dependencyProperty.OverrideMetadata(ownerType, typeMetadata);
}
Now it takes full metadata you passed (with callbacks) and registers it only for the type you passed. In our example that means callbacks will be called only when value changes for type TestProperties itself (which must inherit DependencyObject from now on:
public class TestProperties : DependencyObject { // must inherit dependency object
public static readonly DependencyProperty TestProperty = DependencyProperty.Register(
"Test",
typeof(Boolean),
typeof(TestProperties),
new PropertyMetadata(true, OnTestChanged)
);
private static void OnTestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
// this will be called ONLY when we do something like
// var prop = new TestProperties();
// prop.SetValue(TestProperty, true);
// but will NOT be called when we set value for TextBlock
}
public static void SetTest(UIElement element, Boolean value) {
element.SetValue(TestProperty, value);
}
public static Boolean GetTest(UIElement element) {
return (Boolean) element.GetValue(TestProperty);
}
}
If we want callback to be called for TextBlock - we can add that manually:
static TestProperties() {
TestProperties.TestProperty.OverrideMetadata(typeof(TextBlock), new PropertyMetadata(OnTestChanged));
}
But we have lost the (very useful) ability of "attached" properties to be able to track changes to values defined on any types (TextBlock, Button, whatever).
I hope this answers your question.
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();
}
TL;DR - I'm having a problem passing system types as a value to Type parameters in Silverlight. Is this some kind of known problem? Is it possible at all?
In details:
In my control I have dependency property of type Type. And there is a problem with passing types from System namespace, like int(Int32), string(String), Guid, decimal(Decimal), bool(Boolean). In these cases the depencency property receives null value (depencency property default value is set to some non-null value, so I see in OnPropertyChanged event that null is passed). For other types it works without problems.
Here is the code of my dependency property:
public static readonly DependencyProperty SomeTypeProperty = DependencyProperty.Register(
"SomeType", typeof(Type), typeof(Control1), new PropertyMetadata(typeof(EmptyType), OnSomeTypePropertyChanged));
public Type SomeType
{
get { return (Type)GetValue(SomeTypeProperty); }
set { SetValue(SomeTypeProperty, value); }
}
And the control's usage:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
[...]
<sl1:Control1 SomeType="sys:Boolean" />
What's interesting - it works in XAML designer in Visual Studio. I know this by displaying the property value, so the passed Type, in my Control1 control's content. But in Silverlight runtime environment it doesn't work.
I don't know why this is happening... However, here's a workaround..
Create the following class:
public class TypeOfRes
{
public object Object { get; set; }
public Type TypeOf
{
get { return Object == null ? null : Object.GetType(); }
}
}
Create the following resource in your page:
<local:TypeOfRes x:Key="booleanRes">
<local:TypeOfRes.Object>
<sys:Boolean>True</sys:Boolean>
</local:TypeOfRes.Object>
</local:TypeOfRes>
Reference the resource in your property:
<local:SilverlightControl1 MyType="{Binding Source={StaticResource booleanRes},Path=TypeOf}"/>
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.
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.