Difference between Attached and non-Attached Dependency Properties in Silverlight
Okay 开发者_开发百科Stackers, I've spent a good couple of hours on this question, and I want to know if anybody has a definitive answer.
For all the research I've done, I can't find ANY difference between.Register
and .RegisterAttached
in Silverlight. Now, before you jump the gun and tell me that .RegisterAttached
is used for attaching a DP to another class, try implementing an Attached Dependency Property using DependencyProperty.Register()
. I have found not a single difference, and so I am at a loss as to what the difference is.
Furthermore, in my specific case, I'm attempting to extend the functionality of the Grid class, and want to give it some extra properties. As such, I've tried listing passing both typeof(Grid)
and typeof(FluidLayoutManager)
(the implementing class) as the ownerType parameter and it also seems to make very little difference... (I believe it does make a difference when I pass two custom classes from the same namespace. However when passing a Microsoft defined class vs. a custom class, I always have it showing up in the XAML as a DP of the custom class.)
Any clarification on this topic would be much appreciated, as I'm sitting here scratching my head, and wondering if there is any difference at all, or if Microsoft is just screwing with me once again.Given the discussions flowing in comments I will try to do this one in plain English:
The main difference between Attached Dependency Properties and Dependency Properties (and therefore between .Register and .RegisterAttached) is that RegisterAttached allows the value to be assigned to any dependency object whereas Register only allows it to be attached to the class passed as the ownerType parameter.
As Haris Hasan mentions (deep in the comment thread), your example is using the only type allowed (i.e. CustomControl) and does not show you that the Attached version can be assigned to ANY dependency object.
e.g. you can do this with your Attached Dependency Property (but not a plain DP):
<Grid local:AttacherClass.ADP1="1" x:Name="LayoutRoot" Background="White">
</Grid>
The best reference for ADPs I can find is this one: http://msdn.microsoft.com/en-us/library/ms749011.aspx
We used ADPs as the basis of a localisation system, so that translations could be parasited onto objects during load rather than using horrendously long bindings. Couldn't do that with DPs
Update:
I would also like to clarify that the parent limitation applies to XAML based use of the attribute. From code the parent limitation apparently does not apply.
It is wrong to believe that "RegisterAttached allows the value to be assigned to any dependency object whereas Register only allows it to be attached to the class passed as the ownerType parameter". Here is a perfectly working example of an attached property registered with Register:
class FooPropertyDeclaringType
{
public static readonly DependencyProperty FooProperty =
DependencyProperty.Register("Foo", typeof(int), typeof(FooPropertyDeclaringType));
}
class SomeUnrelatedType : DependencyObject { }
class Program
{
static void Main()
{
var obj = new SomeUnrelatedType();
obj.SetValue(FooPropertyDeclaringType.FooProperty, 10);
Debug.Assert(10 == (int)obj.GetValue(FooPropertyDeclaringType.FooProperty));
}
}
Reflector shows that the only difference between Register and RegisterAttached is that the Register throws out much of the supplied metadata and only preserves it for the instances of registering class (via OverrideMetadata). It means that attributes such as Inherits and various update notifications usually specified in the metadata do not work on properties registered with Register and attached to objects of other types (other than the registering type). So Register is actually a stripped down version of RegisterAttached. It was probably made this way for performance reasons.
In the example linked by Haris Hasan in the comments to his answer, if you change RegisterAttached to Register, the buttons stop moving (because the property no longer provides AffectsParentArrange metadata for Button type) but they are nevertheless redrawn in their new locations when you resize the window. But if you add the same metadata to the Button type after a call to InitializeComponent():
RadialPanel.AngleProperty.OverrideMetadata(
typeof(Button),
new FrameworkPropertyMetadata(
0.0, FrameworkPropertyMetadataOptions.AffectsParentArrange));
then everything works again as if RegisterAttached was called.
They might not be much different as far as implementation is concerned but they are difference in actions i.e. they are different in what they do and are used for.
Simple Register
is used for simple dependency properties which you usually are used for bindings and validations so they are normal CLR properties with some additional magic which helps in WPF
RegisterAttached
is normally used where you want to expose a property that can be accessed and set in the child class like DockPanel
where children of control tells parent where they want to be placed using Dock.Left
or Dock.Right
. So they are kind of special dependency properties which can be accessed in the child controls (which is not the case with simple Register
properties) and they(in case of DockPanel
) helps parent control in displaying children
In short one cay say Register
is used registering dependency properties
which are used in same class while RegisterAttached
is used for registering special dependency properties called attached properties
and they are used and accessed by classes other than one which defined it
This is a good explanation of Attached Properties and what cannot be achieved through simple DP
If you register with RegisterAttached, it becomes global as a property in the store of any DependencyObject, i.e. you could SetValue on any Dependency Object
If you use Register when Get/Setvalue are called there will be a check that the call is prom an object that is castable to the registering type.
An Example of a property that behaves like RegisterAttached is Grid.Row and Grid.Column.
So exactly what is 'ownerType' used for in RegisterAttached? This issue has been nagging at me for a few years, so I finally took a closer look at 4.6.1 code in WindowsBase.
For any DependencyProperty
, attached or otherwise, what it eventually comes down to is what type of PropertyDescriptor
WPF obtains for late-bound XAML access, and this isn't determined until the first time (on a per type/property pairing basis) such access is attempted. This deferral is necessary because PropertyDescriptor
encapsulates a property bound to a specific type, whereas the point of attached properties is to avoid exactly this.
By the time XAML access occurs, the Register(...)
versus RegisterAttached(...)
distinction has been lost to the mists of (run)time. As others have noted on this page, 'DependencyProperty' itself doesn't encode a distinction between an attached vs. non- variety. Every DP is assumed to be eligible for either usage, subject only to what can be figured out at runtime.
For example, the .NET code below seems to rely on not finding a matching instance property on the 'tOwner' type as the first requirement for allowing attached access. To confirm this diagnosis, it then checks whether 'tOwner' exposes one of the static access methods. This is a vague check insofar as it doesn't verify the method signatures. Those signatures only matter for XAML access; all actual runtime targets for the attached property must be DependencyObject
s, which WPF accesses through DependencyObject.GetValue/SetValue
whenever possible. (The VS Designer reportedly does use the static accessors, and your XAML won't compile without them)
The relevant .NET code is the static function DependencyPropertyDescriptor.FromProperty
, shown here with my own comments (summary below):
internal static DependencyPropertyDescriptor FromProperty(DependencyProperty dp, Type tOwner, Type tTarget, bool _)
{
/// 1. 'tOwner' must define a true CLR property, as obtained via reflection,
/// in order to obtain a normal (i.e. non-attached) DependencyProperty
if (tOwner.GetProperty(dp.Name) != null)
{
DependencyPropertyDescriptor dpd;
var dict = descriptor_cache;
lock (dict)
if (dict.TryGetValue(dp, out dpd))
return dpd;
dpd = new DependencyPropertyDescriptor(null, dp.Name, tTarget, dp, false);
lock (dict)
dict[dp] = dpd;
/// 2. Exiting here means that, if instance properties are defined on tOwner,
/// you will *never* get the attached property descriptor. Furthermore,
/// static Get/Set accessors, if any, will be ignored in favor of those instance
/// accessors, even when calling 'RegisterAttached'
return dpd;
}
/// 3. To obtain an attached DependencyProperty, 'tOwner' must define a public,
/// static 'get' or 'set' accessor (or both).
if ((tOwner.GetMethod("Get" + dp.Name) == null) && (tOwner.GetMethod("Set" + dp.Name) == null))
return null;
/// 4. If we are able to get a descriptor for the attached property, it is a
/// DependencyObjectPropertyDescriptor. This type and DependencyPropertyDescriptor
/// both derive directly from ComponentModel.PropertyDescriptor so they share
/// no 'is-a' relation.
var dopd = DependencyObjectProvider.GetAttachedPropertyDescriptor(dp, tTarget);
/// 5. Note: If the this line returns null, FromProperty isn't called below (new C# syntax)
/// 6. FromProperty() uses the distinction between descriptor types mentioned in (4.)
/// to configure 'IsAttached' on the returned DependencyProperty, so success here is
/// the only way attached property operations can succeed.
return dopd?.FromProperty(dopd);
}
Summary: When calling RegisterAttached
to create an attached DependencyProperty
, the only thing 'ownerType' is used for is to identify a type which defines appropriate static Get/Set accessors. At least one of those accessors must exist on 'ownerType' or XAML attached access will not compile or silently fail. Although RegisterAttached does not fail in this case, and instead successfully returns a defunct DP. For DPs intended for attached use only, 'tOwner' doesn't have to derive from DependencyObject. You can use any regular .NET class, static or non-static, and in fact can even be a struct!
精彩评论