问题
In my user control I want to get aware of rotations & co. made in the WPF designer. So in my user control I did:
private void Test_Loaded(object sender, RoutedEventArgs e)
{
this.RenderTransform.Changed += this.RenderTransform_Changed;
}
private void RenderTransform_Changed(object sender, EventArgs e)
{
// do anything
}
It seems this.RenderTransform
is never null. However unless my user control has the exact XAML structure like the designer makes when i.e. rotating my control, it will fail.
Example: When I open a XAML file with the following content:
<my:Test>
<my:Test.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</my:Test.RenderTransform>
</my:Test>
the RenderTransform_Changed
will be called when I rotate my control.
But when the XAML is:
<my:Test>
<my:Test.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<TranslateTransform/>
</TransformGroup>
</my:Test.RenderTransform>
</my:Test>
or
<my:Test>
<my:Test.RenderTransform>
<RotateTransform/>
</my:Test.RenderTransform>
</my:Test>
or
<my:Test/>
it won't call RenderTransform_Changed
when rotating my control.
I assume, this happens, because the designer is recreating this.RotateTransform
when it does not follow the exact same structure as the designer wants. Thus, the subscription will be lost, entirely.
To solve the problem I tried to provide the same structure to this.RenderTransform
in the Loaded
event before subscribing to the Changed
event:
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
}
With that I tried to be ready for any given structure of RenderTransform
on initial time. But it seems the designer does not care what's actually in the RenderTransform
object, it simply rewrites it, because the XAML still does not fulfill the structure it wants.
What can I do to get rid of this problem?
回答1:
Unless I'm wrong in my diagnosis of the problem, here's an answer:
The problem
Whenever you register a handler with this.RenderTransform.Changed += ...
you subscribe to an object which is the value of this.RenderTransform
. When the property is then changed, it no longer holds the object to which you subscribed (and that object is no longer in effect), yet you're still subscribed to that exact object, and not the new value of the property.
The solution
In order to keep track of the actual value of the RenderTransform
proprety you need to subscribe to it's owner (usually a Window
or a Control
) for a "ValueChanged" event, and register RenderTransform.Changed
handler upon each occurrence of that event. Since RenderTransform
is a "shortcut property" for a DependencyProperty
called RenderTransformProperty
, you need to do it like this:
//inside the constructor
{
DependencyPropertyDescriptor
.FromProperty(RenderTransformProperty)
.AddValueChanged(this, new EventHandler(RenderTransformPropertyChanged));
}
private void RenderTransformPropertyChanged(object sender, EventArgs e)
{
//this.RenderTransform.Changed += ...
}
where DependencyPropertyDescriptor
is in System.ComponendModel
namespace.
回答2:
Grx70's solution gave me the right direction, however I was overwriting the PropertyMetadata
to get rid of the reset of RenderTransform
.
So I did
Control.RenderTransformProperty.OverrideMetadata(typeof(Test), new PropertyMetadata(
Control.RenderTransformProperty.GetMetadata(typeof(Test)).DefaultValue
, new PropertyChangedCallback(Test.RenderTransform_Changed)
)
);
in the static constructor of my control. So then I could do
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
}
来源:https://stackoverflow.com/questions/29604082/subscription-to-rendertransform-changed-will-be-lost-when-it-does-not-fulfill-th