I have a situation where I want the user to make choices in a certain order (first I want the user to select the database, then I want him to tell me his credentials).
To do this I have challenged myself with the task of making a listbox that expands the item on selection.
To make it expand is not difficult (something like
Visibility="{Binding Path=IsSelected
, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}
, Converter={StaticResource VisibilityOfBool}
, ConverterParameter=False}"
will do the trick).
The problem is that the transition is instantaneous; it is hard for the user to see what happened. What I should like to do is an animated expansion of the hidden panel...
Here is a page to demonstrate what I mean:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="SlidingExpansionOnSelected">
<!--x:Class="TorsSandBox.Pages.SlidingExpansionOnSelected"-->
<Page.Resources>
<DataTemplate x:Key="daItemTemplate">
<StackPanel Margin="10">
<StackPanel.Triggers>
<EventTrigger RoutedEvent="ListBoxItem.Selected">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="daTransform"
Storyboard.TargetProperty="ScaleY"
To="1.0" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="ListBoxItem.Unselected">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="daTransform"
Storyboard.TargetProperty="ScaleY"
To="0" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</StackPanel.Triggers>
<TextBlock x:Name="Header" Text="{Binding}"/>
<Border x:Name="daBorder"
BorderThickness="3" BorderBrush="DarkOrange" CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="20 10 10 10"
MinHeight="100"
>
<Border.LayoutTransform>
<ScaleTransform x:Name="daTransform" ScaleX="1" ScaleY="0"/>
</Border.LayoutTransform>
<TextBlock Text="Hide this until selected"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</StackPanel>
</DataTemplate>
</Page.Resources>
<ListBox
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource daItemTemplate}"
>
<system:String>First row</system:String>
<system:String>Second row</system:String>
<system:String>Third row</system:String>
<system:String>Last row</system:String>
</ListBox>
</Page>
The triggers doesn't work, but that's because I can't make them fire...Anybody knows how to make this happen?
Regards
Tor Thorbergsen
This stuff is way too complicated...
What's wrong with your approach is that the animation only affects elements lower on the VisualTree, that means the container is not affected as far as i know.
Specifying the property path in animations is a major pain, i say. I could not access the border inside the StackPanel because its Children property is not a dependency property and the whole syntax is rather unusual.
Anyway, here's a hacky solution, that works:
<Style TargetType="ListBoxItem">
<Style.Resources>
<Storyboard x:Key="OnSelected1"/>
</Style.Resources>
<Setter Property="Tag">
<Setter.Value>
<sys:Double>0</sys:Double>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel x:Name="stackPanel" Margin="10">
<TextBlock x:Name="Header" Text="{Binding}"/>
<Border x:Name="daBorder"
BorderThickness="3" BorderBrush="DarkOrange" CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="20 10 10 10"
MinHeight="100">
<Border.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=Tag}"/>
</Border.LayoutTransform>
<TextBlock Text="Hide this until selected" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<EventTrigger RoutedEvent="ListBoxItem.Selected">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(ListBoxItem.Tag)"
Storyboard.TargetName="{x:Null}"
To="1.0" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
<BeginStoryboard Storyboard="{StaticResource OnSelected1}"/>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="ListBoxItem.Unselected">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(ListBoxItem.Tag)"
Storyboard.TargetName="{x:Null}"
To="0" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
I've tried to extract the ScaleTransform and reference it both in the animation and in the Border but that did not work for whatever reason.
EDIT:
Ok, I've figure it out. I've created an attached property for this (instead of using Tag).
public static class ListBoxHelper
{
public static readonly DependencyProperty ScaleYAnimationProperty =
DependencyProperty.RegisterAttached("ScaleYAnimation", typeof(double), typeof(ListBoxHelper), new FrameworkPropertyMetadata(0d));
public static double GetScaleYAnimation(UIElement element)
{
return (double)element.GetValue(ScaleYAnimationProperty);
}
public static void SetScaleYAnimation(UIElement element, double value)
{
element.SetValue(ScaleYAnimationProperty, value);
}
}
<ListBox ItemsSource="{Binding Contacts}" HorizontalContentAlignment="Stretch">
<ListBox.Resources>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="controls:ListBoxHelper.ScaleYAnimation" Value="0"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<controls:CircleContentControl Grid.Column="0" Width="40" Height="40">
<Image Source="{Binding Image}"/>
</controls:CircleContentControl>
<TextBlock Text="{Binding FullName}" Grid.Column="1" Margin="10,0,5,0" VerticalAlignment="Center"/>
<TextBlock Text="{Binding PhoneNumber}" Grid.Column="2" VerticalAlignment="Center" FontStyle="Italic">
<TextBlock.LayoutTransform>
<ScaleTransform ScaleX="0.7" ScaleY="0.7"/>
</TextBlock.LayoutTransform>
</TextBlock>
<StackPanel Orientation="Horizontal" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Center">
<Button cal:Message.Attach="Call($dataContext)" Width="30" Height="30" Style="{StaticResource ContactDialButtonStyle}">
<Rectangle Width="10" Height="10" Fill="{Binding Path=Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{DynamicResource appbar_phone}" />
</Rectangle.OpacityMask>
</Rectangle>
</Button>
</StackPanel>
</Grid>
<Border x:Name="daBorder"
BorderThickness="3" BorderBrush="DarkOrange" CornerRadius="5"
HorizontalAlignment="Stretch"
Margin="20 10 10 10"
MinHeight="100">
<Border.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=(controls:ListBoxHelper.ScaleYAnimation)}"/>
</Border.LayoutTransform>
<TextBlock Text="Hide this until selected" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<EventTrigger RoutedEvent="ListBoxItem.Selected">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(controls:ListBoxHelper.ScaleYAnimation)"
Storyboard.TargetName="{x:Null}"
To="1.0" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger RoutedEvent="ListBoxItem.Unselected">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(controls:ListBoxHelper.ScaleYAnimation)"
Storyboard.TargetName="{x:Null}"
To="0.0" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
I'm using #H.B solution. It works the first time the list is loaded. However, if I expand one listboxitem, switch to another tab and go back to the tab where listbox is, an exception is thrown:
System.Windows.Data Error: 23 : Cannot convert '<null>' from type '<null>' to type 'System.Double' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: DoubleConverter não pode ser convertido de (nulo).
em System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
em System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
em System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
em MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'
System.Windows.Data Error: 6 : 'ObjectSourceConverter' converter failed to convert value '<null>' (type '<null>'); fallback value will be used, if available. BindingExpression:Path=Tag; DataItem='ListBoxItem' (Name=''); target element is 'ScaleTransform' (HashCode=48000142); target property is 'ScaleY' (type 'Double') NotSupportedException:'System.NotSupportedException: DoubleConverter não pode ser convertido de (nulo).
em MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)
em MS.Internal.Data.ObjectSourceConverter.Convert(Object o, Type type, Object parameter, CultureInfo culture)
em System.Windows.Data.BindingExpression.ConvertHelper(IValueConverter converter, Object value, Type targetType, Object parameter, CultureInfo culture)'
Anyone having this too?
You should not need the ListBoxItem prefix on your events, just use "Selected" and "Unselected".
Another alternative to try is a property trigger:
https://web.archive.org/web/20120225232943/http://en.csharp-online.net/WPF_Styles_and_Control_Templates%E2%80%94Property_Triggers
Related
I would like to visualize multiple circles, each of which has its own animation using a path. Now I have a button that I like to click to start animation for all these circles in the view. I am new to WPF and I would like to stick to MVVM as much as possible. What makes the most sense to me is to create ItemsControl which has Canvas in the view and binding the ItemSource to my viewmodel.
The issue I have is I don't know set up RouteEvent to button click inside the ItemsControl.
<EventTrigger RoutedEvent="Button.Click" SourceName=" PlayButton"> clearly it does not find the button with that name because the MyCircle object does not have the button. See the following for my source code.
my view (XMAL) source code
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button x:Name="PlayButton" Click="Play_Click" Content="Play" Grid.Row="0" Grid.Column="1"/>
<Button x:Name="StopButton" Click="Stop_Click" Content="Stop" Grid.Row="1" Grid.Column="1"/>
</Grid>
<Grid Grid.Row="1">
<Canvas>
<ItemsControl ItemsSource="{Binding MyItemsModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate x:Name="ClockwiseBobbinsDataTemplate">
<Path Fill="Red" x:Name="ClockwiseBobbinsPath">
<Path.Data>
<EllipseGeometry x:Name="MyCircle" RadiusX="5" RadiusY="5"/>
</Path.Data>
<Path.Triggers>
<EventTrigger RoutedEvent="Button.Click" SourceName="PlayButton">
<BeginStoryboard>
<Storyboard>
<PointAnimationUsingPath
Storyboard.TargetName="MyCircle"
Storyboard.TargetProperty="Center"
Duration="0:0:10"
RepeatBehavior="Forever">
<PointAnimationUsingPath.PathGeometry>
<PathGeometry
Figures="{Binding AnimationPath}"
PresentationOptions:Freeze="True" />
</PointAnimationUsingPath.PathGeometry>
</PointAnimationUsingPath>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Path.Triggers>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Grid>
</Grid>
my viewmodel source code
public class ParentViewModel : BindableBase
{
private List<MyCircle> myItemsModel;
public List<MyCircle> MyItemsModel
{
get { return myItemsModel; }
set
{
SetProperty(ref myItemsModel, value);
}
}
public ParentViewModel()
{
// instantiate MyItemsModel
}
}
MyCircle class
public class MyCircle
{
public double X { get; set; }
public double Y { get; set; }
public PathFigureCollection AnimationPath { get; set; }
public MyCircle(double x, double Y, PathFigureCollection path)
{
// do something
}
}
To go around the issue you should use DataTrigger rather then EventTrigger in ItemsControl.ItemTemplate.
The thing is, that Path.Triggers does accept only EventTrigger so you should move the part with BeginStoryboard to the DataTemplate.Triggers.
Now is the question - which data must trigger the storyboard? You could try to add some property to the viewmodel and set and access it somehow, or add an invisible e.g. CheckBox and act with it.
So remove Path.Triggers and put its content to the DataTemplate.Triggers
<DataTemplate.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="IsChecked" ElementName="ChbPlay"/>
</DataTrigger.Binding>
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="sb1">
<Storyboard>
<SomeAnimation/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="sb1"/>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
Now you must add an invisible CheckBox and control it with your buttons:
<CheckBox x:Name="ChbPlay" Visibility="Hidden"/>
<Button x:Name="PlayButton" Content="Play" Grid.Row="0" Grid.Column="1">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="ChbPlay" Storyboard.TargetProperty="IsChecked">
<BooleanAnimationUsingKeyFrames.KeyFrames>
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames.KeyFrames>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
<Button x:Name="StopButton" Content="Stop" Grid.Row="1" Grid.Column="1">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="ChbPlay" Storyboard.TargetProperty="IsChecked">
<BooleanAnimationUsingKeyFrames.KeyFrames>
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
</BooleanAnimationUsingKeyFrames.KeyFrames>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Button.Triggers>
</Button>
I have these border and label:
<Border x:Name="PulseBoba" Width="auto" Height="auto" Background="#FFF75959" CornerRadius="2" Margin="0" HorizontalAlignment="Left">
<Label Content="{Binding kolicina}" FontSize="20" DockPanel.Dock="Right" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold" Width="Auto" Margin="5,0">
<Label.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Label.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Label.Triggers>
</Label>
</Border>
In my vb.net code i have this piece of code which sets values correctly and displays in label:
...
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Public Property kolicina() As String
Get
Return m_kolicina
End Get
Set
m_kolicina = Value
NotifyPropertyChanged("kolicina")
End Set
End Property
...
How would i animate this border to blink or rotate or any other animation when value of "kolicina" is changed?
You could for example bind the Tag property of the Border and use an EventTrigger that listens to the Binding.SourceUpdated attached event:
<Border x:Name="PulseBoba" Width="auto" Height="auto" Background="#FFF75959" CornerRadius="2" Margin="0" HorizontalAlignment="Left"
Tag="{Binding kolicina, NotifyOnTargetUpdated=True}">
<Border.RenderTransform>
<RotateTransform Angle="0" />
</Border.RenderTransform>
<Border.Triggers>
<EventTrigger RoutedEvent="Binding.SourceUpdated">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(Border.RenderTransform).(RotateTransform.Angle)"
From="0"
To="360"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<Label Content="{Binding kolicina}" FontSize="20" DockPanel.Dock="Right" HorizontalAlignment="Center" VerticalAlignment="Center"
FontWeight="Bold" Width="Auto" Margin="5,0" />
</Border>
If you require more control, you should implement the animation programmtically. You could then for example handle the PropertyChanged event of the view model in the view and create a Storyboard yourself in the code-behind based on any condition. This is a typical example of a case where it makes perfect sense to implement view-related stuff in the view.
I have a ToggleButton that open a popup, and have a ItemsControl in Popup.
I want to hide popup when click on items in items control.
<ToggleButton Content="?????" x:Name="LeaveButton" Style="{StaticResource ToggleButtonImageStyle}" Padding="13"/>
<Popup
KeyDown="UIElement_OnKeyDown"
Opened="SubMenuPopup_OnOpened"
IsOpen="{Binding IsChecked, ElementName=LeaveButton}"
StaysOpen="False"
x:Name="LeavePopup"
AllowsTransparency="True"
PopupAnimation="Fade"
PlacementTarget="{Binding ElementName=LeaveButton}"
Placement="Right">
<StackPanel Orientation="Horizontal" Margin="15">
<Polygon Points="15 15,0 30,15 45" Fill="{DynamicResource HeaderBackgroundBrush}" />
<StackPanel Width="250">
<ItemsControl ItemsSource="{Binding WorkshopList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Name}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.LeaveCommand}"
CommandParameter="{Binding Id}"
Style="{StaticResource ButtonImageTextStyle}"
Padding="20">
<Button.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click">
<BeginStoryboard Storyboard="{StaticResource HideLeavePopup}" />
</EventTrigger>
</Button.Triggers>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Popup>
and set a story for this.
<Storyboard x:Key="HideLeavePopup" Storyboard.TargetName="LeaveButton" Storyboard.TargetProperty="IsOpen">
<BooleanAnimationUsingKeyFrames>
<DiscreteBooleanKeyFrame KeyTime="00:00:00.1" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
but when I use this, I get following error
LeaveButton name can not be found in the name scope of type 'System.Windows.Control.Button'
Did you try this?
Inside of your storyboard, Instead of
Storyboard.TargetName="LeaveButton"
use
Storyboard.Target="{Binding ElementName=LeaveButton}"
It depends where Storyboard is defined so I'll assume it's in Window.Resources or something alike. You use TargetName as LeaveButton and TargetProperty as IsOpen. You want either LeaveButton and IsChecked or LeavePopup and IsOpen. Also try changing TargetName to Target and use binding:
<Storyboard x:Key="HideLeavePopup"
Storyboard.Target="{Binding ElementName=LeaveButton}"
Storyboard.TargetProperty="IsChecked">
<BooleanAnimationUsingKeyFrames>
<DiscreteBooleanKeyFrame KeyTime="00:00:00.1" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
I am trying to have a polygon move from completely off the left of the screen, across the screen, and then completely off the right of the screen, then back again.
I've gotten this working. BUT, for some reason, as soon as the left margin becomes negative, the animation suddenly slows down. As soon as the left margin becomes positive, it speeds up again.
Why does this happen? How can I stop it?
Here's the complete code that demonstrates this:
<Window x:Class="Geometry.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>
<PathGeometry x:Key="MyGeometry">
<PathGeometry.Figures>
<PathFigure>
<PathFigure.Segments>
<LineSegment Point="0.30,0" />
<LineSegment Point="0.70,1" />
<LineSegment Point="0.40,1" />
<LineSegment Point="0,0" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
<Storyboard x:Key="MovingAnimation">
<ThicknessAnimationUsingKeyFrames RepeatBehavior="1:0:0" FillBehavior="HoldEnd" Storyboard.TargetName="_path" Storyboard.TargetProperty="Margin" >
<DiscreteThicknessKeyFrame KeyTime="0:0:0" Value="-2.0,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:10" Value="1.0,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:20" Value="-2.0,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard Storyboard="{StaticResource MovingAnimation}" ></BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label>Margin:</Label>
<TextBlock Text="{Binding ElementName=_path, Path=Margin.Left, StringFormat={}{0:0.#}}" />
</StackPanel>
<Canvas Name="_canvas" Grid.Row="1">
<Border Margin="0" Width="1" Height="1" VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.RenderTransform>
<ScaleTransform
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</Border.RenderTransform>
<Path
Name="_path"
Fill="#CCCCFF"
Data="{StaticResource MyGeometry}"
Width="1.0"
Height="1.0"
>
</Path>
</Border>
</Canvas>
</Grid>
</Window>
I don't have an explanation why the negative margin animates slowly, but I found a workaround.
Instead of animating the Margin of the Path I animated the X value of a TranslateTransform on the Border object that contains the path.
I had to put the TranslateTransform in front of the ScaleTransform so that the translation was applied before the scale. That allows you to use almost the same values in your animation that you used for the Margin.
<Storyboard x:Key="MovingAnimation">
<ThicknessAnimationUsingKeyFrames RepeatBehavior="1:0:0" Storyboard.TargetName="_blank" Storyboard.TargetProperty="Margin" >
<LinearThicknessKeyFrame KeyTime="0:0:0" Value="-1.5,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:10" Value="1,0,0,0" />
<LinearThicknessKeyFrame KeyTime="0:0:20" Value="-1.5,0,0,0" />
</ThicknessAnimationUsingKeyFrames>
</Storyboard>
The ugly part is that I couldn't find a quick way to apply the values in the key frames directly to the X property of the TranslateTransform, so I cheated and used element binding and a placeholder Canvas object.
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5">
<TextBlock Margin="0,0,5,0" Text="Margin.Left:"/>
<TextBlock Text="{Binding ElementName=_blank, Path=Margin.Left, StringFormat={}{0:0.#}}" />
</StackPanel>
<Canvas Name="_blank" /> <!--Placeholder object-->
<Canvas Name="_canvas" Grid.Row="1">
<Border Margin="0" Width="1" Height="1"
Name="_border"
VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform X="{Binding Margin.Left, ElementName=_blank}"/>
<ScaleTransform
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</TransformGroup>
</Border.RenderTransform>
Even thought the animation is still being applied to a Margin and then to a TranslateTransform through element binding, the delay for a negative margin is gone.
I suspect that the negative margin delay has something to do with the Path being in the Border that was being scaled, but that is conjecture on my part.
If you can find a way to bind the KeyFrame values directly to the X property of the TranslateTransform, that would make this workaround much less ugly.
EDIT:
Figured out the proper binding to use:
<Storyboard x:Key="MovingAnimation2">
<DoubleAnimationUsingKeyFrames RepeatBehavior="1:0:0" Storyboard.TargetName="tt" Storyboard.TargetProperty="X" >
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="-1.5" />
<LinearDoubleKeyFrame KeyTime="0:0:5" Value="1" />
<LinearDoubleKeyFrame KeyTime="0:0:10" Value="-1.5" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
This gets rid of the extra canvas placeholder object.
<Canvas Name="_canvas" Grid.Row="1">
<Border Margin="0" Width="1" Height="1"
Name="_border"
VerticalAlignment="Center" HorizontalAlignment="Center">
<Border.RenderTransform>
<TransformGroup>
<TranslateTransform x:Name="tt"/>
<ScaleTransform
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</TransformGroup>
</Border.RenderTransform>
Animating Margin property will trigger additional measure/arrange pass which in turn cause a bit more performance impact (though in this example it may not be noticeable). Animation of "render-only" properties on the other hand will not trigger layout re-arrangement and, thus, is more performance friendly.
Please, take a look at a bit easier way to do what, I suppose, you are want to get:
<Window x:Class="Geometry.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="518" Width="530">
<Window.Resources>
<PathGeometry x:Key="MyGeometry">
<PathGeometry.Figures>
<PathFigure>
<PathFigure.Segments>
<LineSegment Point="0.30,0" />
<LineSegment Point="0.70,1" />
<LineSegment Point="0.40,1" />
<LineSegment Point="0,0" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
<Storyboard x:Key="MovingAnimation">
<DoubleAnimationUsingKeyFrames RepeatBehavior="1:0:0" FillBehavior="HoldEnd" Storyboard.TargetName="_scaleTransform" Storyboard.TargetProperty="CenterX" >
<LinearDoubleKeyFrame KeyTime="0:0:0" Value="1.2" />
<LinearDoubleKeyFrame KeyTime="0:0:10" Value="-0.5" />
<LinearDoubleKeyFrame KeyTime="0:0:20" Value="1.2" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard Storyboard="{StaticResource MovingAnimation}" ></BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label>Margin:</Label>
<TextBlock Text="{Binding ElementName=_scaleTransform, Path=CenterX, StringFormat={}{0:0.#}}" VerticalAlignment="Center" />
</StackPanel>
<!--
<Border Grid.Row="1" Margin="150" BorderBrush="Red" BorderThickness="1">
-->
<Grid Name="_canvas" Grid.Row="1">
<Path Name="_path" Fill="#CCCCFF" Data="{StaticResource MyGeometry}"
Width="1.0"
Height="1.0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path.RenderTransform>
<ScaleTransform x:Name="_scaleTransform"
ScaleX="{Binding ElementName=_canvas, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvas, Path=ActualHeight}"
CenterX="1.2"
CenterY="0.5">
</ScaleTransform>
</Path.RenderTransform>
</Path>
</Grid>
<!--
</Border>
-->
</Grid>
</Window>
From the two answers so far, it's clear that if you want to have a shape fly around the screen, don't animate the margins.
Stewbob solved the problem by animating the X value of a translate transform.
sevenate solved the problem by animating the Center X value of a scale transform.
Another solution is, instead of wrapping the polygon in a border and animating the margin, wrap it up in a canvas and animate the left value.
Wrapping it in a canvas:
<Canvas Name="_canvasFrame" Grid.Row="1">
<Canvas Margin="0" Width="1" Height="1">
<Canvas.RenderTransform>
<ScaleTransform
ScaleX="{Binding ElementName=_canvasFrame, Path=ActualWidth}"
ScaleY="{Binding ElementName=_canvasFrame, Path=ActualHeight}"
CenterX="0"
CenterY="0">
</ScaleTransform>
</Canvas.RenderTransform>
<Path
Name="_path"
Fill="#CCCCFF"
Data="{StaticResource MyGeometry}"
Width="1.0" Height="1.0"
>
</Path>
</Canvas>
</Canvas>
Then animating the left value:
<Storyboard x:Key="MovingAnimation">
<DoubleAnimationUsingKeyFrames
RepeatBehavior="1:0:0"
FillBehavior="HoldEnd"
Storyboard.TargetName="_path"
Storyboard.TargetProperty="(Canvas.Left)" >
<DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="-1.0" />
<LinearDoubleKeyFrame KeyTime="0:0:10" Value="1.0" />
<LinearDoubleKeyFrame KeyTime="0:0:20" Value="-1.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
I have a Popup that contains a "close" button. The popup is opened by a toggle button (its IsOpen property is bound to a ToggleButton as provided by this answer). How can I close the popup when the button is pressed? This is my XAML:
<Canvas x:Name="LayoutRoot">
<ToggleButton x:Name="ToggleButton"
Style="{DynamicResource ToggleButtonStyle}" Height="51" Canvas.Left="2.999" Width="50.333" IsHitTestVisible="{Binding ElementName=Popup, Path=IsOpen, Mode=OneWay, Converter={StaticResource BoolInverter}}"/>
<Popup x:Name="Popup" IsOpen="{Binding IsChecked, ElementName=ToggleButton}" StaysOpen="False" AllowsTransparency="True">
<Canvas Height="550" Width="550">
<Grid Height="500" Width="500" Canvas.Left="25" Canvas.Top="25" d:LayoutOverrides="Width, Height, Margin">
<Grid.Effect>
<DropShadowEffect BlurRadius="15" ShadowDepth="0"/>
</Grid.Effect>
<Grid.RowDefinitions>
<RowDefinition Height="0.132*"/>
<RowDefinition Height="0.868*"/>
</Grid.RowDefinitions>
<Rectangle x:Name="Background" Fill="#FFF4F4F5" Margin="0" Stroke="Black" RadiusX="6" RadiusY="6" Grid.RowSpan="2"/>
<Border x:Name="TitleBar" BorderThickness="1" Height="70" VerticalAlignment="Top" Margin="0,0.5,0,0" CornerRadius="5">
<DockPanel>
<TextBlock TextWrapping="Wrap" Text="FOOBAR POPUP TITLE" FontSize="24" FontFamily="Arial Narrow" Margin="17,0,0,0" d:LayoutOverrides="Height" VerticalAlignment="Center" FontWeight="Bold"/>
<Button x:Name="CloseButton" Content="Button" VerticalAlignment="Center" DockPanel.Dock="Right" HorizontalAlignment="Right" Margin="0,0,13,0" Style="{DynamicResource CloseButtonStyle}"/>
</DockPanel>
</Border>
<Border BorderThickness="1" Height="413" Grid.Row="1" Background="#FF2F2F2F" Margin="12">
<Rectangle Fill="#FFF4F4F5" RadiusY="6" RadiusX="6" Stroke="Black" Margin="12"/>
</Border>
</Grid>
</Canvas>
</Popup>
</Canvas>
A better approach than code behind is to use an event trigger on the button click event:
<Button>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsChecked" Storyboard.TargetName="ToggleButton">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="False" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Disclaimer: I haven't run this code through VS so it might have a typo or 2
Other answers didn't work for me, because I was using a DataTemplate for the buttons inside the popup. After lot's of searching I found that I should use Storyboard.Target instead of Storyboard.TargetName. Otherwise the x:Name was not found and there was some namespace exception.
<ToggleButton x:Name="MyToggleButtonName" Content="{Binding MyToggleButtonString}"/>
And later inside the Popup that has a ListBox which is populated from some ItemsSource:
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name, Mode=OneWay}"
Command="{StaticResource MyCommandThatTakesAParameter}"
CommandParameter="{Binding Name}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsChecked" Storyboard.Target="{Binding ElementName=MyToggleButtonName}">
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
This way it is possible to get a somewhat working ComboBox which can execute commands with the buttons inside it. (A normal ComboBox can't launch commands for some odd reason.)
One way of doing it is to add event handler for your CloseButton:
<Button x:Name="CloseButton" Click="OnButtonClick" Content="Button" VerticalAlignment="Center" DockPanel.Dock="Right" HorizontalAlignment="Right" Margin="0,0,13,0" Style="{DynamicResource CloseButtonStyle}"/>
And in OnButtonClick event handler set state of your
TuggleButton.IsChecked = false;
I have't tested code in VS, so there might be some typos