in Code
private void TickerGrid_Loaded(object sender, RoutedEventArgs e)
{
double height = TickerCanvas.ActualHeight - TextBoxMarquee.ActualHeight;
TextBoxMarquee.Margin = new Thickness(0, height / 2, 0, 0);
DoubleAnimation doubleAnimation = new DoubleAnimation();
doubleAnimation.From = -TextBoxMarquee.ActualWidth; // -277
doubleAnimation.To = TickerCanvas.ActualWidth; //524
doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(5));
TextBoxMarquee.BeginAnimation(Canvas.RightProperty, doubleAnimation);
}
in Xaml
<Grid x:Name="TickerGrid" Grid.Row="2" Loaded="TickerGrid_Loaded" Background="#2B2F3B" >
<Canvas ClipToBounds="True" Name="TickerCanvas" Background="Transparent">
<TextBlock ClipToBounds="True" Name="TextBoxMarquee" Background="#2B2F3B">
<TextBlock.Inlines>
<Run FontWeight="Bold" Foreground="#55CFE3" FontSize="14" Text="This is WPF Ticker Title." />
<Run FontSize="13" Foreground="#FFFFFF" Text="This is Content text." />
</TextBlock.Inlines>
</TextBlock>
</Canvas>
</Grid>
I did make a ticker, but I do not understand the principle that Canvas moves from "From" to "To".
TextBoxMarquee.BeginAnimation(Canvas.RightProperty, doubleAnimation); is the confusing part.
It does NOT animate the Canvas. It animates the Right Property of the textblock.
Canvas.RightProperty is just an identifier of the property, not a reference to the object that has the property. The call to BeginAnimation is on the TextBoxMarquee so the Right property of the TextBox will be animated.
If this is supposed to be a marquee animation then I'd use two textblocks.
You will find a working sample linked from the article here:
https://social.technet.microsoft.com/wiki/contents/articles/31416.wpf-mvvm-friendly-user-notification.aspx#Marquee
https://gallery.technet.microsoft.com/WPF-User-Notification-MVVM-98940828
The sample has a grid, which will expand to fit the width of whatever container it's in.
There is a canvas in this.
A Canvas does not clip it's content if it's outside of the canvas bounds.
That canvas allows one textblock to be positioned offscreen to the right of the panel and another left of the panel.
The grid is then animated left to right.
Storyboard:
<Window.Resources>
<!-- "To" of this is set in code because of the window resizing -->
<Storyboard x:Key="SBmarquee">
<DoubleAnimation From="0"
Duration="00:00:8"
Storyboard.TargetProperty="X"
Storyboard.TargetName="Xmarquee"
RepeatBehavior="3"/>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="MarqueeContainer"
To="1"/>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Storyboard.TargetName="MarqueeContainer"
BeginTime="0:0:20"
Duration="0:0:4" To="0"/>
</Storyboard>
</Window.Resources>
Grid:
<Grid x:Name="MarqueeContainer" VerticalAlignment="Bottom">
<Grid.RenderTransform>
<TranslateTransform x:Name="Xmarquee" X="0"/>
</Grid.RenderTransform>
<Canvas Height="24"
TextBlock.Foreground="Red">
<TextBlock Text="{Binding MarqueeMessage, NotifyOnTargetUpdated=True}" Canvas.Left="0">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard Storyboard="{StaticResource SBmarquee}" />
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
<TextBlock Text="{Binding MarqueeMessage}"
Foreground="Red"
Canvas.Left="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Canvas}}}"/>
</Canvas>
</Grid>
As the leftmost textblock moves off the display, the rightmost appears.
Code in Window_ContentRendered and Window_SizeChanged is used to calculate what the current width of the window is.
private Storyboard SBMarquee;
private DoubleAnimation XAnimation;
private void Window_ContentRendered(object sender, EventArgs e)
{
SBMarquee = this.Resources["SBmarquee"] as Storyboard;
XAnimation = SBMarquee.Children[0] as DoubleAnimation;
XAnimation.To = MarqueeContainer.ActualWidth * -1;
this.SizeChanged += Window_SizeChanged;
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
XAnimation.To = MarqueeContainer.ActualWidth * -1;
MarqueeContainer.Visibility = Visibility.Hidden;
SBMarquee.Begin();
MarqueeContainer.Visibility = Visibility.Visible;
}
The animation is stopped and re-started by telling it to begin again. This is the simplest way to avoid weirdness as the user resizes.
I hope that's clear.
Related
I have two borders that are replacing each other via animation, triggered by MouseEnter and MouseLeave events. And I also have a button on one of the borders with MouseEnter="Test_MouseEnter" MouseDown="Test_MouseDown".
XAML:
<UserControl.Resources>
<Storyboard x:Key="FirstCardStoryboard">
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetName="FirstBorder"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
<Storyboard x:Key="SecondCardStoryboard">
<DoubleAnimation
Duration="0:0:0.2"
Storyboard.TargetName="SecondBorder"
Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</UserControl.Resources>
<Grid MouseEnter="Grid_MouseEnter" MouseLeave="Grid_MouseLeave">
<Border x:Name="FirstBorder" Width="170" Height="210" CornerRadius="15" BorderThickness="2" Background="#FF323236" BorderBrush="Cyan">
<StackPanel VerticalAlignment="Center">
<Button x:Name="Test" Width="130" Height="30" MouseEnter="Test_MouseEnter" Click="Test_Click">
</Button>
</StackPanel>
</Border>
<Border x:Name="SecondBorder" Width="170" Height="210" CornerRadius="15" BorderThickness="2"
Background="#FF323236" BorderBrush="Cyan" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
</Grid>
</Border>
</Grid>
And the C# side:
public partial class ProductControl : UserControl
{
public ProductControl()
{
InitializeComponent();
}
private void Grid_MouseEnter(object sender, MouseEventArgs e)
{
Storyboard storyboard = (this.Resources["FirstCardStoryboard"] as Storyboard);
DoubleAnimation animation = storyboard.Children.First() as DoubleAnimation;
animation.To = 1;
storyboard.Begin();
Storyboard storyboardSecond = (this.Resources["SecondCardStoryboard"] as Storyboard);
DoubleAnimation animationSecond = storyboardSecond.Children.First() as DoubleAnimation;
animationSecond.To = 0;
storyboardSecond.Begin();
}
private void Grid_MouseLeave(object sender, MouseEventArgs e)
{
Storyboard storyboard = (this.Resources["FirstCardStoryboard"] as Storyboard);
DoubleAnimation animation = storyboard.Children.First() as DoubleAnimation;
animation.To = 0;
storyboard.Begin();
Storyboard storyboardSecond = (this.Resources["SecondCardStoryboard"] as Storyboard);
DoubleAnimation animationSecond = storyboardSecond.Children.First() as DoubleAnimation;
animationSecond.To = 1;
storyboardSecond.Begin();
}
private void Test_MouseEnter(object sender, MouseEventArgs e)
{
}
private void Test_Click(object sender, RoutedEventArgs e)
{
}
}
Then I put breakpoints on Test_MouseEnter and Test_MouseDown methods, but it seems that they do not call, when I move mouse over the button or click on it.
Is this because of the Grid with MouseEnter="Grid_MouseEnter" MouseLeave="Grid_MouseLeave" or is there another reason of this? If the Grid stops events, is the a way to pass on this events?
I tested Click="Test_Click" on the button, but this also doesn't work.
I found the problem. I was changing only opacity of the borders, but they were still there. So the Second Border handled all the events. So I also need to change Visibility of the borders in animation.
Or as #Clemens said I can change just IsHitTestVisible.
Basically I have this animation where when you click the button(s), it toggles the grid either revealing or hiding the 2nd column (which contains Button2) ... My problem is that when you spam-click the button, the animation gets queued so it needs to finish the first animation before doing the second one. The only workaround I have is to disable the button while the animation is active then re-enabling them once the animation is completed. But I'm trying to find a way where the active animation gets interrupted somehow and instead uses the current width of the grid to start the 2nd animation.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_toggle = false;
GridBodyWidthOne = new GridLength(1, GridUnitType.Star);
GridBodyWidthZero = new GridLength(0, GridUnitType.Star);
GridBody0Width = GridBodyWidthOne;
GridBody1Width = GridBodyWidthZero;
storyboard = this.FindResource("expandBody0") as Storyboard;
storyboard.FillBehavior = FillBehavior.Stop;
storyboard.Completed += (object o, EventArgs ea) => {
GridBody0.Width = GridBodyWidthOne;
GridBody1.Width = GridBodyWidthZero;
GridBody0.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridBody1.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridSplitter0.IsEnabled = false;};
storyboard = this.FindResource("retractBody0") as Storyboard;
storyboard.FillBehavior = FillBehavior.Stop;
storyboard.Completed += (object o, EventArgs ea) => {
GridBody0.Width = GridBodyWidthOne;
GridBody1.Width = GridBodyWidthOne;
GridBody0.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridBody1.BeginAnimation(ColumnDefinition.WidthProperty, null);
GridSplitter0.IsEnabled = true;};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ToggleMainBody1();
}
private void ToggleMainBody1()
{
double maxWidth = GridBody0.ActualWidth + GridBody1.ActualWidth;
double width0 = GridBody0.ActualWidth / maxWidth;
double width1 = GridBody1.ActualWidth / maxWidth;
GridBody0Width = new GridLength(width0, GridUnitType.Star);
GridBody1Width = new GridLength(width1, GridUnitType.Star);
if (!_toggle)
RevealMainBody1();
else
HideMainBody1();
_toggle = !_toggle;
}
private void HideMainBody1()
{
//storyboard = this.FindResource("retractBody0") as Storyboard;
//storyboard.Stop(this);
storyboard = this.FindResource("expandBody0") as Storyboard;
storyboard.Begin(this);
}
private void RevealMainBody1()
{
//storyboard = this.FindResource("expandBody0") as Storyboard;
//storyboard.Stop(this);
storyboard = this.FindResource("retractBody0") as Storyboard;
storyboard.Begin(this);
}
as for why I have FillBehavior set to 'Stop', I have a problem with the GridSplitter if it's set to 'HoldEnd'. 'HoldEnd' actually works well but not when I adjust it via the GridSplitter. So basically, the problem occurs when you double-click the toggle button (or trigerring the toggle twice without having the first animation finish).
<Window x:Class="ColumnAdjustmentV2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ColumnAdjustmentV2"
xmlns:g="clr-namespace:ColumnAdjustmentV2"
mc:Ignorable="d"
Title="MainWindow" Height="650" Width="800" Loaded="Window_Loaded" SizeChanged="Window_SizeChanged" StateChanged="Window_StateChanged" >
<Window.Resources>
<Storyboard x:Key="expandBody0">
<!--<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody0" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody0Width}" To="{Binding Path=GridBodyWidthOne}" />-->
<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody1" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody1Width}" To="{Binding Path=GridBodyWidthZero}" />
</Storyboard>
<Storyboard x:Key="retractBody0">
<!--<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody0" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody0Width}" To="{Binding Path=GridBodyWidthOne}" />-->
<g:GridLengthAnimation BeginTime="0:0:0" Duration="0:0:0.5" Storyboard.TargetName="GridBody1" Storyboard.TargetProperty="Width" From="{Binding Path=GridBody1Width}" To="{Binding Path=GridBodyWidthOne}" />
</Storyboard>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="500" />
<RowDefinition Height="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="GridBody0" Width="{Binding Path=GridBody0Width, Mode=TwoWay}" />
<ColumnDefinition x:Name="GridBodyDivider" Width="Auto" />
<ColumnDefinition x:Name="GridBody1" Width="{Binding Path=GridBody1Width, Mode=TwoWay}" />
</Grid.ColumnDefinitions>
<Button x:Name="Button1" Grid.Column="0" Content="Button1" Click="Button_Click" />
<GridSplitter x:Name="GridSplitter0" Grid.Column="1" Background="#FFCC0099" HorizontalAlignment="Center" Margin="0" Width="4" VerticalAlignment="Stretch" LayoutUpdated="GridSplitter_LayoutUpdated" MouseDoubleClick="GridSplitter_MouseDoubleClick" PreviewKeyDown="GridSplitter_PreviewKeyDown" PreviewMouseUp="GridSplitter_PreviewMouseUp" />
<Button x:Name="Button2" Grid.Column="2" Content="Button2" Click="Button_Click" />
</Grid>
<Grid Grid.Row="1">
<Button x:Name="Button3" Content=""/>
</Grid>
</Grid>
https://www.youtube.com/watch?v=2iE8cZC6EFQ
I have prepared a sample project that I think may be helpful for you: https://github.com/Drreamer/AnimationReverse
In this example I resize a button from min to max width using animation. To start animation or to run it in reverse direction simply click the button.
<Grid x:Name="rootGrid">
<Button x:Name="button" MinWidth="40" Width="40" Content="Expand"
local:MainWindow.AnimationDuration="0:0:2" HorizontalAlignment="Left" Click="button_Click" >
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="button"
Storyboard.TargetProperty="Width"
From="{Binding ActualWidth, ElementName=button}"
Duration="{Binding Path=(local:MainWindow.AnimationDuration), ElementName=button}">
<DoubleAnimation.To>
<MultiBinding Converter="{StaticResource ToWidthConverter}">
<Binding ElementName="button" Path="Tag" />
<Binding ElementName="button" Path="MinWidth" />
<Binding ElementName="rootGrid" Path="ActualWidth" />
</MultiBinding>
</DoubleAnimation.To>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
Here I use binding to dynamically set the To and Duration properties. I store information about current animation direction in the Tag property. The Duration is calculated dynamically from the Button.Click event based on the current element width. The goal here is to make sure that animation is always aplied with the same speed.
private void button_Click(object sender, RoutedEventArgs e) {
double currentPosition = (button.ActualWidth - button.MinWidth) / (rootGrid.ActualWidth - button.MinWidth);
if (button.Tag is bool && (bool)button.Tag)
button.Tag = false;
else {
currentPosition = 1 - currentPosition;
button.Tag = true;
}
SetAnimationDuration(button, new Duration(TimeSpan.FromSeconds(5 * currentPosition)));
}
I've come across a problem with making menus within a re-sizeable window. I've made a test app to demonstrate my problem below:
I'm animating a settings menu that transitions down from the top by changing the grid margin. When the settings menu is pushed above the window by changing the margin, if I resize the window height I can see the hidden menu rather than seeing the main screen (green) extending.
How Can I change it so the main grid (green) extends rather than being able to see the hidden settings menu?
Code:
<Window x:Class="ResizeTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ResizeTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
MinWidth="800"
MinHeight="450"
MaxWidth="800"
MaxHeight="900">
<Window.Resources>
<Storyboard x:Key="ShowRightMenu">
<ThicknessAnimation Storyboard.TargetProperty="Margin"
Storyboard.TargetName="gridMenu"
From="0, -450, 0, 450"
To="0"
DecelerationRatio="0.9"
Duration="0:0:1" />
</Storyboard>
<Storyboard x:Key="HideRightMenu">
<ThicknessAnimation Storyboard.TargetProperty="Margin"
Storyboard.TargetName="gridMenu"
From="0"
To="0, -450, 0, 450"
DecelerationRatio="0.9"
Duration="0:0:1" />
</Storyboard>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Background="Green">
</Grid>
<Grid x:Name="gridMenu" Background="Blue"
Margin="0, -450, 0, 450"
Grid.Column="1">
<TextBlock Text="Well hello there!" Foreground="White" FontSize="60"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
<Button HorizontalAlignment="Left" Width="100" Height="50" Margin="10" Content="Clicky Me"
Click="Button_Click" />
</Grid>
</Window>
I would suggest that you change the To and From Properties of the ThicknessAnimation so that it is up-to-date with the height of the window. And additionaly that at the start of an animation you set the Visibility of the menu to Visible and at the end to Collapsed if the menu should not be visible at the moment.
For this I would suggest that you add a field that holds the openness state of the menu
private bool menuOpen = false;
And alter one of the stroyboards to this
<Storyboard x:Key="MenuStoryboard">
<ThicknessAnimation Storyboard.TargetProperty="Margin"
Storyboard.TargetName="gridMenu"
DecelerationRatio="0.9"
Duration="0:0:1" />
</Storyboard>
and remove the other.
You should also remove the margin of the Grid and add Collapsed as a default Visibility
<Grid x:Name="gridMenu" Background="Blue"
Visibility="Collapsed"
Grid.Column="1">
...
</Grid>
And then in the Click-Event-Handler you can adjust the Storyboard to the situation and run the animation.
private void Button_Click(object sender, RoutedEventArgs e)
{
var storyBoard = this.FindResource("ShowRightMenu") as Storyboard;
var animation = storyBoard.Children[0] as ThicknessAnimation;
if (menuOpen)
{
animation.To = new Thickness(0, -this.Height, 0, Height);
animation.From = new Thickness(0);
}
else
{
animation.From = new Thickness(0, -this.Height, 0, this.Height);
animation.To = new Thickness(0);
}
this.gridMenu.Visibility = Visibility.Visible;
storyBoard.Begin();
void animationCompleated(object sender2, EventArgs e2)
{
if (!menuOpen)
{
this.gridMenu.Visibility = Visibility.Collapsed;
}
storyBoard.Completed -= animationCompleated;
}
storyBoard.Completed += animationCompleated;
menuOpen = !menuOpen;
}
I want to add a scrolling/moving text(from right to left) in the textblock.
It should scroll one time only.
I have Googled it but didn't find anything.
I want to scroll the text in the textblock (not the whole textblock) only once. Then scrolling should be stopped.
I found this code on net but it is not what I want. I want to scroll the text 1 time and then stop scrolling. Any idea how to do that?
<TextBlock FontSize="22" x:Name="txtScrolling" Margin="1386,208,-616,460">
<TextBlock.RenderTransform>
<TranslateTransform x:Name="translate" />
</TextBlock.RenderTransform>
<TextBlock.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="1">
<DoubleAnimation
From="1000" To="-1000"
Storyboard.TargetName="translate"
Storyboard.TargetProperty="X"
Duration="0:0:10" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
This is the Text to Scroll
</TextBlock>
You could try something like this..
This is an xaml example:
<Grid>
<ScrollViewer x:Name="Scroll_Content" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto" CanContentScroll="True" VerticalAlignment="Center" HorizontalAlignment="Center" Width="250" FlowDirection="RightToLeft">
<TextBlock Text="Hello World ------------ Hello World ------------ Hello World -------- Hello World"></TextBlock>
</ScrollViewer>
<Button x:Name="Start_Timer" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,10" Width="100" Height="50" Content="Start Timer" Click="Start_Timer_Click"/>
</Grid>
Code behind:
DispatcherTimer timer1 = new DispatcherTimer();
double num = 0;
public MainWindow()
{
InitializeComponent();
timer1.Interval = new TimeSpan(0,0,0,0,250);
timer1.Tick += timer1_Tick;
}
void timer1_Tick(object sender, EventArgs e)
{
try
{
Scroll_Content.ScrollToHorizontalOffset(num);
num++;
}
catch { }
}
void Start_Timer_Click(object sender, RoutedEventArgs e)
{
if (timer1.IsEnabled == false)
{
num = 0;
timer1.Start();
}
else
timer1.Stop();
}
How to stop an animation so it won't produce Completed event. Here's simple example
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="248" Width="318">
<Grid>
<Border Width="20" Height="20" Background="Red" MouseEnter="Border_MouseEnter" MouseLeave="Border_MouseLeave" x:Name="border" />
</Grid>
</Window>
And backing code:
private void Border_MouseEnter(object sender, MouseEventArgs e)
{
var a = new DoubleAnimation { To = 0, Duration = TimeSpan.FromMilliseconds(4000) };
a.Completed += (obj, args) => MessageBox.Show("Boom!");
border.BeginAnimation(Border.OpacityProperty, a);
}
private void Border_MouseLeave(object sender, MouseEventArgs e)
{
border.BeginAnimation(Border.OpacityProperty, null);
border.Opacity = 1;
}
If I move mouse out before rectangle becomes white it still will display popup window after some time. How to prevent this? Let's assume that Border_MouseLeave and Border_MouseEnter methods doesn't know about each other (they can't pass animation instance variable to each other).
you can use this:
<Border Width="20" Height="20" Background="Red" x:Name="border" >
<Border.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard Name="Ali">
<Storyboard>
<DoubleAnimation To="0" Duration="0:0:4" Completed="com" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<StopStoryboard BeginStoryboardName="Ali"/>
</EventTrigger>
</Border.Triggers>
</Border>
and :
private void com(object sender, EventArgs e)
{
MessageBox.Show("boom!");
}
You could use Property or Data Trigger's EnterActions and ExitActions properties or as #Ali said correctly use Begin and Stop storyboard.