VisualStateManager ignores RepeatBehavior="Forever" - wpf

I have a very simple VisualStateGroup that should start gradually (cubic ease) spinning animation in the Spinning state and stop it as soon as it exits the state. However the animation stops after the first round as if RepeatBehavior="Forever" was completely ignored by VisualStateManager:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="SpinningStates">
<VisualState Name="Spinning">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
Storyboard.TargetName="SpinnerIcon"
To="360" Duration="0:0:1" RepeatBehavior="Forever" />
</Storyboard>
</VisualState>
<VisualState Name="NotSpinning"/>
<VisualStateGroup.Transitions>
<VisualTransition To="Spinning" GeneratedDuration="0:0:1.0">
<VisualTransition.GeneratedEasingFunction>
<CubicEase/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Is not RepeatBehavior="Forever" supported by VSM transitions? Is there a way to fix the problem?
Infrastructure code for example above:
<UserControl x:Class="Controls.Spinner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<VisualStateManager.VisualStateGroups/> <!--from above-->
<TextBlock x:Name="SpinnerIcon" Text="X" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform x:Name="preventFreeze" />
</TextBlock.RenderTransform>
</TextBlock>
<UserControl/>
code behind:
public static readonly DependencyProperty SpinProperty =
DependencyProperty.Register("Spin", typeof(bool), typeof(Spinner), new PropertyMetadata(true, OnSpinChanged));
private static void OnSpinChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Spinner spinner && e.NewValue is bool shouldSping)
{
VisualStateManager.GoToElementState(spinner, shouldSping ? "Spinning" : "NotSpinning", true);
}
}
punlic bool Spin
{
get { return (bool)GetValue(SpinProperty); }
set { SetValue(SpinProperty, value); }
}

Related

I can not dynamically GoToState in WPF. What is wrong?

I created the simplest WPF application with Blend for Visual Studio.
I created the simplest visual state groups for a single textbox:
My problem is that I can not dynamically GoToState using this code:
public MainWindow()
{
InitializeComponent();
textBox.MouseEnter+=new System.Windows.Input.MouseEventHandler(textBox_MouseEnter);
}
private void textBox_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
// This will be an empty list
var states = VisualStateManager.GetVisualStateGroups(textBox);
// This will be false
bool thisReturnsFalse = VisualStateManager.GoToState(textBox, "VisualState1", true);
}
This is my XAML:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="VisualState">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="textBox">
<EasingColorKeyFrame KeyTime="0" Value="#FFE40404"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="VisualState1">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="textBox">
<EasingColorKeyFrame KeyTime="0" Value="#FF23FF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<ei:GoToStateAction StateName="VisualState1"/>
</i:EventTrigger>
<i:EventTrigger EventName="KeyDown">
<ei:GoToStateAction StateName="VisualState"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Your VisualStateGroups are not defined in the TextBox, so the call
var states = VisualStateManager.GetVisualStateGroups(textBox);
won't return what you expect. If you want to get the defined states, pass the FrameworkElement that contains the VisualStateGroups (I'm assuming the Window here):
var states = VisualStateManager.GetVisualStateGroups(this);
The same should probably be used for the call to VisualStateManager.GoToState:
bool shouldReturnTrue = VisualStateManager.GoToState(this, "VisualState1", true);

Create custom VisualState in xaml and manually set it in CodeBehind

