none
StackPanelへコントロール追加時のアニメーション RRS feed

  • 質問

  • 他のパネルにも言えることですが、ここではStackPanelに動的にコントロールを追加するときにアニメーションで追加したいのです。

    例えば下詰めのStackPanelを用意してそこに動的に任意のコントロールを追加すると下に積まれます。

    これを上から投入し、下に落ちて積まれるようなアニメーションを実現したいのですがどのような実現方法があるでしょうか?

    2012年5月1日 12:19

回答

  • 頭の体操みたいになりましたがVisualStateとか使ってできました。仕組みはMarginのTopをマイナスにして初期状態でウィンドウの外側にいるコントロールをStackPanelに追加します。そして、コントロールのLoadedイベントでMarginを0,0,0,0に1秒かけてアニメーションするVisualStateに切り替えてます。

    <Window x:Class="AnimationWpfApplication.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:l="clr-namespace:AnimationWpfApplication"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            Title="MainWindow" Height="371" Width="548">
        <DockPanel>
            <Button DockPanel.Dock="Top" Content="Add" Click="AddButton_Click"/>
            <StackPanel Name="target" VerticalAlignment="Bottom" />
        </DockPanel>
    </Window>
    

    namespace AnimationWpfApplication
    {
        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Media.Animation;
    
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            // 追加するコントロール
            private IEnumerator<FrameworkElement> controls = Controls();
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void AddButton_Click(object sender, RoutedEventArgs e)
            {
                // 追加するコントロールを取得
                this.controls.MoveNext();
                var current = this.controls.Current;
    
                // コントロールのセットアップ(VSMやMargin等)
                SetUpControl(current);
                // StackPanelに追加
                this.target.Children.Add(current);
            }
    
            private void SetUpControl(FrameworkElement current)
            {
                // 上づめ
                current.VerticalAlignment = VerticalAlignment.Top;
                // マージンを調整してウィンドウのちょっと上に強制移動
                current.Margin = new Thickness(0, -this.ActualHeight, 0, 0);
                
                // VisualStateの組み立て
                VisualStateGroup g = new VisualStateGroup();
                // 何もない標準のVisualState
                g.States.Add(new VisualState { Name = "Normal" });
                // 1秒かけてMarginを0,0,0,0にするストーリーボードを持つVisualState
                var added = new VisualState
                {
                    Name = "Added",
                    Storyboard = new Storyboard { Duration = new Duration(TimeSpan.FromSeconds(1)) }
                };
                var animation = new ThicknessAnimation { To = new Thickness(0,0,0,0) };
                Storyboard.SetTargetProperty(animation, new PropertyPath("Margin"));
                added.Storyboard.Children.Add(animation);
    
                // VisualStateをグループに追加
                g.States.Add(added);
    
                // コントロールにVisualStateGroupを追加
                VisualStateManager.GetVisualStateGroups(current).Add(g);
    
                // LoadedイベントでAddedのステートに変更
                current.Loaded += (_, __) => VisualStateManager.GoToElementState(current, "Added", false);
            }
    
            // コントロールを無限に生成するEnumeratorを返すメソッド
            public static IEnumerator<FrameworkElement> Controls()
            {
                while (true)
                {
                    yield return new Button { Content = DateTime.Now.ToString("HH:mm:ss") };
                    yield return new TextBlock { Text = DateTime.Now.ToString("HH:mm:ss") };
                    yield return new CheckBox { Content = DateTime.Now.ToString("HH:mm:ss") };
                }
            }
        }
    }
    


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    • 回答としてマーク 和和和 2012年5月5日 8:47
    2012年5月3日 14:19

