When developing WPF views (XAML), we sometimes want to give the user an option to remove some elements from the view.
Usually, in order to achieve that, we bind the Command
property of an ICommandSource
(Button
, MenuItem
, etc...) to a Command
(property with type that implements ICommand
) in the view-model.
Sometimes, we want to perform an animation on the removed element when it is removed. Now, we have a problem. Suppose we have a button that is bound to a remove command in the view-model. When we click this button, the Click
event is raised and the bound command is executed. After the command has been executed, the element that the animation is intended for, is already removed. So, if we know about the remove operation only when the button is clicked and, directly after the button has been clicked the element is removed, when can we perform the animation?
In order to solve this problem, we can extend the Button
control by creating a class that derives from Button
and, add the needed functionality, like the following:
public class SuspendedButton : Button
{
#region SuspendTime
public TimeSpan SuspendTime
{
get { return (TimeSpan)GetValue(SuspendTimeProperty); }
set { SetValue(SuspendTimeProperty, value); }
}
public static readonly DependencyProperty SuspendTimeProperty =
DependencyProperty.Register("SuspendTime", typeof(TimeSpan), typeof(SuspendedButton), new UIPropertyMetadata(TimeSpan.Zero));
#endregion
#region BeforeClick
public static readonly RoutedEvent BeforeClickEvent = EventManager.RegisterRoutedEvent(
"BeforeClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SuspendedButton));
public event RoutedEventHandler BeforeClick
{
add { AddHandler(BeforeClickEvent, value); }
remove { RemoveHandler(BeforeClickEvent, value); }
}
#endregion
protected override void OnClick()
{
RaiseEvent(new RoutedEventArgs(SuspendedButton.BeforeClickEvent));
TimeSpan time = SuspendTime;
ThreadPool.QueueUserWorkItem(new WaitCallback(o =>
{
Thread.Sleep(time);
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new ThreadStart(() =>
{
base.OnClick();
}));
}));
}
}
The SuspendTime
property of this control can be used to tell how much time we have to wait between the actual click and the raise of the Click
event. Here we can set the duration of the animation.
The BeforeClick
event of this control is raised in the time of the actual click and, let us perform an animation before the Click
event is raised.
For example, look at the following ItemsControl
:
<ItemsControl ItemsSource="{Binding MyItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Storyboard x:Key="hideItemStoryboard">
<DoubleAnimation Storyboard.TargetName="mainBorder"
Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(ScaleTransform.ScaleY)"
To="0"
Duration="0:0:0.2" />
</Storyboard>
</DataTemplate.Resources>
<Border x:Name="mainBorder"
BorderThickness="1"
BorderBrush="Black"
CornerRadius="3"
Margin="2">
<Border.LayoutTransform>
<ScaleTransform ScaleY="1" />
</Border.LayoutTransform>
<DockPanel Margin="2">
<local:SuspendedButton x:Name="btnRemove"
Content="Remove"
SuspendTime="0:0:0.2"
DockPanel.Dock="Left"
VerticalAlignment="Center"
Command="{Binding RemoveCommand}"
Margin="5,0"/>
<TextBlock Text="{Binding Name}" />
</DockPanel>
</Border>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="local:SuspendedButton.BeforeClick"
SourceName="btnRemove">
<BeginStoryboard Storyboard="{StaticResource hideItemStoryboard}" />
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This ItemsControl
has an ItemTemplate
that contains: a Storyboard
with an animation for the remove operation, a SuspendedButton
that is bound to a remove command and, an EventTrigger
that performs the remove animation when the BeforeClick
event is raised. When the user clicks the remove button, the remove animation is performed and, then the element is removed.