I have a TabItem style, which has VisualStates.
<VisualState x:Name="MouseOver">
<!-- Tab turns bronze when mouseover -->
</VisualState>
Now I want to have a custom visual state and manually set the state in codebehind instead of relying on the MouseOver event.
<VisualState x:Name="CustomVisualState">
<!-- this will be a storyboard to cause flashing -->
</VisualState>
Then I need to set it in CodeBehind.
MyTabItem.VisualState = CustomVisualState. //something like this
Have you tried
VisualStateManager.GoToState
Takes a Control, string with the custom state name and a bool flag for using transitions.
Example Usage From msdn
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
A slightly more complicated example usage from here
Given this xaml
<Grid x:Name="LayoutRoot" Background="LightBlue">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SG1">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="00:00:01">
<VisualTransition.GeneratedEasingFunction>
<ElasticEase EasingMode="EaseOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="SG1Normal"/>
<VisualState x:Name="SG1EllipseRight" >
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="320"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Ellipse x:Name="ellipse" Fill="Red" Stroke="Black"
Height="116" HorizontalAlignment="Left" Margin="50,98,0,0"
VerticalAlignment="Top" Width="235" RenderTransformOrigin="0.5,0.5" >
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
Changing state can be done like so.
VisualStateManager.GoToState(this, SG1EllipseRight.Name, true);
Or alternatively
VisualStateManager.GoToState(control, "SG1EllipseRight", true);
Try this,
VisualStateManager.GoToElementState(Control, "StateName", true/false);
or
VisualStateManager.GoToState(Control, "StateName", true/false);
The VisualStateManager also enables you to specify when a control
enters a specific state. The method that you should call to change
states depends on your scenario. If you create a control that uses the
VisualStateManager in its ControlTemplate, call the GoToState method.
For more information about how to create controls that use the
VisualStateManager, see Creating a Control That Has a Customizable
Appearance. If you use the VisualStateManager outside of a
ControlTemplate (for example, if you use a VisualStateManager in a
UserControl or in a single element), call the GoToElementState method.
In either case, the VisualStateManager performs the logic that is
required to appropriately start and stop the storyboards that are
associated with the involved state. - VisualStateManager Class
That is how different between GoToElementState and GoToState.

Silverlight Layout states beforeloaded -> afterloaded transition just not working

I am trying to add a initial animation when items are added to a list box.
For this I'm using Layoutstates, generated by blend inside the itemcontainerstyle:
<VisualStateGroup x:Name="LayoutStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="AfterLoaded"/>
<VisualState x:Name="BeforeLoaded">
<Storyboard>
<DoubleAnimation Duration="0" To="35" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="BeforeUnloaded">
<Storyboard>
<DoubleAnimation Duration="0" To="0.85" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0.85" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="grid" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
my listbox looks like this:
<ListBox Grid.Row="1" ItemsSource="{Binding Days}" x:Name="Days"
HorizontalAlignment="Stretch"
SelectedItem="{Binding CurrentDay, Mode=TwoWay}"
ItemTemplate="{StaticResource TimeRecordByDayItemTemplate}"
ItemsPanel="{StaticResource ByMonthDaysItemsPanelTemplate}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemContainerStyle="{StaticResource DayListBoxItemStyle}" />
I just don't get any animation even though i was just following the channel9 tutorials step by step!
This is the first problem with my state manager, I am also encountering problems with datatriggers, that should go to a state when certain conditions are met, where some do work and some do not, but all of my bindings are correct!
Also all the animations do work in Expression Blend preview.
I can't make out the problem, I've had this so often with silverlight and animations copied from the simplest samples not working in the own environment (look here at channel 9)...
Thanks for you help!
Sounds like you need to add the items one by one once everything is loaded. Seems like a simple solution would be this in your view model:
public class MyViewModel
{
private string[] _items;
private ObservableCollection<string> _itemCollection;
public MyViewModel()
{
// meets requirement of loading items in constructor
_items = { "Johnny", "Thommy", "Jay", "Wommy" };
}
public ObservableCollection<string> Items
{
get
{
if (_itemCollection == null)
{
_itemCollection = new ObservableCollection<string>();
Dispatcher.Invoke(() => LoadItems());
}
return _itemCollection;
}
}
private void LoadItems()
{
foreach (var item in _items)
{
ItemCollection.Add(item);
}
}
}
Basically when the ListBox sets up the binding you queue up the adding of the items to the collection. This should have the items loaded at the right time so that they trigger your animations.

Accessing a style's storyboard of a button

