RenderTransform.Changed 的订阅在初始时间不满足设计师的XAML 模式时将丢失
Subscription to RenderTransform.Changed will be lost when it does not fulfill the XAML pattern of the designer on initial time
在我的用户控件中,我想了解旋转和合作。在 WPF 设计器中制作。所以在我的用户控件中我做了:
private void Test_Loaded(object sender, RoutedEventArgs e)
{
this.RenderTransform.Changed += this.RenderTransform_Changed;
}
private void RenderTransform_Changed(object sender, EventArgs e)
{
// do anything
}
似乎 this.RenderTransform
永远不会 null。但是,除非我的用户控件具有设计者在旋转我的控件时所做的确切 XAML 结构,否则它将失败。
示例:
当我打开包含以下内容的 XAML 文件时:
<my:Test>
<my:Test.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</my:Test.RenderTransform>
</my:Test>
旋转控件时将调用 RenderTransform_Changed
。
但是当 XAML 是:
<my:Test>
<my:Test.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<TranslateTransform/>
</TransformGroup>
</my:Test.RenderTransform>
</my:Test>
或
<my:Test>
<my:Test.RenderTransform>
<RotateTransform/>
</my:Test.RenderTransform>
</my:Test>
或
<my:Test/>
旋转我的控件时它不会调用 RenderTransform_Changed
。
我想,发生这种情况是因为设计者正在重新创建 this.RotateTransform
,但它不遵循设计者想要的完全相同的结构。因此,订阅将完全丢失。
为了解决这个问题,我尝试在订阅 Changed
事件之前在 Loaded
事件中提供与 this.RenderTransform
相同的结构:
private void Test_Loaded(object sender, RoutedEventArgs e)
{
ScaleTransform scale = this.RenderTransform is ScaleTransform
? (ScaleTransform)this.RenderTransform
: new ScaleTransform();
SkewTransform skew = this.RenderTransform is SkewTransform
? (SkewTransform)this.RenderTransform
: new SkewTransform();
RotateTransform rotate = this.RenderTransform is RotateTransform
? (RotateTransform)this.RenderTransform
: new RotateTransform();
TranslateTransform translate = this.RenderTransform is TranslateTransform
? (TranslateTransform)this.RenderTransform
: new TranslateTransform();
if (this.RenderTransform is TransformGroup)
{
TransformCollection tc = ((TransformGroup)this.RenderTransform).Children;
foreach (Transform t in tc)
{
if (t is ScaleTransform) scale = (ScaleTransform)t;
if (t is SkewTransform) skew = (SkewTransform)t;
if (t is RotateTransform) rotate = (RotateTransform)t;
if (t is TranslateTransform) translate = (TranslateTransform)t;
}
if (!tc.Any(x => x is ScaleTransform)) tc.Add(scale);
if (!tc.Any(x => x is SkewTransform)) tc.Add(skew);
if (!tc.Any(x => x is RotateTransform)) tc.Add(rotate);
if (!tc.Any(x => x is TranslateTransform)) tc.Add(translate);
}
else
{
this.RenderTransform = new TransformGroup()
{
Children =
{
scale,
skew,
rotate,
translate,
},
};
}
foreach (Transform t in ((TransformGroup)this.RenderTransform).Children)
{
t.Changed += this.RenderTransform_Changed;
}
this.RenderTransform.Changed += this.RenderTransform_Changed;
}
private void RenderTransform_Changed(object sender, EventArgs e)
{
// do anything
}
有了这个,我试图在初始时间为 RenderTransform
的任何给定结构做好准备。但似乎设计者并不关心 RenderTransform
对象中实际上是什么,它只是重写它,因为 XAML 仍然没有实现它想要的结构。
我该怎么做才能摆脱这个问题?
除非我对问题的诊断有误,否则答案如下:
问题
每当您向 this.RenderTransform.Changed += ...
注册处理程序时,您都会订阅一个对象,该对象的值为 this.RenderTransform
。当 属性 被更改时,它不再包含您订阅的对象(并且该对象不再有效),但您仍然订阅了那个确切的对象,而不是新的值属性.
解决方法
为了跟踪 RenderTransform
属性的实际值,您需要订阅它的所有者(通常是 Window
或 Control
)以获得 "ValueChanged" 事件,并在每次发生该事件时注册 RenderTransform.Changed
处理程序。因为 RenderTransform
是一个 "shortcut property" 对应一个叫做 RenderTransformProperty
的 DependencyProperty
,你需要这样做:
//inside the constructor
{
DependencyPropertyDescriptor
.FromProperty(RenderTransformProperty)
.AddValueChanged(this, new EventHandler(RenderTransformPropertyChanged));
}
private void RenderTransformPropertyChanged(object sender, EventArgs e)
{
//this.RenderTransform.Changed += ...
}
其中 DependencyPropertyDescriptor
在 System.ComponendModel
命名空间中。
Grx70 的解决方案给了我正确的方向,但是我覆盖了 PropertyMetadata
以摆脱 RenderTransform
的重置。
我也是
Control.RenderTransformProperty.OverrideMetadata(typeof(Test), new PropertyMetadata(
Control.RenderTransformProperty.GetMetadata(typeof(Test)).DefaultValue
, new PropertyChangedCallback(Test.RenderTransform_Changed)
)
);
在我的控件的静态构造函数中。那么我可以做
protected static void RenderTransform_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Test obj = (Test)d; // my control
Transform x = obj.RenderTransform; // new rendertransform
if (x.IsFrozen == false)
{
x.Changed += obj.RenderTransform_Changed;
}
// do anything
}
在我的用户控件中,我想了解旋转和合作。在 WPF 设计器中制作。所以在我的用户控件中我做了:
private void Test_Loaded(object sender, RoutedEventArgs e)
{
this.RenderTransform.Changed += this.RenderTransform_Changed;
}
private void RenderTransform_Changed(object sender, EventArgs e)
{
// do anything
}
似乎 this.RenderTransform
永远不会 null。但是,除非我的用户控件具有设计者在旋转我的控件时所做的确切 XAML 结构,否则它将失败。
示例: 当我打开包含以下内容的 XAML 文件时:
<my:Test>
<my:Test.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</my:Test.RenderTransform>
</my:Test>
旋转控件时将调用 RenderTransform_Changed
。
但是当 XAML 是:
<my:Test>
<my:Test.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<TranslateTransform/>
</TransformGroup>
</my:Test.RenderTransform>
</my:Test>
或
<my:Test>
<my:Test.RenderTransform>
<RotateTransform/>
</my:Test.RenderTransform>
</my:Test>
或
<my:Test/>
旋转我的控件时它不会调用 RenderTransform_Changed
。
我想,发生这种情况是因为设计者正在重新创建 this.RotateTransform
,但它不遵循设计者想要的完全相同的结构。因此,订阅将完全丢失。
为了解决这个问题,我尝试在订阅 Changed
事件之前在 Loaded
事件中提供与 this.RenderTransform
相同的结构:
private void Test_Loaded(object sender, RoutedEventArgs e)
{
ScaleTransform scale = this.RenderTransform is ScaleTransform
? (ScaleTransform)this.RenderTransform
: new ScaleTransform();
SkewTransform skew = this.RenderTransform is SkewTransform
? (SkewTransform)this.RenderTransform
: new SkewTransform();
RotateTransform rotate = this.RenderTransform is RotateTransform
? (RotateTransform)this.RenderTransform
: new RotateTransform();
TranslateTransform translate = this.RenderTransform is TranslateTransform
? (TranslateTransform)this.RenderTransform
: new TranslateTransform();
if (this.RenderTransform is TransformGroup)
{
TransformCollection tc = ((TransformGroup)this.RenderTransform).Children;
foreach (Transform t in tc)
{
if (t is ScaleTransform) scale = (ScaleTransform)t;
if (t is SkewTransform) skew = (SkewTransform)t;
if (t is RotateTransform) rotate = (RotateTransform)t;
if (t is TranslateTransform) translate = (TranslateTransform)t;
}
if (!tc.Any(x => x is ScaleTransform)) tc.Add(scale);
if (!tc.Any(x => x is SkewTransform)) tc.Add(skew);
if (!tc.Any(x => x is RotateTransform)) tc.Add(rotate);
if (!tc.Any(x => x is TranslateTransform)) tc.Add(translate);
}
else
{
this.RenderTransform = new TransformGroup()
{
Children =
{
scale,
skew,
rotate,
translate,
},
};
}
foreach (Transform t in ((TransformGroup)this.RenderTransform).Children)
{
t.Changed += this.RenderTransform_Changed;
}
this.RenderTransform.Changed += this.RenderTransform_Changed;
}
private void RenderTransform_Changed(object sender, EventArgs e)
{
// do anything
}
有了这个,我试图在初始时间为 RenderTransform
的任何给定结构做好准备。但似乎设计者并不关心 RenderTransform
对象中实际上是什么,它只是重写它,因为 XAML 仍然没有实现它想要的结构。
我该怎么做才能摆脱这个问题?
除非我对问题的诊断有误,否则答案如下:
问题
每当您向 this.RenderTransform.Changed += ...
注册处理程序时,您都会订阅一个对象,该对象的值为 this.RenderTransform
。当 属性 被更改时,它不再包含您订阅的对象(并且该对象不再有效),但您仍然订阅了那个确切的对象,而不是新的值属性.
解决方法
为了跟踪 RenderTransform
属性的实际值,您需要订阅它的所有者(通常是 Window
或 Control
)以获得 "ValueChanged" 事件,并在每次发生该事件时注册 RenderTransform.Changed
处理程序。因为 RenderTransform
是一个 "shortcut property" 对应一个叫做 RenderTransformProperty
的 DependencyProperty
,你需要这样做:
//inside the constructor
{
DependencyPropertyDescriptor
.FromProperty(RenderTransformProperty)
.AddValueChanged(this, new EventHandler(RenderTransformPropertyChanged));
}
private void RenderTransformPropertyChanged(object sender, EventArgs e)
{
//this.RenderTransform.Changed += ...
}
其中 DependencyPropertyDescriptor
在 System.ComponendModel
命名空间中。
Grx70 的解决方案给了我正确的方向,但是我覆盖了 PropertyMetadata
以摆脱 RenderTransform
的重置。
我也是
Control.RenderTransformProperty.OverrideMetadata(typeof(Test), new PropertyMetadata(
Control.RenderTransformProperty.GetMetadata(typeof(Test)).DefaultValue
, new PropertyChangedCallback(Test.RenderTransform_Changed)
)
);
在我的控件的静态构造函数中。那么我可以做
protected static void RenderTransform_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Test obj = (Test)d; // my control
Transform x = obj.RenderTransform; // new rendertransform
if (x.IsFrozen == false)
{
x.Changed += obj.RenderTransform_Changed;
}
// do anything
}