刷为DependencyProperty =自动冻结?

时间:2018-12-31 02:50:20

标签: c# wpf

我有一个用Xaml定义的画笔:

<RadialGradientBrush x:Key="MyCoolBrush" MappingMode="Absolute" RadiusX="70" RadiusY="70">
    <RadialGradientBrush.GradientStops>
        <GradientStop Color="#FF000000" Offset="0" />
        <GradientStop Color="#00000000" Offset="0.6" />
    </RadialGradientBrush.GradientStops>
</RadialGradientBrush>

然后我有了一个DependencyProperty:

    public static readonly DependencyProperty MyCoolBrushProperty = DependencyProperty.Register(nameof(MyCoolBrush), typeof(Brush),
        typeof(MyCoolClass), new FrameworkPropertyMetadata(GetDefaultCoolBrush()));

GetDefaultCoolBrush看起来像:

    private static Brush GetDefaultCoolBrush()
    {
        Brush brush = Application.Current.TryFindResource("MyCoolBrush") as Brush;

        if (brush == null)
            return null;

        return brush.Clone();
    }

我可以理解TryFindResource返回了冻结笔刷,因为它是在Xaml中定义的,因此,我正在返回它的Clone()。

问题是,当我尝试(通过DP)对MyCoolBrush进行操作时,出现异常,表明它是只读的。如果我尝试直接修改GetDefaultCoolBrush()的返回值,则可以正常工作。

为什么将画笔设置为DP会冻结?那是预期的吗?我猜在某种程度上,如果有人将DP设置为Black,例如无法将其更改为Green是有意义的,为什么不仅仅传入新的画笔呢?但是GradialRadientBrushes()设置起来有点昂贵,不是吗?真的,我想做的就是移动画笔,所以我不想一直重新创建它,我只想更新中心点。

1 个答案:

答案 0 :(得分:1)

据我了解,这是因为由于相同的原因,可冻结设计与DependencyObject基础结构紧密耦合,而DependencyObject基础结构的行为类似于资源系统。

在XAML中定义诸如Brushes,Templates或Styles之类的FrameworkElements(或DependencyObjects)时,例如App.xaml,它们对于应用程序是静态的,但在实例化之前不是任何视觉树的一部分。为了能够绕过它们,它们将被密封(以将它们从Dispatcher系统中解钩),这将导致Freezable类型冻结。设置默认值时(通过PropertyMetadata),这也适用于DependencyProperty。此默认值对于应用程序是静态的。因此,基础依赖系统必须密封此静态值,以便能够在各个实例之间传递它们,以便用作默认值。当您在类初始化之后(例如,在引发Loaded之后)设置DependecyProperty时,实际实例值不再冻结,因为它们已耦合到该特定实例。

这是Freezable.cs中的摘录。当DependencyProperty在可冻结对象上调用DependencyObject.Seal()时,将调用ISealable.Seal()的覆盖,从而导致实例冻结:

/// <summary>
/// Seal this freezable
/// </summary>
void ISealable.Seal()
{
    Freeze();
 }

DependencyProperty.Register()将调用其内部方法RegisterCommon(),该方法通过调用ValidateMetadataDefaultValue()来验证默认值,该方法调用ValidateDefaultValueCommon()。此方法(在DependencyProperty.cs中定义)最终密封了默认值:

 private static void ValidateDefaultValueCommon(
        object defaultValue,
        Type propertyType,
        string propertyName,
        ValidateValueCallback validateValueCallback,
        bool checkThreadAffinity)
    {
        // Ensure default value is the correct type
        if (!IsValidType(defaultValue, propertyType))
        {
            throw new ArgumentException(SR.Get(SRID.DefaultValuePropertyTypeMismatch, propertyName));
        }

        // An Expression used as default value won't behave as expected since
        //  it doesn't get evaluated.  We explicitly fail it here.
        if (defaultValue is Expression )
        {
            throw new ArgumentException(SR.Get(SRID.DefaultValueMayNotBeExpression));
        }

        if (checkThreadAffinity)
        {
            // If the default value is a DispatcherObject with thread affinity
            // we cannot accept it as a default value. If it implements ISealable
            // we attempt to seal it; if not we throw  an exception. Types not
            // deriving from DispatcherObject are allowed - it is up to the user to
            // make any custom types free-threaded.

            DispatcherObject dispatcherObject = defaultValue as DispatcherObject;

            if (dispatcherObject != null && dispatcherObject.Dispatcher != null)
            {
                // Try to make the DispatcherObject free-threaded if it's an
                // ISealable.

                ISealable valueAsISealable = dispatcherObject as ISealable;

                if (valueAsISealable != null && valueAsISealable.CanSeal)
                {
                    Invariant.Assert (!valueAsISealable.IsSealed,
                           "A Sealed ISealable must not have dispatcher affinity");

                    valueAsISealable.Seal();

                    Invariant.Assert(dispatcherObject.Dispatcher == null,
                        "ISealable.Seal() failed after ISealable.CanSeal returned true");
                }
                else
                {
                    throw new ArgumentException(SR.Get(SRID.DefaultValueMustBeFreeThreaded, propertyName));
                }
            }
        }

在上面的代码中,您找到以下注释:

  

如果默认值为具有线程关联性的DispatcherObject               我们不能接受它作为默认值。如果实现ISealable               我们试图密封它;如果没有,我们抛出异常。类型不               允许从DispatcherObject派生-由用户决定               使任何自定义类型成为自由线程。

摘要:诸如Style,FrameworkTemplate,Brushes或Freezable之类的类型(例如Brush)都实现了ISealable,而Freezable提供的实现则调用了Freeze()。设置DependencyProperty的默认值会导致DependencyProperty调用ISealable.Seal()。 因此,克隆后的克隆(将IsFrozen设置为false)在分配给PropertyMetadata作为默认值时将再次冻结。由于您正在使用此默认值,因此在修改它时会出现异常。