I\'ve got a WPF ListBox bound to an ObservableCollection. When I add items to it I want some animation that \"draws attention\" to the \"new arrival\". There are numerous ex
To trigger an animation I created my attached dependency property 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);
}
}
Then create the border (warning to add a new item) of our dependency property:
<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>
Style HideBorderStyle contained DataTrigger that is triggered when the property AddNewItem=True. Listing of 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>
Suppose we have a SampleListBox and 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>
Listing of AddButton_Click:
private void AddButton_Click(object sender, RoutedEventArgs e)
{
PersonListBox.Add(new Person()
{
Name = "NewItem",
});
SampleListBox.ItemsSource = PersonListBox;
}
In SampleListBox_Loaded I initialize the data for SampleListBox and assign a handler NotifyCollectionChangedEventHandler, which is called when a collection changes. Listing of SampleListBox_Loaded event:
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);
}
In PersonListBox_CollectionChanged I set value True for dependency property AddNewItem. Listing of PersonListBox_CollectionChanged:
private void PersonListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
MyDependencyClass.SetAddNewItem(WarningBorder, true);
}
}
Now the animation is triggered when an item is added to the collection. If you add another element animation not triggered. So she worked, you need to fold values for future display animation:
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
MyDependencyClass.SetAddNewItem(WarningBorder, false);
}
In this case, you need to decide when to reset the value the animation.
EDIT:
To animate an added item I had based my previous example. But now style with a DataTrigger will be assigned for ListBoxItem:
<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>
For correct work the animation (especially at the start of the program) was created according to another property - 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
}
The data are initialized in Window_ContentRendered event:
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 assign in SampleListBox_Loaded event:
private void SampleListBox_Loaded(object sender, RoutedEventArgs e)
{
PersonListBox.CollectionChanged += new NotifyCollectionChangedEventHandler(PersonListBox_CollectionChanged);
}
Handler and AddButton_Click remained the same:
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",
});
}
Converter BoolToInitItemsConverter, returns true if the addition of the elements did not happen during the start:
/// <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;
}
}
Now the animation work is only for the added item.