My problem is that I have a Button and I want to access the Storyboard, which is a part of the assigned style.
<Button x:Name="pictureFolderButton" Content="Pictures" Style="{StaticResource ImageTileButtonStyle}" Click="pictureFolderButton_Click" />
The style is very comprehensive so I'll post only a part of it:
<Style x:Key="ImageTileButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Storyboard x:Key="OnLoaded1"/>
</ControlTemplate.Resources>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
...
</VisualStateGroup>
<VisualStateGroup x:Name="AnimationStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<CircleEase EasingMode="EaseOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="ExpandedFull">
<Storyboard x:Name="expandStoryBoard" >
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="border1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="47"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="47"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter RecognizesAccessKey="True" VerticalAlignment="Stretch" Margin="0,47,0,0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I just want to get notified when the "ExpandedFull" animation has ended. Therefore, I thought I have to get the "expandStoryBoard" programmatically and add a Completed event handler.
The only thing I got working is to access the button's style at runtime:
Style style = pictureFolderButton.FindResource("ImageTileButtonStyle") as Style;
How do I have to proceed?
Thank you very much!
In theory you should be able to go down the visual and logical tree of your button to get to the storyboard, but that is rather tedious, if you name the Grid in the template "grid", something like the following might work:
Grid grid = pictureFolderButton.FindName("grid") as Grid;
IList groups = VisualStateManager.GetVisualStateGroups(grid);
VisualStateGroup targetGroup = null;
foreach (var group in groups)
{
if (group is VisualStateGroup && (group as VisualStateGroup).Name == "AnimationStates")
{
targetGroup = group as VisualStateGroup;
break;
}
}
if (targetGroup != null)
{
IList states = targetGroup.States;
VisualState targetState = null;
foreach (var state in states)
{
if (state is VisualState && (state as VisualState).Name == "ExpandedFull")
{
targetState = state as VisualState;
break;
}
}
if (targetState != null)
{
targetState.Storyboard.Completed += new EventHandler(Expansion_Completed);
}
else throw new Exception("VisualState not found.");
}
else throw new Exception("VisualStateGroup not found.");
Another way that comes to mind is extracting your storyboard to a resource, but i am not sure if this will have any side effects, i.e.:
<ControlTemplate.Resources>
...
<Storyboard x:Key="expandStoryBoard" x:Name="expandStoryBoard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="border1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="47"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="47"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
...
<VisualState x:Name="ExpandedFull" Storyboard="{StaticResource expandStoryBoard}"/>
Then you should be able to use FindResource on the button to get the storyboard.
Hopefully some of that works or at least helps a bit.
Just try with this with StoryBoard name "OnLoaded1":
<Button Height="75" Width="120" Style="{StaticResource ImageTileButtonStyle}" Click="Button_Click" >Hello</Button>
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn=(Button)sender;
Storyboard stb = btn.TryFindResource("OnLoaded1") as Storyboard;
}
If you add the Storyboard to the resources, you can set the event handler for the Timeline.Completed event in the XAML file and implement the handler in the corresponding class.
Define the Storyboard in the Resources section of your control like this:
<UserControl.Resources>
<Storyboard x:Key="expandStoryBoard" Completed="OnExpandCompleted"> ... </Storyboard>
...
</UserControl.Resources>
Reference the Storyboard as a static resource:
<VisualState x:Name="ExpandedFull" Storyboard="{StaticResource expandStoryBoard}" />
Implement the Completed event handler in the corresponding class:
void OnExpandCompleted(object sender, EventArgs e)
{
...
}

Add image silverlight hovereffect from codebehind

I have a stackpanel that has a dynamic amount of images that are added programatically, Is there a way I can set a hover/click effect programatically on these images. I would like for the image to "glow" when clicked. How do i do that in silverlight? I've noticed the Image.Effect property, but I'm not really sure how to use it.
What you need to do is create a new usercontrol with the image control inside with the visualstates attached to it. This way, you can dynamically add the usercontrol to the stackpanel and have the animations being called without attaching them by events from your main app.
I used the DropShadowEffect on the Image.Effect to create a pulsating animation.
For eg. This is inside your usercontrol:
XAML
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ImageState">
<VisualState x:Name="NormalImageState">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.BlurRadius)" Storyboard.TargetName="image1" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="GlowingImageState">
<Storyboard AutoReverse="True">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect).(DropShadowEffect.BlurRadius)" Storyboard.TargetName="image1">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="20"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Image Name="image1" MouseEnter="image1_MouseEnter" MouseLeave="image1_MouseLeave" >
<Image.Effect>
<DropShadowEffect Color="Red" BlurRadius="0" ShadowDepth="0"/>
</Image.Effect>
</Image>
C#
public ImageSource ImageSource
{
get;
set
{
image1.Source = value;
}
}
private void image1_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToState(this, "GlowingImageState", true);
}
private void image1_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
VisualStateManager.GoToState(this, "NormalImageState", true);
}
Then you can add this usercontrol to your stack panel like so:
MyUC uc= new MyUC(); //control we just created
uc.ImageSource = sourceOfImg; //the source of the intended image
myStack.Children.Add(uc); //adding it to the stackpanel.
Tell me if this works.
You can use a transformation to create an animation to change the colour of your image when it is clicked.
Have a look at the MSDN page: Animation Overview. This page includes details on how to do this programmatically (Creating an Animation in Procedural Code).

Resources