WPF/MVVM - ItemsControl 中的 WrapPanel,添加子项上的动画

标签 wpf animation mvvm storyboard transition

鉴于:

<ScrollViewer VerticalScrollBarVisibility="Auto" >
    <ItemsControl ItemsSource="{Binding Controls}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel />              
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</ScrollViewer>

其中“控件绑定(bind)”是包含某种用户控件的 View 模型中的 ObservableCollection。

由于 wrappanel 中的内容居中,因此当前行为如下:
  • UC A 被添加到列表中,并显示在面板的中心。
  • UC B 被添加到列表中,UC A 向左移动,UC B 被添加到面板中。
  • UC C 被添加到列表中,UC A 和 B
    向左移动,并将 UC C 添加到面板中。

  • 我想要的是在添加新的用户控件时添加“移动”翻译/过渡,即我想在添加每个 UC 时向左侧显示 A/B..n 的过渡动画。

    我更愿意在 XAML 中做尽可能多的事情,而不是破坏 MVVM 模式。

    enter image description here

    奖励,我也希望能够在删除 UC 时制作动画。

    最佳答案

    我将控件视为 FrameworkElement 的 ObservableCollection
    您可以使用以下代码:

    <Window x:Class="Marathonbet.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Loaded="MainWindow_OnLoaded" Title="MainWindow" Height="350" Width="525"
            DataContext="{Binding RelativeSource={RelativeSource Self}}">
        <Window.Resources>
            <Storyboard x:Key="OnLoaded1">
                <ThicknessAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Margin)" >
                    <EasingThicknessKeyFrame KeyTime="0" Value="20,0,0,0"/>
                    <EasingThicknessKeyFrame KeyTime="0:0:0.4" Value="5"/>
                </ThicknessAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" >
                    <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                    <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="55"/>
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" >
                    <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                    <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
                    <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="1"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
            <Storyboard x:Key="OnUnloaded1" >
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
                    <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
                </DoubleAnimationUsingKeyFrames>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)">
                    <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="55"/>
                    <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </Window.Resources>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <ScrollViewer VerticalScrollBarVisibility="Auto"  Grid.Row="0">
                <ItemsControl ItemsSource="{Binding Controls}" x:Name="x">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <WrapPanel HorizontalAlignment="Center"/>
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                </ItemsControl>
            </ScrollViewer>
            <Button Grid.Row="1" Content="Add" Margin="5" HorizontalAlignment="Left" Padding="15,2,15,2" Click="btAdd_OnClick"/>
            <Button Grid.Row="1" Content="Remove" Margin="64,5,0,5" HorizontalAlignment="Left" Padding="15,2,15,2" Click="btRemove_OnClick"/>
        </Grid>
    </Window>
    

    背后的代码
    public class MyObservableCollection : ObservableCollection<FrameworkElement>
        {
    
            private Storyboard unloadedStoryboard;
    
            public Storyboard UnloadedSotryBoard
            {
                get { return unloadedStoryboard; }
                set
                {
                    unloadedStoryboard = value;
                    unloadedStoryboard.Completed += UnloadedStoryboardOnCompleted;
                }
            }
    
            public Storyboard LoadedSotryBoard { get; set; }
    
            protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    foreach (FrameworkElement item in e.NewItems)
                        item.BeginStoryboard(LoadedSotryBoard);
                }
                base.OnCollectionChanged(e);
            }
    
            private HashSet<int> indexesToRemove = new HashSet<int>();
    
            protected override void RemoveItem(int index)
            {
                indexesToRemove.Add(index);
                var item = Items[index];
                UnloadedSotryBoard.Begin(item);
            }
    
            private void UnloadedStoryboardOnCompleted(object sender, EventArgs eventArgs)
            {
                foreach (var i in new HashSet<int>(indexesToRemove))
                {
                    base.RemoveItem(i);
                    indexesToRemove.Remove(i);
                }
            }
    
        }
    
        public partial class MainWindow
        {
    
            public MyObservableCollection Controls { get; set; }
    
    
            #region Constructors
    
            public MainWindow()
            {
                Controls = new MyObservableCollection();
                InitializeComponent();
                Controls.LoadedSotryBoard = (Storyboard) FindResource("OnLoaded1");
                Controls.UnloadedSotryBoard = (Storyboard) FindResource("OnUnloaded1");
            }
    
            #endregion
    
    
            #region Events
    
            private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
            {
                Controls.Add(new MyControl {DataContext = "A"});
                Controls.Add(new MyControl {DataContext = "B"});
                Controls.Add(new MyControl {DataContext = "C"});
            }
    
            private void btAdd_OnClick(object sender, RoutedEventArgs e)
            {
                Controls.Add(new MyControl {DataContext = (char) new Random().Next(0, Byte.MaxValue)});
            }
    
            private void btRemove_OnClick(object sender, RoutedEventArgs e)
            {
                if (Controls.Count == 0)
                    return;
                Controls.RemoveAt(Controls.Count - 1);
            }
    
            #endregion
        }
    

    关于WPF/MVVM - ItemsControl 中的 WrapPanel,添加子项上的动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33756887/

    相关文章:

    c# - 设置列表框的默认值

    wpf - 将组合框 SelectedValue 绑定(bind)到文本框

    wpf - 我可以从其他模型继承 MVVM 模型吗?

    jquery - CSS显示:inline animation not working using jquery addClass

    c# - 未处理 WPF 用户控件

    c# - 如何从 ViewModel 异步更新 UI 元素

    .net - WPF 数据网格行删除线

    wpf - 编辑 Resharper 的 INotifyPropertyChanged

    javascript - 动画不透明度在 IE 上无法正常工作

    animation - 在 SwiftUI 中滑动新图像