I'm brand spanking new to WPF and XAML, so I'm sorry if this is a silly question:
I am trying to start a StoryBoard on a TextBlock that changes the text. I want to start this animation when the TextBlock becomes visible. It looks like the only events you can trigger inside of a TextBlock.Triggers bracket is an EventTrigger. If this is so, as far as I can see an EventTrigger needs a routed event, but IsVisibilityChanged isn't one. Since I can't use that, any ideas as to what I should do instead?
I have attached a sample of my code that isn't working (doesn't compile), just to illustrate what it is that I am trying to do:
<TextBlock Foreground="LightGray" Text="Payfast Running" Name="AnimatedTextBlock">
<TextBlock.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<StringAnimationUsingKeyFrames
Storyboard.TargetProperty="(TextBlock.Text)"
Duration="0:0:1.5"
Storyboard.TargetName="AnimatedTextBlock"
RepeatBehavior="Forever">
<DiscreteStringKeyFrame Value="Payfast Running" KeyTime="0:0:0"/>
<DiscreteStringKeyFrame Value="Payfast Running." KeyTime="0:0:0:5"/>
<DiscreteStringKeyFrame Value="Payfast Running.." KeyTime="0:0:1"/>
<DiscreteStringKeyFrame Value="Payfast Running..." KeyTime="0:0:1:5"/>
</StringAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</TextBlock.Triggers>
</TextBlock>
Lastly, I need to do this in markup, not CodeBehind, if possible.
You might need to wrap that all up into a style.
<TextBlock Foreground="LightGray" Text="Payfast Running" Name="AnimatedTextBlock">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Visibility, RelativeSource={RelativeSource TemplatedParent}}" Value="Visible">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<StringAnimationUsingKeyFrames
Storyboard.TargetProperty="(TextBlock.Text)"
Duration="0:0:1.5"
Storyboard.TargetName="AnimatedTextBlock"
RepeatBehavior="Forever">
<DiscreteStringKeyFrame Value="Payfast Running" KeyTime="0:0:0"/>
<DiscreteStringKeyFrame Value="Payfast Running." KeyTime="0:0:0:5"/>
<DiscreteStringKeyFrame Value="Payfast Running.." KeyTime="0:0:1"/>
<DiscreteStringKeyFrame Value="Payfast Running..." KeyTime="0:0:1:5"/>
</StringAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
"I want to start this animation when the TextBlock becomes visible."
You could bind your TextBlock's Visibility to a boolean property (note that you have to add a resource for boolean-to-visibility conversion):
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
...
<TextBlock Visibility="{Binding Path=ReadyToPlay, Converter={StaticResource BoolToVisConverter}}"/>
And then, in your ReadyToPlay property's setter method, you could add an if statement that checks for whether or not to start your Storyboard:
if (value) {
// start Storyboard:
// find Storyboard instance and call Begin() method on it
}
By doing this, when ReadyToPlay becomes true, the TextBlock will become visible and the Storyboard will begin simultaneously.
Related
I'm getting an error with the following details:
Source Name property cannot be set within Style. Triggers section
<Rectangle Margin="121,163,0,248" HorizontalAlignment="Left" Width="33" Height="34">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"></Setter>
<Style.Triggers>
<EventTrigger SourceName="myButton" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Fill.Color" To="Orange"
Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
BeginTime="0:0:0">
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
I want to change the rectangle fill color with Color Animation tag when click on button.
Like it says, you cannot use source name in a style like that.
You can use a data trigger instead. Set say a bool property in your viewmodel from your button's command of click.
Then start your storyboard with a datatrigger binding that bool property and comparing value.
You can probably easily Google datatrigger and storyboard but here's a so question includes an example.
WPF Data Triggers and Story Boards
Btw.
Routed events are rarely very useful IME. Binding icommand is way more practical. Usually.
Edit:
Here's a quick and dirty sample using a togglebutton. Since this approach uses binding it can reference controls by name. Binding is resolved at run time.
<Grid>
<Rectangle>
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=StartStop}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="ColourStoryboard">
<Storyboard>
<ColorAnimation
Storyboard.TargetProperty="Fill.Color" To="Orange"
Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever"
>
</ColorAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="ColourStoryboard"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<ToggleButton Content="Start Stop"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Name="StartStop"/>
</Grid>
</Window>
Paste the grid inside a mainwindow, spin it up. When you click the togglebutton it sets ischecked true so the rectangle animates to orange and back to blue. When you click the button again, ischecked becomes false and the animation stops.
You could instead write code in a button handler that set a bound property which is in the datacontext and bind the datatrigger to that bound property. That's what the markup in the link is doing with IsBusy.
In short my quetion is: Can I use databinding for the duration property of a storyboard?
My intention was to design a circular progressbar where i have a simple counter. Now based on the counter I wanted to change the duration of the animation. Is there any way I can achieve this by databinding or is there any other way.
I am using an Arc to create the progressbar effect as you can see below. When the DependencyProperty PauseAnimation becomes false, my animation starts. When it is set to true, it resets(I couldnt find a way to Pause the animation so I am resetting it to start from begining). The below code runs perfectly but I am having trouble setting the duration of the storyboard. I want to change that based on a property in my control. Can I achieve this?
<ed:Arc x:Name="AnimatingArc" ArcThickness="2" ArcThicknessUnit="Pixel" StartAngle="0" EndAngle="360" Fill="{DynamicResource ApplicationPrimaryColour}" HorizontalAlignment="Left" Height="25" Stretch="None" Stroke="Transparent" VerticalAlignment="Top" Width="25" Margin="8,47,0,0">
<ed:Arc.Style>
<Style TargetType="{x:Type ed:Arc}">
<Style.Triggers>
<DataTrigger Binding="{Binding PauseAnimation}" Value="false">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="StartAngle" From="0" To="360" Duration="0:0:30" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="StartAngle" To="0" Duration="0:0:10" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ed:Arc.Style>
I want a control (e.g. a GroupBox) to show a grow animation when it becomes visible and a shrink animation, when the visibility is changed to "Collapsed".
Therefore, I created a style which implements an animated grow and shrink effect as shown here in a small sample application (shown below).
However, only the grow animation is shown. Instead of showing the shrink animation, the groupbox disappears at once.
Can anyone tell me, why?
And even better, how to fix it?
<Window x:Class="ShrinkTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="FrameworkElement" x:Key="ExpandableElement">
<Setter Property="RenderTransformOrigin" Value="0.5 0" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" From="0" To="1" Duration="0:0:0.5" AccelerationRatio="0.2" DecelerationRatio="0.4"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="Visibility" Value="Hidden">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" From="1" To="0" Duration="0:0:0.5" AccelerationRatio="0.2" DecelerationRatio="0.4"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Margin="8" Width="140" Click="ButtonBase_OnClick">Expand / Shrink</Button>
<TextBlock Grid.Row="1" Text="--- Header ---"/>
<GroupBox x:Name="GroupBox" Grid.Row="2" Header="GroupBox" Style="{StaticResource ExpandableElement}" >
<StackPanel Orientation="Vertical">
<TextBlock Text="Test Test Test"/>
<TextBlock Text="Test Test Test"/>
<TextBlock Text="Test Test Test"/>
<TextBlock Text="Test Test Test"/>
<TextBlock Text="Test Test Test"/>
</StackPanel>
</GroupBox>
<TextBlock Grid.Row="3" Text="--- Footer ---"/>
</Grid>
</Window>
I had a similar problem. Just think about it for a minute... your problem is that you can see your animation when it's visible, but you can't when it is hidden. That is also your answer to why... because it is hidden. I know, that's fairly unsatisfactory answer, but that's just how it is.
As to how to fix it... well saying it is simple, but implementing it is not. Simply put, you have to run your animation until it ends and then set the Visibility to Hidden. So unfortunately this means that nice, simple setting the Visibility property in the Trigger is no longer viable... it's ok to make it visible, just not for hiding.
In my case, I have a whole framework that I built my animations into. Basically speaking though, when I remove items from the collections, internally the item is not actually removed, but instead its exit animation is started. Only when that animation is complete will the internal collection actually remove the item.
So if you can be bothered, then you'll have to implement something like this where, rather than setting the Visibility property to Hidden, you set another property to true which triggers the animation and when the Completed event from that animation is called, then you set the Visibility property to Hidden.
Sheridan is right. As soon as a control becomes invisible, it doesn't matter, which animation you apply to it. :-)
So I created a special ExpandingContentControl:
public class ExpandingContentControl : ContentControl
{
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register(
"IsExpanded", typeof(bool), typeof(ExpandingContentControl), new PropertyMetadata(false));
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}
public ExpandingContentControl()
{
Visibility = IsExpanded ? Visibility.Visible : Visibility.Collapsed;
}
}
But there was also a problem with the style: Creating two triggers which are bound to different values of the same property obviously doesn't work.
Instead, I'm now using just one trigger where the EnterAction implements growing and the ExitAction implements shrinking the control:
<Style TargetType="controls:ExpandingContentControl" >
<Setter Property="RenderTransformOrigin" Value="0.5 1" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" KeyTime="00:00:00"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" From="0" To="1"
Duration="0:0:0.3" DecelerationRatio="0.4"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" From="1" To="0" Duration="0:0:0.2" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" KeyTime="00:00:0.2"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
I have a DataTemplate for an ItemsControl which is working fine. There is a DataTrigger in the DataTemplate which contains a BeginStoryboard EnterAction. I am trying to wire up the Completed event of the storyboard to something in code behind, specifically a method on the data object, but I can be flexible about that - at the moment I just want it to run any piece of C# code when the animation has completed.
Specifying a value for the Completed XAML attribute does not compile as the attribute is defined inside a template so there is no specific method to wire up to. So I will need to use code behind to wire up the event manually.
To this end I have looked at the application with Snoop to try to find where in the logical or visual tree the inflated template Storyboards end up. So far all I can see is a ContentControl created for each item, with its ContentTemplate set. The Content property of each ContentControl is set to its corresponding data object. The ContentTemplate property contains the Triggers collection which contain the EnterActions and ultimately the Storyboard. My question is, do all the items share a single template instance for their ContentTemplate property, or do they each get their own copy? If they share one, then where are the inflated triggers and storyboards created?
I've extracted the pertinent parts of my XAML:
<Style TargetType="{x:Type m:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type m:MyControl}">
<Grid Name="ControlRoot" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<!-- ... -->
<ItemsControl ItemsSource="...">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type m:MyDataType}">
<Grid>
<Ellipse Name="IconHighlight1" Fill="{DynamicResource GoldRadialFade}" Width="70" Height="70" StrokeThickness="0" Opacity="0"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Highlighted}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard HandoffBehavior="Compose">
<Storyboard Name="ConnectToMe" Duration="0:0:2.5" FillBehavior="Stop">
<DoubleAnimation To="400" Duration="0:0:1.5" Storyboard.TargetName="IconHighlight1" Storyboard.TargetProperty="Height" FillBehavior="Stop" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In such cases, I'd normally prefer to have a bool in the DataContext of the Item that your Storyboard is applying to and say call it AnimationCompleted
Now by modifying your Storyboard to
<Storyboard x:Key="ConnectToMe" Duration="0:0:2.5" FillBehavior="Stop">
<DoubleAnimation To="400" Duration="0:0:1.5" Storyboard.TargetName="IconHighlight1" Storyboard.TargetProperty="Height" FillBehavior="Stop" />
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="DataContext.AnimationCompleted" FillBehavior="HoldEnd">
<DiscreteBooleanKeyFrame Value="False" KeyTime="0:0:0" />
</BooleanAnimationUsingKeyFrames>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="DataContext.AnimationCompleted" FillBehavior="HoldEnd">
<DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:2.5" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
We toggle the bool AnimationCompleted to true at the end point of the animation. Hence in the property setter of AnimationCompleted check if the incoming value is True and trigger your corresponding function/method from there
I simply want to open up the WPF Popup with a delay, sort of like a ToolTip.
How can I achieve this?
And, by the way, Popup.PopupAnimation = PopupAnimation.Fade ... fades in too quickly. I want at least half a second in there.
You can create a style to be applied to the Popup in the following way:
<Style x:Key="TooltipPopupStyle" TargetType="Popup">
<Style.Triggers>
<DataTrigger Binding="{Binding PlacementTarget.IsMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="OpenPopupStoryBoard" >
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" FillBehavior="HoldEnd">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.25" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<PauseStoryboard BeginStoryboardName="OpenPopupStoryBoard"/>
<BeginStoryboard x:Name="ClosePopupStoryBoard">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" FillBehavior="HoldEnd">
<DiscreteBooleanKeyFrame KeyTime="0:0:1" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<PauseStoryboard BeginStoryboardName="ClosePopupStoryBoard" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<PauseStoryboard BeginStoryboardName="OpenPopupStoryBoard"/>
<ResumeStoryboard BeginStoryboardName="ClosePopupStoryBoard" />
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
Then, whenever you want to use it, you would write markup similar to this (notice the binding for the PlacementTarget):
<TextBlock x:Name="TargetControl" Text="Hover over me!" />
<Popup PlacementTarget="{Binding ElementName=TargetControl}" Style="{StaticResource TooltipPopupStyle}">
<Border BorderBrush="Red" BorderThickness="1" Background="White">
<TextBlock Text="This is a Popup behaving somewhat like the tooltip!" Margin="10" />
</Border>
</Popup>
The answer cplotts pasted is good but may not apply in your case because it leaves the animation attached to the IsOpen property, effectively locking it in place and preventing it from being changed via direct property setting, binding, and other ways. This may make it difficult to use with your code, depending on how you are using it.
If that is the case, I would switch to starting a DispatcherTimer when you want to open a popup after some delay, like this:
_popupTimer = new DispatcherTimer(DispatcherPriority.Normal);
_popupTimer.Interval = TimeSpan.FromMilliseconds(100);
_popupTimer.Tick += (obj, e) =>
{
_popup.IsOpen = true;
};
_popupTimer.Start();
For a ToolTip-like behavior this could be done on MouseEnter. If you want to cancel the popup opening for some reason (such as if the mouse leaves the control before the popup appears), just:
_popupTimer.Stop();
Update
As cplotts obseved in the comment, you will also want to set _popup.IsOpen = false in some situations in the MouseLeave event, depending on your logic for handling the mouse enter / exit events between your control and the popup. Be aware that you usually don't want to blindly set IsOpen=false on every MouseLeave event, because it may do so when the popup appears over it. This would in some situations lead to a flickering popup. So you'll need some logic there.
First off ... the credit for this answer goes to Eric Burke. He answered this very question posted in the WPF Disciples group. I thought it would be useful to put this answer out on StackOverflow too.
Basically, you need to animate the IsOpen property of the Popup with with a DiscreteBooleanKeyFrame.
Check out the following xaml (which can easily be pasted into Kaxaml or another loose xaml editing utility):
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<ContentPresenter>
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Grid>
<CheckBox
x:Name="cb"
Width="100"
Height="40"
Content="Hover Over Me"
/>
<Popup
x:Name="popup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=cb}"
>
<Border Width="400" Height="400" Background="Red"/>
</Popup>
</Grid>
<DataTemplate.Triggers>
<Trigger SourceName="cb" Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="bsb">
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="popup"
Storyboard.TargetProperty="IsOpen"
FillBehavior="HoldEnd"
>
<DiscreteBooleanKeyFrame KeyTime="0:0:0.5" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="bsb"/>
</Trigger.ExitActions>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Page>
Please note that I modified his original solution slightly ... to trigger the IsOpen on mouse over versus checking the CheckBox as he had it. All in the attempt to make Popup behave a little like ToolTip.
System.Windows.Controls.ToolTip tp = new System.Windows.Controls.ToolTip();
System.Windows.Threading.DispatcherTimer tooltipTimer =
new System.Windows.Threading.DispatcherTimer
(
System.Windows.Threading.DispatcherPriority.Normal
);
private void TooltipInvalidCharacter()
{
tp.Content =
"A flie name cannot contain any of the following character :" +
"\n" + "\t" + "\\ / : * ? \" < > |";
tooltipTimer.Interval = TimeSpan.FromSeconds(5);
tooltipTimer.Tick += new EventHandler(tooltipTimer_Tick);
tp.IsOpen = true;
tooltipTimer.Start();
}
void tooltipTimer_Tick(object sender, EventArgs e)
{
tp.IsOpen = false;
tooltipTimer.Stop();
}
You can extend the XAML for this solution so the popup stays open as long as the mouse is over it, then disappears automatically.
I modified the sample as follows:
Create a "ClosePopop" animation which sets IsOpen to False after 0.5 seconds. I made this a resource because it's used twice.
For the control's IsMouseOver trigger, add an ExitAction that starts the ClosePopup animation. This gives the user a chance to move the mouse over the popup before it closes. I named this animation"bxb"
Add a Trigger to the popup's IsMouseOver property. On mouseover, stop (but don't remove) the original "bxb" ClosePopup animation. This leaves the popup visible; removing the animation here will make the popup close.
On the popup's mouseout, start a new ClosePopup animation then remove the "bxb" animation. The last step is critical because otherwise the first, stopped "bxb" animation will keep the popup open.
This version turns the pop blue while the mouse is over it so you can see the sequence of events with Kaxaml.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="TooltipPopup">
<Grid>
<CheckBox
x:Name="cb"
Width="100"
Height="40"
Content="Hover Over Me"/>
<Popup
x:Name="popup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=cb}">
<Border x:Name="border" Width="400" Height="400" Background="Red"/>
</Popup>
</Grid>
<DataTemplate.Resources>
<Storyboard x:Key="ClosePopup">
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="popup"
Storyboard.TargetProperty="IsOpen"
FillBehavior="Stop">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.5" Value="False"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</DataTemplate.Resources>
<DataTemplate.Triggers>
<Trigger SourceName="cb" Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="bsb" >
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetName="popup"
Storyboard.TargetProperty="IsOpen"
FillBehavior="HoldEnd">
<DiscreteBooleanKeyFrame KeyTime="0:0:0.5" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="bsb"/>
<BeginStoryboard x:Name="bxb" Storyboard="{StaticResource ClosePopup}"/>
</Trigger.ExitActions>
</Trigger>
<Trigger SourceName="popup" Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="Blue"/>
<Trigger.EnterActions>
<StopStoryboard BeginStoryboardName="bxb"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource ClosePopup}"/>
<RemoveStoryboard BeginStoryboardName="bxb"/>
</Trigger.ExitActions>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</Page>