Silverlight中附加和非附加依赖属性的区别

13

好的,Stackers们,我花了几个小时在这个问题上,我想知道是否有人有一个确定的答案。
在我做的所有研究中,我找不到任何关于 Silverlight.Register.RegisterAttached 的区别。现在,在你们匆忙告诉我.RegisterAttached用于将 DP 附加到另一个类之前,请尝试使用 DependencyProperty.Register() 实现一个 Attached Dependency Property。我没有发现任何区别,所以我对差异感到困惑。
此外,在我的特定情况下,我正在尝试扩展 Grid 类的功能,并希望给它一些额外的属性。因此,我尝试将 typeof(Grid)typeof(FluidLayoutManager)(实现类)都作为 ownerType 参数传递,但看起来并没有什么区别......(我相信当我传递同一命名空间中的两个自定义类时会有所不同。但是当传递一个 Microsoft 定义的类和一个自定义类时,它总是显示为自定义类的 DP)。
如果有关此主题的任何澄清,将不胜感激,因为我正坐在这里想啊想啊,不知道是否有任何区别,还是微软再次在跟我开玩笑。

5个回答

10

考虑到评论中的讨论,我将尝试用简单易懂的英语来表述:

附加属性和依赖属性(因此也就是 .Register 和 .RegisterAttached 之间)的主要区别在于,RegisterAttached 允许将值分配给任何依赖对象,而 Register 只允许将其附加到传递作为 ownerType 参数的类。

正如Haris Hasan所提到的(在评论线程深处),您的示例正在使用唯一允许的类型(即 CustomControl),并没有向您展示附加版本可以分配给任何依赖对象的情况。

例如,您可以使用附加依赖属性来执行此操作(但不能使用普通 DP):

<Grid local:AttacherClass.ADP1="1" x:Name="LayoutRoot" Background="White">
</Grid>

我能找到的最好的ADP参考资料是这个:http://msdn.microsoft.com/en-us/library/ms749011.aspx 我们将ADPs用作本地化系统的基础,因此可以在加载期间将翻译附加到对象上,而不必使用可怕的长绑定。无法使用DPs进行此操作。
更新:
我还想澄清一个问题,父级限制仅适用于基于XAML的属性使用。从代码中,父级限制显然不适用。

2
更多XAML信息1:XAML使用SetDP/ADP()GetDP/ADP()函数,但调用SetValue(DP/ADP, value)GetValue(DP/ADP)则不会。 2:在XAML设计器中,Intellisense将使用SetDP/ADP()GetDP/ADP()方法签名来确定是否显示依赖属性或附加依赖属性。因此,如果SetDP/ADP()参数接受DependencyObject而不是更具体的类,则会显示无效的依赖属性。 - Melodatron
1
+1 对那个评论的赞同...现在我只需要把它翻译成清晰的英语,我们就完成了 :) - iCollect.it Ltd
那么我接下来的问题是,这两者之间是否还有其他的区别,而且在Silverlight中传递到.RegisterAttachedownerType参数实际上是否有任何意义? - Melodatron
@Melodatron:好问题。我似乎记得附加属性与特定类型的父级相关联(并且被称为Owner Type),但我不知道它实际上如何用于限制。它看起来更像是在暗示关系而不是强制执行关系。 - iCollect.it Ltd

8
错误地认为"RegisterAttached允许将值赋予任何依赖对象,而Register仅允许将其附加到传递作为ownerType参数的类"。这是一个完美运行的附加属性注册示例,使用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展示了Register和RegisterAttached之间唯一的区别是,Register会过滤掉很多提供的元数据并且只保留它用于注册类的实例(通过OverrideMetadata)。这意味着在使用Register注册和附加到其他类型对象(不同于注册类型)的属性上通常指定的像Inherits和各种更新通知等元数据属性将不起作用。因此,Register实际上是RegisterAttached的简化版本。这样做可能是为了提高性能。

在Haris Hasan回答评论中链接的示例中,如果你将RegisterAttached更改为Register,则按钮将停止移动(因为该属性不再为Button类型提供AffectsParentArrange元数据),但当你调整窗口大小时,它们仍然以新位置重新绘制。但如果在InitializeComponent()方法调用后为Button类型添加相同的元数据:

RadialPanel.AngleProperty.OverrideMetadata(
    typeof(Button), 
    new FrameworkPropertyMetadata(
        0.0, FrameworkPropertyMetadataOptions.AffectsParentArrange));

那么一切都恢复正常,就像调用了RegisterAttached一样。


只是为了澄清,我提到的父级限制适用于在XAML中使用属性,而不是在代码中。 - iCollect.it Ltd

