Create custom VisualState in xaml and manually set it in CodeBehind - wpf

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.

Related

Change Visual State and use transitions or not based on Dependency Property

I have one control, that I sometimes want to animate a state transition and sometimes I don't.
At the moment I have something like so:
<Grid>
<Interactivity:Interaction.Behaviors>
<ic:DataStateBehavior Binding="{Binding Direction}" Value="Up" TrueState="Up_Direction" />
</Interactivity:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="AnimatedStates">
<VisualStateGroup.Transitions>
<VisualTransition x:Name="transition" GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<ElasticEase .../>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Up_Direction" >
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
Storyboard.TargetName="pathArrow" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path x:Name="pathArrow" ...>
<Path.RenderTransform>
<RotateTransform Angle="90" />
</Path.RenderTransform>
</Path>
</Grid>
This works great, but I want to have a UseTransitions dependency property that controls whether that state transitions are animated or not.
I've tried a few things like setting the GeneratedDuration to 0 on the UseTransitions property changed handler, but it seems to get set after the state has already changed, so has no effect.
I also tried the GoToStateAction with a DataTrigger but they only seem to get triggered on a change and the initial value of the DataContext doesn't set the state correctly.
I've thought about having a ValueConverter on the DataStateBehaviour that evaluates the UseTransition property and triggers different states, but that seems like a really ugly solution.
Anyone have an elegant solution?

DataTrigger is not using GoToStateAction in Silverlight

The follow XAML represents an object I am trying to build in Expression Blend. I am having trouble with the DataTrigger in the StackPanel - the application does not go to Empty when the trigger matches the data. Further explanation is after this code:
<DataTemplate x:Key="SampleTemplate">
<StackPanel x:Name="SampleStack" Style="{StaticResource DefaultSampleStyle}" Width="64" Height="60">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0">
<Storyboard>
<ColorAnimation Duration="0" To="#FFDFE04B" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="SampleStack" d:IsOptimized="True"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Empty">
<Storyboard>
<ColorAnimation Duration="0" To="#FF4B6FE0" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="SampleStack" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ei:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding IsActive}" Value="False">
<ei:GoToStateAction StateName="Empty" UseTransitions="False"/>
</ei:DataTrigger>
</i:Interaction.Triggers>
<TextBlock x:Name="StartOn" Text="{Binding StartOn, StringFormat=hh:mm}"/><TextBlock x:Name="textBlock" Text="-" />
<TextBlock x:Name="EndOn" Text="{Binding EndOn, StringFormat=hh:mm}"/>
</StackPanel>
</DataTemplate>
If I use an EventTrigger with a Loaded value, the Empty state is correctly applied based on the IsActive binding.
If I use the existing DataTrigger and change a Property on the Stackpanel, such as Height, based on the binding of IsActive this also works.
Am I doing something fundamentally wrong in the XAML? Do you need a more complete example of the XAML to understand the issue?
do you need the GoToStateAction?
I guess, the problem is the Binding "at startup". I added a dispatcher and threw the NotifyPropertyChanged again after one second. Then it works. Propably you can workaround it like this. You wait till the control is loaded and then throw the PropertyChanged again. This is not a nice way and similar to your idea (If I use an EventTrigger with a Loaded value,...)
I would recommend you to use a DataStateBehaviour. If you hav a boolean to decide in which satte you have to go, this is great. It is a behaviour where you can bind the condition to a property and then set a true and a false state.
It would look like this (I did a few adjustments just for testing at my computer):
<DataTemplate x:Key="SampleTemplate">
<StackPanel x:Name="SampleStack" Width="64" Height="60" Background="White">
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsChecked}" Value="True" TrueState="Empty" FalseState="Base"/>
</i:Interaction.Behaviors>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Empty">
<Storyboard>
<ColorAnimation Duration="0" To="Red" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="SampleStack" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Base"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ei:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<TextBlock x:Name="StartOn" Text="Test"/>
</StackPanel>
</DataTemplate>
As you can see I added a second state to the VisualStateGroup (There is now empty and base). I would recommend this not only because the DataStateBehaviour needs at least two states in one group. If you have only one state, you have no chance to change the state of this group back to normal, e.g.
I hope this answer helps you.
BR,
TJ

VisualStateManager does nothing (silverlight)

