WPF 後臺程式碼做 TranslateTransform 的動畫

lindexi 發表於 2021-06-17

本文告訴大家,在後臺程式碼,對 TranslateTransform 做動畫的方法

今天小夥伴問我一個問題,說為什麼相同的程式碼,如果設定到按鈕上,是可以讓按鈕的某個屬性變更,但是如果設定給 TranslateTransform 的 X 或 Y 就不會有任何值變更

在 WPF 中,通過 官方文件 裡面的描述,對於 Freezable 型別的物件,如 SolidColorBrush 和 RotateTransform 和 GradientStop 等型別,都是不支援直接的動畫,也就是如以下程式碼是不能觸發動畫

假定有 XAML 介面如下,期望在點選按鈕時,修改按鈕的 TranslateTransform 做動畫

  <Grid>
    <Button x:Name="Button" HorizontalAlignment="Center" VerticalAlignment="Center" Content="按鈕" Click="Button_OnClick">
      <Button.RenderTransform>
        <TranslateTransform x:Name="ButtonTranslateTransform"></TranslateTransform>
      </Button.RenderTransform>
    </Button>
  </Grid>

如果直接對使用 Storyboard 的 SetTarget 方法給物件設定 DoubleAnimation 將會是無效的,也就是說如以下的程式碼做的 TranslateTransform 動畫是無效的,沒有反應的

        private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            var storyboard = new Storyboard();

            var doubleAnimation = new DoubleAnimation();
            Storyboard.SetTarget(doubleAnimation, ButtonTranslateTransform);
            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath(TranslateTransform.XProperty));

            doubleAnimation.To = 100;
            doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));

            storyboard.Children.Add(doubleAnimation);
            storyboard.Begin();
        }

如果想要給 Freezable 型別的物件做動畫,可以通過間接的方法,也就是通過 Freezable 型別的物件所在的元素,使用點的方式寫出來具體的程式碼

        private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            var storyboard = new Storyboard();

            var doubleAnimation = new DoubleAnimation();
            Storyboard.SetTarget(doubleAnimation, Button);
            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));

            doubleAnimation.To = 100;
            doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));

            storyboard.Children.Add(doubleAnimation);
            storyboard.Begin();
        }

寫法就是通過某個元素的某個屬性加上某個型別的某個屬性。如上面程式碼使用的是 UIElement 的 RenderTransform 屬性,這個屬性的值的型別是 TranslateTransform 型別,設定這個型別的 X 屬性

上面的 PropertyPath 有可以換成如下方式寫

            var propertyChain = new object[]
            {
                UIElement.RenderTransformProperty,
                TranslateTransform.XProperty
            };

            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(0).(1)", propertyChain));

我更推薦使用這個寫法,因為這樣就不會寫錯命名

而如果只是為了修改 TranslateTransform 的 X 屬性,最簡單的寫法就是通過 BeginAnimation 的方式,如下面程式碼

        private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            ButtonTranslateTransform.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation()
            {
                To = 100,
                Duration = new Duration(TimeSpan.FromSeconds(1))
            });
        }

以上程式碼可以看到很清真

這裡的 Duration 其實可以通過 TimeSpan 轉換,而不需要建立 Duration 物件。然而在 WPF 依然定義 Duration 類的原因是為了支援 Duration.Automatic 和 Duration.Forever 特殊的定義

如果是需要有多個屬性開始做動畫,不想使用 BeginAnimation 的方式,可以通過在後臺程式碼用 SetTargetName 的方法指定,如下面程式碼

        private void Button_OnClick(object sender, RoutedEventArgs e)
        {
            var storyboard = new Storyboard();

            var doubleAnimation = new DoubleAnimation();
            Storyboard.SetTargetName(doubleAnimation, nameof(ButtonTranslateTransform));
            Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath(TranslateTransform.XProperty));

            doubleAnimation.To = 100;
            doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));

            var storyboardName = "s" + storyboard.GetHashCode();
            // 加入到字典,讓 Storyboard 和 ButtonTranslateTransform 在相同的一個 NameScope 裡
            Resources.Add(storyboardName, storyboard);

            storyboard.Children.Add(doubleAnimation);
            storyboard.Begin();
        }

在後臺程式碼做動畫,如果使用 SetTargetName 就需要讓 Storyboard 和對應的元素在相同的一個 NameScope 裡,不然將會提示 System.InvalidOperationException 不存在可解析名稱“xx”的適用名稱領域,如下面程式碼

System.InvalidOperationException:“不存在可解析名稱“ButtonTranslateTransform”的適用名稱領域。”

上面程式碼通過將動畫加入到資源字典的方式,讓動畫和元素在相同的 NameScope 而讓動畫能找到元素。但是上面程式碼將會在資源字典加入一個 Storyboard 而沒有釋放,如果在你的實際程式碼,我推薦在動畫完成之後,刪除資源字典的動畫

我特別翻了 WPF 程式設計寶典,發現寶典裡面沒有這部分知識,也就是沒有告訴大家為什麼直接給 TranslateTransform 的屬性做動畫將會失效。好在官方文件裡面有說到這點

本文程式碼還請到 githubgitee 上閱讀程式碼

可以通過如下方式獲取本文的原始碼,先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 78f63c1c076065d1891559f5af2cb29f10a39f8b

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

獲取程式碼之後,進入 KayceefiwhearHaijanihukere 資料夾

Storyboards Overview - WPF .NET Framework