Subscription to RenderTransform.Changed will be lo

2019-09-11 00:02发布

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?

2条回答
▲ chillily
2楼-- · 2019-09-11 00:41

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
    }
查看更多
Anthone
3楼-- · 2019-09-11 00:55

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.

查看更多
登录 后发表回答