【问题标题】:WPF DataBound ListBox Animate on Add but not ScrollWPF DataBound ListBox 动画添加但不滚动
【发布时间】:2013-06-09 14:01:43
【问题描述】:

我有一个绑定到 ObservableCollection 的 WPF ListBox。当我向其中添加项目时,我想要一些“引起注意”到“新来者”的动画。有许多使用 DataTemplate 触发器和 FrameworkElment.Loaded 事件执行此操作的示例。效果很好,看起来很酷。

但是,滚动列表时也会触发相同的动画。这是由于 ListBox 的虚拟化功能,它会根据需要“加载”新项目。

有没有办法构造一个 ListBox,使它只在 ObservableCollection 发生变化时触发“加载”动画,而在滚动时不触发?

附:禁用虚拟化不是一种选择

【问题讨论】:

    标签: wpf


    【解决方案1】:

    为了触发动画,我创建了附加的依赖属性 AddNewItem (Boolean):

    public class MyDependencyClass : DependencyObject
    {
        public static readonly DependencyProperty AddNewItemProperty;
    
        public static void SetAddNewItem(DependencyObject DepObject, bool value)
        {
            DepObject.SetValue(AddNewItemProperty, value);
        }
    
        public static bool GetAddNewItem(DependencyObject DepObject)
        {
            return (bool)DepObject.GetValue(AddNewItemProperty);
        }
    
        static MyDependencyClass()
        {
            PropertyMetadata MyPropertyMetadata = new PropertyMetadata(false);
    
            AddNewItemProperty = DependencyProperty.RegisterAttached("AddNewItem",
                                                                typeof(bool),
                                                                typeof(MyDependencyClass),
                                                                MyPropertyMetadata);
        }
    }
    

    然后创建我们的依赖属性的边框(警告添加一个新项目):

    <Border x:Name="WarningBorder" Style="{StaticResource HideBorderStyle}" local:MyDependencyClass.AddNewItem="False" Height="33" Background="#44515B" BorderThickness="0" Margin="65,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Opacity="0">
            <TextBlock x:Name="WarningText" FontFamily="./#Segoe UI" TextAlignment="Center" Margin="0,5,0,0" FontSize="18" Foreground="Gainsboro" Text="You add new item" />
    </Border>
    

    样式 HideBorderStyle 包含在属性 AddNewItem=True 时触发的 DataTrigger。 HideBorderStyle 列表:

        <Style x:Key="HideBorderStyle" TargetType="{x:Type Border}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding ElementName=WarningBorder, Path=(local:MyDependencyClass.AddNewItem), Mode=OneWay}" Value="True">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Width" BeginTime="0:0:0" From="0.0" To="427.0" Duration="0:0:0.5" />
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" BeginTime="0:0:0" From="0.0" To="1.0" Duration="0:0:1.0" />
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" BeginTime="0:0:5" From="1.0" To="0.0" Duration="0:0:1.0" />
                                <DoubleAnimation Storyboard.TargetProperty="Width" BeginTime="0:0:5" From="427.0" To="0.0" Duration="0:0:1.0" />
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    

    假设我们有一个 SampleListBox 和 AddButton:

     <Grid>
         <ListBox x:Name="SampleListBox" Width="400" Height="200" BorderThickness="1" BorderBrush="Black" DisplayMemberPath="Name" Background="AliceBlue" Loaded="SampleListBox_Loaded" />
    
         <Button Content="Add" Width="50" Height="30" VerticalAlignment="Bottom" Click="AddButton_Click" />
    </Grid>
    

    AddButton_Click 列表:

        private void AddButton_Click(object sender, RoutedEventArgs e)
        {            
            PersonListBox.Add(new Person()
            {
                Name = "NewItem",
            });
    
            SampleListBox.ItemsSource = PersonListBox;            
        }
    

    在 SampleListBox_Loaded 中,我初始化 SampleListBox 的数据并分配一个处理程序 NotifyCollectionChangedEventHandler,当集合更改时调用该处理程序。 SampleListBox_Loaded 事件列表:

    private void SampleListBox_Loaded(object sender, RoutedEventArgs e)
    {
        PersonListBox.Add(new Person()
        {
            Name = "Peter Orange",
            Age = 32,
            Sample = "Sample",
        });
    
        SampleListBox.ItemsSource = PersonListBox;
        PersonListBox.CollectionChanged += new NotifyCollectionChangedEventHandler(PersonListBox_CollectionChanged);
    }
    

    在 PersonListBox_CollectionChanged 中,我为依赖属性 AddNewItem 设置了值 True。 PersonListBox_CollectionChanged 列表:

        private void PersonListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                MyDependencyClass.SetAddNewItem(WarningBorder, true);             
            }
        }
    

    现在,当一个项目被添加到集合中时,动画就会被触发。如果添加另一个元素动画不会触发。所以她工作了,你需要为以后的显示动画折叠值:

    private void ResetButton_Click(object sender, RoutedEventArgs e)
    {
        MyDependencyClass.SetAddNewItem(WarningBorder, false);
    }
    

    在这种情况下,您需要决定何时重置动画的值。

    编辑:

    根据我之前的示例为添加的项目设置动画。但是现在将给 ListBoxItem 分配带有 DataTrigger 的样式:

      <ListBox x:Name="SampleListBox" local:MyDependencyClass.AddNewItem="False" local:MyDependencyClass.InitCountOfList="0" Width="400" Height="200" BorderThickness="1" BorderBrush="Black" DisplayMemberPath="Name" Background="AliceBlue" Loaded="SampleListBox_Loaded">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Style.Triggers>                        
                        <MultiDataTrigger>
                            <MultiDataTrigger.Conditions>
                                <Condition Binding="{Binding ElementName=SampleListBox, Path=(local:MyDependencyClass.AddNewItem), Mode=OneWay}" Value="True" />
                                <Condition Binding="{Binding ElementName=SampleListBox, Converter={StaticResource BoolToInitItemsConverter}}" Value="True" />
                            </MultiDataTrigger.Conditions>
    
                            <MultiDataTrigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetProperty="Opacity" BeginTime="0:0:0" From="0.0" To="1.0" Duration="0:0:1.0" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </MultiDataTrigger.EnterActions>
                        </MultiDataTrigger>
                    </Style.Triggers>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
    

    为了正确工作,动画(尤其是在程序开始时)是根据另一个属性 - InitCountOfList (Int) 创建的:

    public class MyDependencyClass : DependencyObject
    {
        public static readonly DependencyProperty AddNewItemProperty, InitCountOfListProperty;
    
        #region AddNewItem
    
        public static void SetAddNewItem(DependencyObject DepObject, bool value)
        {
            DepObject.SetValue(AddNewItemProperty, value);
        }
    
        public static bool GetAddNewItem(DependencyObject DepObject)
        {
            return (bool)DepObject.GetValue(AddNewItemProperty);
        }
    
        #endregion
    
        #region InitCountOfList
    
        public static void SetInitCountOfList(DependencyObject DepObject, int value)
        {
            DepObject.SetValue(InitCountOfListProperty, value);
        }
    
        public static int GetInitCountOfList(DependencyObject DepObject)
        {
            return (int)DepObject.GetValue(InitCountOfListProperty);
        }
    
        #endregion 
    
        #region Constructor of DependencyProperty
    
        static MyDependencyClass()
        {
            PropertyMetadata BoolPropertyMetadata = new PropertyMetadata(false);
            PropertyMetadata IntPropertyMetadata = new PropertyMetadata(0);
    
            AddNewItemProperty = DependencyProperty.RegisterAttached("AddNewItem",
                                                                typeof(bool),
                                                                typeof(MyDependencyClass),
                                                                BoolPropertyMetadata);
    
            InitCountOfListProperty = DependencyProperty.RegisterAttached("InitCountOfList",
                                                                     typeof(int),
                                                                     typeof(MyDependencyClass),
                                                                     IntPropertyMetadata);
        }
    
        #endregion
    }
    

    数据在 Window_ContentRendered 事件中初始化:

     private void Window_ContentRendered(object sender, EventArgs e)
     {
         PersonListBox.Add(new Person()
         {
             Name = "Peter Orange",                              
         });
    
         SampleListBox.ItemsSource = PersonListBox;
    
         // Set the initial number of collection
         MyDependencyClass.SetInitCountOfList(SampleListBox, SampleListBox.Items.Count);
     }
    

    Handler NotifyCollectionChangedEventHandler 在 SampleListBox_Loaded 事件中赋值:

    private void SampleListBox_Loaded(object sender, RoutedEventArgs e)
    {            
        PersonListBox.CollectionChanged += new NotifyCollectionChangedEventHandler(PersonListBox_CollectionChanged);
    }
    

    Handler 和 AddButton_Click 保持不变:

    private void PersonListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            MyDependencyClass.SetAddNewItem(SampleListBox, true);
        }
    }
    
    private void AddButton_Click(object sender, RoutedEventArgs e)
    {            
        PersonListBox.Add(new Person()
        {
            Name = "NewItem",
        });
    }
    

    转换器BoolToInitItemsConverter,如果在开始时没有添加元素,则返回true:

    /// <summary>
    /// Return true, if count of collection > initial
    /// </summary>
    public class BoolToInitItemsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            ListBox MyListBox = value as ListBox;
            int InitCountOfList = MyDependencyClass.GetInitCountOfList(MyListBox);
    
            if (MyListBox.Items.Count > InitCountOfList)
            {
                // Added a new element
                return true;
            }
    
            // First run and we do not want
            // to run the animation
            return false;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return DependencyProperty.UnsetValue as object;
        }
    }
    

    现在动画工作只针对添加的项目。

    【讨论】:

    • 我知道你打算用这个去哪里。据我所知,设置 MyDependencyCall.SetAddNewItem() 会为所有可见项目设置动画,而不仅仅是“新来者”。不完全是我所追求的。 (我假设 的东西是一个模板)
    • 在 FrameworkElement.Loaded 事件上制作动画感觉是正确的方法。我需要的是某种仅在 NotifyCollectionChanged 触发时执行 FrameworkElement.Loaded 事件动画的条件。
    • 所以你想选择一个只添加新元素的动画?例如:在不同的背景上设置时间添加项。你到底想要什么动画?
    • 就是这样。添加的新项目具有动画效果。我可以处理的实际动画部分,但出于演示目的,淡入效果很好。
    猜你喜欢
    • 2021-05-12
    • 1970-01-01
    • 2013-02-24
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多