c# - 刷为 DependencyProperty = 自动冻结?
问题描述
我在 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()。
问题是,当我尝试对 MyCoolBrush 做一些事情(通过 DP)时,我得到一个异常,说它是只读的。如果我尝试直接修改 GetDefaultCoolBrush() 的返回值,它工作正常。
为什么将画笔设置为 DP 会冻结它?这是预期的吗?我想在某种程度上,如果有人将 DP 设置为黑色,例如不能将其更改为绿色是有道理的,为什么不直接传递一个新的画笔呢?但是 GradialRadientBrushes() 设置起来有点昂贵,不是吗?真的,我想做的是移动画笔,所以我不想继续重新创建它,我只想更新中心点。
解决方案
据我了解,这是因为可冻结设计与 DependencyObject 基础架构紧密耦合,出于同样的原因,该基础架构的行为类似于资源系统。
当在 XAML(例如 App.xaml)中定义像画笔、模板或样式这样的 FrameworkElements(或 DependencyObjects)时,它们对于应用程序是静态的,但在实例化之前不是任何可视化树的一部分。为了能够传递它们,它们将被密封(从 Dispatcher 系统中解开它们),这会导致 Freezable 类型冻结。设置默认值(通过 PropertyMetadata)时,这同样适用于 DependencyProperty。此默认值对于应用程序是静态的。因此,底层依赖系统必须密封这些静态值,以便能够在各个实例之间传递它们,以用作默认值。当您在类初始化后设置 DependecyProperty 时(例如,在Loaded
已引发)实际实例值不再被冻结,因为它们与此特定实例耦合。
这是 Freezable.cs 的一个片段。当 DependencyProperty 在可冻结对象上调用 DependencyObject.Seal() 时,将调用 ISealable.Seal() 的覆盖,导致实例冻结:
/// <summary>
/// Seal this freezable
/// </summary>
void ISealable.Seal()
{
Freeze();
}
DependencyProperty.Register() 将调用其内部方法 RegisterCommon(),该方法通过调用调用 ValidateDefaultValueCommon() 的 ValidateMetadataDefaultValue() 来验证默认值。这个方法,在 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 的默认值会导致 ISealable.Seal() 被 DependencyProperty 调用。因此,您的克隆(将 IsFrozen 设置为 false)在分配给 PropertyMetadata 作为默认值时将再次冻结。由于您在此默认值上进行操作,因此在修改它时会出现异常。
推荐阅读
- android - Xamarin.Android 谷歌地图导航模糊
- c# - 硒的 1 行中有 2 个发送键
- python - Python 逻辑不会增加太多时间复杂度
- django - ValueError:字典更新序列元素 #0 的长度为 25;使用字典解包元组时需要 2
- c# - 如何在 dot net 标准应用程序中使用 Dapper 从数据库中读取块中的文件
- iphone - 在 SwiftUI 中,如何使用核心数据将触摸 ID 存储为人员/类的属性并记录它们?
- r - R - 自动排除 quantile_split 函数
- c# - 如何将文件转换为base 64
- typescript - 如何编写抽象的打字稿泛型类型?
- php - “成功”注册后数据未显示在数据库中