5
它们在实现上可能没有太大的区别,但它们在行为上有所不同,即它们在做什么和用途上不同。
简单的 Register 用于简单的依赖属性,通常用于绑定和验证,因此它们是带有一些额外魔法的普通 CLR 属性,有助于 WPF。 RegisterAttached 通常用于要公开可以在子类中访问和设置的属性,例如 DockPanel,其中控件的子项使用 Dock.LeftDock.Right 告诉父项它们想要放置在哪里。因此,它们是一种特殊的依赖属性,可以在子控件中访问(这在简单的 Register 属性中不是这样的),并且它们(在 DockPanel 的情况下)有助于父控件显示子项。
简而言之,可以说 Register 用于注册在同一类中使用的 dependency properties,而 RegisterAttached 用于注册名为 attached properties 的特殊依赖属性,并且它们被其他定义它的类所使用和访问。 这里 是关于 Attached Properties 的很好的解释,以及通过简单 DP 无法实现的内容。

@Haris:请考虑我对问题所做的编辑。我理解“Register/RegisterAttached通常用于...”,但这并不影响实现或功能,这是我想要回答的问题。此外,正如您在示例中看到的那样,从另一个类访问“Registered”DP绝对没有问题。 - Melodatron
1
@Melodatron:问题在于您试图在错误的上下文中使用示例中的“附加属性”。您正在使用“附加属性”,即使简单的“依赖属性”也可以工作。正如我所说,“附加属性”是具有附加功能的“依赖属性”,这就是为什么您能够以相同的方式访问它的原因。但是在您的示例中,您没有使用“附加属性”的实际功能。您没有从另一个类中访问它,这是与简单的“依赖属性”不同的主要事项。 - Haris Hasan
1
我现在没有时间做一个例子,但是互联网上有很多这样的例子。例如,请参见http://blogs.msdn.com/b/bencon/archive/2006/07/24/677520.aspx。你不能通过正常的DP来完成它,这就是ADP真正派上用场并展示其力量的地方。 - Haris Hasan
另一个不错且简单的示例:http://www.silverlightshow.net/items/Attached-Properties-in-Silverlight.aspx - Haris Hasan
@Haris:虽然我觉得第一个例子很有趣,但它让我意识到我需要澄清一些仅在标签中提到而不在问题描述中提到的事情。这个问题是专门针对Silverlight框架而不是WPF框架的。除此之外,我将“.RegisterAttached”更改为“.Register”,但它不再正常工作了。主要区别在于按钮的位置在被单击后没有更新,我认为这可能与“this.InternalChildren”和/或“FrameworkPropertyMetadata”有关 - 在SL中两者都不存在! - Melodatron
显示剩余3条评论

2
如果使用RegisterAttached进行注册,它将作为属性成为任何DependencyObject存储中的全局属性。也就是说,您可以在任何Dependency Object上调用SetValue。
如果使用Register进行注册,则在调用Get/Setvalue时会检查该调用是否来自于可以转换为注册类型的对象。
一个表现类似于RegisterAttached的属性例子是Grid.Row和Grid.Column。

好的,我得承认“一个表现为RegisterAttached的属性示例是Grid.Row和Grid.Column”这句话让我有些抓狂,但你说得对。到目前为止,我发现它们之间仅有的区别是,当使用“IncompatibleControl.SetValue()”时,我可以在C#中设置和检索附加依赖值。然而,依赖属性会抛出错误,并且两者在XAML中均无法正常工作。 - Melodatron

1

'ownerType'在RegisterAttached中的作用是什么?这个问题困扰我已经几年了,所以我最近细看了WindowsBase中4.6.1版本的代码。

对于任何DependencyProperty,无论是附加属性还是其他属性,最终的关键在于WPF为后期绑定的XAML访问获取了什么类型的PropertyDescriptor,而且这不会在尝试访问的第一次(基于每种类型/属性配对)之前确定。这种推迟是必要的,因为PropertyDescriptor封装了绑定到特定类型的属性,而附加属性的重点就是避免这种情况。

当XAML访问发生时,Register(...)RegisterAttached(...)的区别已经失去了(运行)时间的意义。正如其他人在本页上指出的那样,“DependencyProperty”本身并没有编码区分附加与非附加属性的区别。每个DP都被认为有资格用于任何用途,只要在运行时可以找到。

例如,下面的.NET代码似乎依赖于在“tOwner”类型上找不到匹配的“实例属性”作为允许附加访问的第一个要求。为了确认这一诊断,它随后检查“tOwner”是否公开了其中一个静态访问方法。这是一个模糊的检查,因为它没有验证方法签名。这些签名只对XAML访问有影响;所有实际运行时目标的附加属性必须是“DependencyObject”,每当可能时WPF通过“DependencyObject.GetValue/SetValue”访问它们。(据报道,VS Designer确实使用静态访问器,你的XAML没有它们将无法编译)
相关的.NET代码是静态函数“DependencyPropertyDescriptor.FromProperty”,以下是我的注释(摘要如下):
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);
}

总结:当调用RegisterAttached创建附加的DependencyProperty时,'ownerType'唯一的作用就是标识一个定义了适当的静态Get/Set访问器的类型。'ownerType'上必须存在至少一个这样的访问器,否则XAML附加访问将不能编译或静默失败。尽管在这种情况下,RegisterAttached不会失败,而是成功返回一个失效的DP。对于仅用于附加使用的DP,'tOwner'不必派生自DependencyObject。您可以使用任何常规的.NET类,静态或非静态,甚至可以是一个结构体!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接