I am building a custom control using studio 2010 and silverlight 4.
I am trying to use the visual state manager.
With the following xml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SilverView">
<Style TargetType="controls:ScaleImage">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ScaleImage">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition To="MouseOver"
GeneratedDuration="0:0:.5"/>
<VisualTransition To="Normal"
GeneratedDuration="0:0:.5"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="img"
Storyboard.TargetProperty="Width"
From="50" To="100"/>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="img"
Storyboard.TargetProperty="Width"
From="50" To="100"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Image Name="img" Width="50">
<Image.RenderTransform>
<ScaleTransform x:Name="scale"/>
</Image.RenderTransform>
</Image>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Nothing happens when I mouse over the image.
How do I get the image to enlarge when the mouse is over it?
Thanks
The VisualStateManager.VisualStateGroups attached property defines the set of visual states however the names of the groups and the names of the states are just names, they do not actually enable the functionality they describe automatically.
It's up to code in your control to decide when it is in a specific state and then inform the VisualStateManager of that choice. You do that with code like this:-
VisualStateManager.GotoState(this, "MouseOver", true);
Typically you would collect information like whether the mouse is over the control via the various control events and have a central UpdateVisualState function that sets all the appropriate states.
In the XAML above you are only defining state groups and states with names like "MouseOver". You are not actually causing the state to change, as they are apparently not connected to any events.
If you are not already, try using GoToState behaviours to trigger the state changes of your control.
Do you have any more code or XML that triggers a state change?

Animating same property from mutually exclusive VisualStateGroups

My question is simply: is it even possible?
Suppose I want to style a ListBoxItem such that it has a black foreground by default, blue when selected, and red when the mouse is over it. I ended up with something like this:
<!-- assume the default foreground color is black -->
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0.2" To="Red" Storyboard.TargetName="contentControl" Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Unselected"/>
<VisualState x:Name="Selected">
<Storyboard>
<ColorAnimation Duration="0:0:0.2" To="Blue" Storyboard.TargetName="contentControl" Storyboard.TargetProperty="(Control.Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentControl x:Name="contentControl" Foreground="{TemplateBinding Foreground}" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
The problem is that the ListBoxItem class has correctly placed selection states in their own visual state group, separate to common states such as mouse over. That means that a ListBoxItem can be in both the selected and mouse over state.
If the ListBoxItem is selected and correctly displayed in blue, mousing over it will revert it to black because it transitions back to the normal state.
Is there any way for me to handle this without resorting to subclassing ListBoxItem and adding my own custom states? Everything I've read suggests that it is not possible, but it seems ridiculously limiting to me. What am I missing?
You basically asking for Foreground to be black AND blue at the same time. Now that is just impossible. This conflict could be resolved if individual states had precedence, like MouseOver > Selected > Normal > Unselected. But it would introduce unnecessary complication to already complicated visual state manager. Typically this situation is resolved by adding new element and animating that element's properties in one of the conflicting state groups.
Custom styled listbox - how can I keep the style for a selected item?

Silverlight Button Style gets stuck on fullscreen

So I have a number of buttons in Silverlight that I've made from images. In each case the button has a regular image and a hover image. I've used Blend to have the hover image fade in over .15 seconds on hover.
The problem with this is that I can't figure out how to access the images anymore, since they are embedded in the style. So, I have a separate style for each button, instead of a single UserControl with two interchangable images.
Also I have a set of two buttons: FullScreen and ExitFullScreen. The hover image gets stuck in this case:
Press fullscreen. The exit fullscreen button is now in a different place.
Press exit fullscreen. The fullscreen button is back in the original place. The hover animation is displayed, even though the mouse is not over the button.
Code:-
<Style x:Key="ExitFullScreenButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="00:00:00.1500000" To="MouseOver"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Image Source="Images/ControlBar/exitFullScreenButton.png"/>
<Image x:Name="image" Opacity="0" Source="Images/ControlBar/exitFullScreenButtonHover.png"/>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Content="" ToolTipService.ToolTip="Full Screen"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button x:Name="ExitFullScreenButton" Click="ExitFullScreenButton_Click" Canvas.Top="14"
Style="{StaticResource ExitFullScreenButton}"
Width="32" Content="Button" Visibility="Collapsed"/>
I ended up just making a user control and doing the animation in code.
private Storyboard hoverAnimation = new Storyboard();
private void CreateAnimation()
{
SizeChanged += OnSizeChanged;
Duration duration = new Duration(TimeSpan.FromMilliseconds(150));
hoverAnimation.Duration = duration;
DoubleAnimation animation = new DoubleAnimation();
animation.Duration = duration;
hoverAnimation.Children.Add(animation);
Storyboard.SetTarget(animation, HoverIcon);
Storyboard.SetTargetProperty(animation, new PropertyPath(Image.OpacityProperty));
animation.To = 1;
}

Resources