すべての返信

  • こんな

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        
        <Button Click="Button_Click" Content="TEST" />
        
        <Grid Grid.Row="1">
            <StackPanel x:Name="stackPanel1"
                        VerticalAlignment="Bottom">
            </StackPanel>
            <Canvas x:Name="canvas1"  ClipToBounds="True"/>
        </Grid>
    </Grid>
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Control ctl;
        switch (stackPanel1.Children.Count % 3)
        {
        default:
            ctl=new Button(){ Content="あ" };
            break;
        case 1:
            ctl = new Label() { Content = "い" };
            break;
        case 2:
            ctl = new CheckBox() { Content = "う" };
            break;
        }
        Point pNext = new Point();
        if (stackPanel1.Children.Count > 0)
        {
            var lastChild = stackPanel1.Children[stackPanel1.Children.Count - 1] as Control;
            pNext = lastChild.TranslatePoint(new System.Windows.Point(0, lastChild.ActualHeight), canvas1);
        }
        else
        {
            pNext = stackPanel1.TranslatePoint(new Point(0, stackPanel1.ActualHeight), canvas1);
        }
        ctl.Width = stackPanel1.Width;
        canvas1.Children.Add(ctl);
        System.Windows.Media.Animation.DoubleAnimation ani
            = new System.Windows.Media.Animation.DoubleAnimation();
        ani.From = 0;
        ani.To = pNext.Y;
        ani.Duration = new Duration(new TimeSpan((long)(50000 * pNext.Y )));
        
        ani.Completed += (sa, ea) =>
            {
                canvas1.Children.Remove(ctl);
                ((System.Windows.Markup.IAddChild)stackPanel1).AddChild(ctl);
            };
        ctl.BeginAnimation(Canvas.TopProperty, ani);
    }

    Adornerとかでも出来るかもしれないけど、面倒だったのでCanvasで。
    ItemsControlとかでの追加時のアニメーションはItemsControlに項目を追加したときにアニメーションを行いたいでもやってますが、今回のはXAMLだけで出来るかわかりません。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク 和和和 2012年5月5日 8:42
    • 回答としてマークされていない 和和和 2012年5月5日 8:47
    2012年5月2日 9:44
  • 頭の体操みたいになりましたがVisualStateとか使ってできました。仕組みはMarginのTopをマイナスにして初期状態でウィンドウの外側にいるコントロールをStackPanelに追加します。そして、コントロールのLoadedイベントでMarginを0,0,0,0に1秒かけてアニメーションするVisualStateに切り替えてます。

    <Window x:Class="AnimationWpfApplication.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:l="clr-namespace:AnimationWpfApplication"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
            Title="MainWindow" Height="371" Width="548">
        <DockPanel>
            <Button DockPanel.Dock="Top" Content="Add" Click="AddButton_Click"/>
            <StackPanel Name="target" VerticalAlignment="Bottom" />
        </DockPanel>
    </Window>
    

    namespace AnimationWpfApplication
    {
        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Media.Animation;
    
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            // 追加するコントロール
            private IEnumerator<FrameworkElement> controls = Controls();
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void AddButton_Click(object sender, RoutedEventArgs e)
            {
                // 追加するコントロールを取得
                this.controls.MoveNext();
                var current = this.controls.Current;
    
                // コントロールのセットアップ(VSMやMargin等)
                SetUpControl(current);
                // StackPanelに追加
                this.target.Children.Add(current);
            }
    
            private void SetUpControl(FrameworkElement current)
            {
                // 上づめ
                current.VerticalAlignment = VerticalAlignment.Top;
                // マージンを調整してウィンドウのちょっと上に強制移動
                current.Margin = new Thickness(0, -this.ActualHeight, 0, 0);
                
                // VisualStateの組み立て
                VisualStateGroup g = new VisualStateGroup();
                // 何もない標準のVisualState
                g.States.Add(new VisualState { Name = "Normal" });
                // 1秒かけてMarginを0,0,0,0にするストーリーボードを持つVisualState
                var added = new VisualState
                {
                    Name = "Added",
                    Storyboard = new Storyboard { Duration = new Duration(TimeSpan.FromSeconds(1)) }
                };
                var animation = new ThicknessAnimation { To = new Thickness(0,0,0,0) };
                Storyboard.SetTargetProperty(animation, new PropertyPath("Margin"));
                added.Storyboard.Children.Add(animation);
    
                // VisualStateをグループに追加
                g.States.Add(added);
    
                // コントロールにVisualStateGroupを追加
                VisualStateManager.GetVisualStateGroups(current).Add(g);
    
                // LoadedイベントでAddedのステートに変更
                current.Loaded += (_, __) => VisualStateManager.GoToElementState(current, "Added", false);
            }
    
            // コントロールを無限に生成するEnumeratorを返すメソッド
            public static IEnumerator<FrameworkElement> Controls()
            {
                while (true)
                {
                    yield return new Button { Content = DateTime.Now.ToString("HH:mm:ss") };
                    yield return new TextBlock { Text = DateTime.Now.ToString("HH:mm:ss") };
                    yield return new CheckBox { Content = DateTime.Now.ToString("HH:mm:ss") };
                }
            }
        }
    }
    


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    • 回答としてマーク 和和和 2012年5月5日 8:47
    2012年5月3日 14:19
  • ありがとうございます。

    テストプロジェクトに貼り付けて色々触ってみました。

    つまりコントロール移動するアニメーションのときはコントロールは自由に動けるCanvas内に配置しておき、最終的にはStackPanelに入れるということですね。

    2012年5月5日 8:41
  • ありがとうざいました。これは完璧でした!

    移動後に追加されるんその瞬間も美しくアニメーションしているのでまさにうってつけでした。

    しかし VisualStateなどわからないものがあるのでしばらく触ってみます。
    • 編集済み 和和和 2012年5月5日 8:54
    2012年5月5日 8:48