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 属性的实际值,您需要订阅它的所有者(通常是 WindowControl)以获得 "ValueChanged" 事件,并在每次发生该事件时注册 RenderTransform.Changed 处理程序。因为 RenderTransform 是一个 "shortcut property" 对应一个叫做 RenderTransformPropertyDependencyProperty,你需要这样做:

//inside the constructor
{
    DependencyPropertyDescriptor
        .FromProperty(RenderTransformProperty)
        .AddValueChanged(this, new EventHandler(RenderTransformPropertyChanged));
}

private void RenderTransformPropertyChanged(object sender, EventArgs e)
{
    //this.RenderTransform.Changed += ...
}

其中 DependencyPropertyDescriptorSystem.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
    }