我有一个用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()设置起来有点昂贵,不是吗?真的,我想做的就是移动画笔,所以我不想一直重新创建它,我只想更新中心点。
答案 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作为默认值时将再次冻结。由于您正在使用此默认值,因此在修改它时会出现异常。