This question is a follow-up to my previous question as well as this related question about how VisualStates work in WPF.
Currently, my understanding is that animating the same property within different VisualStateGroups can cause problems (see the linked questions).
To resolve these problems, it requires loop-holes to be taken advantage of. (Perhaps loop-hole isn't the correct term, but it appears that the solution isn't what the WPF designers intended.)
I'm wondering what is the correct way to animate the same property in multiple VisualStateGroups without causing adverse side-effects. If it is not possible, what is the correct route for achieving the same visual behavior for a control?
I was able to find some related documentation at MSDN:
The control is always in exactly one state per group. For example, a Button can have focus even when the mouse pointer is not over it, so a Button in the Focused state can be in the MouseOver, Pressed, or Normal state.
This leads me to a second question...
How can you provide a visual behavior that should only occur when two specific VisualStates are active?
Take for example a ToggleButton:
If the button is Checked, I would like to display Behavior 1.
If the button is Disabled, I would like to display Behavior 2.
Finally, if the button is Checked and Disabled, I would like to display Behavior 3.
In the above example, how would you go about rendering the third visual behavior?
For the first part of your question, you'll want each state to interact with a different individual object for each instead of hitting the same one with each VisualState, as example;
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DisabledState"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Checked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="CheckedState"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- Each state interacts with its own object in your ControlTemplate ideally -->
<Border x:Name="CheckedState" Visibility="Collapsed"
Background="Green"/>
<Border x:Name="DisabledState" Visibility="Collapsed"
Background="White" Opacity=".5"/>
Instead of one object sharing a property change like;
<VisualState x:Name="Disabled">
<Storyboard>
<ColorAnimation d:IsOptimized="True"
Duration="0"
Storyboard.TargetName="Background"
Storyboard.TargetProperty="(SolidColorBrush.Color)"
To="White" />
</Storyboard>
</VisualState>
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimation d:IsOptimized="True"
Duration="0"
Storyboard.TargetName="Background"
Storyboard.TargetProperty="(SolidColorBrush.Color)"
To="Green" />
</Storyboard>
</VisualState>
<Border x:Name="Background" Background="Blue"/>
As per your second question, A VisualState is going to act as a bool in that it either is, or it isn't in that state. To share a declaration of a state you'd have to add a little more finesse with a MultiTrigger or a converter somewhere or something.
Hope this helps. Cheers
EDIT ADDITION:
So you also have available to you VisualTransition you could employ like;
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualStateGroup.Transitions>
<VisualTransition From="Normal"
GeneratedDuration="0:0:0.2"
To="Checked">
<VisualTransition.GeneratedEasingFunction>
<ExponentialEase EasingMode="EaseIn" Exponent="7" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
<VisualTransition From="Checked"
GeneratedDuration="0:0:0.2"
To="Normal">
<VisualTransition.GeneratedEasingFunction>
<CircleEase EasingMode="EaseIn" />
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal" />
<!-- etc, etc, etc -->
So you can play with your different eases, your generated duration times, etc.
Related
I'm a beginner working a C# Windows 8 app. I have a RichEditBox in the center of the screen whose width changes depending on the window size. I can set the width of this RichEditBox to Auto by editing its own properties, but I want to set the width to Auto when the width of the window falls below a certain point. I'm using VisualStates to define the various screen options. The problem is that when I set the value to Auto, the app will crash when it tries to invoke the new VisualState.
My code is as follows:
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FlexibleViewState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Editor" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="Auto"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
I don't know why it's doing this. I can do the following without any problems:
<RichEditBox x:Name="Editor" Width="Auto"/>
But when I try to set the width to Auto with a VisualState it crashes. Is there any way to fix this or to work around this problem?
The first thing to realize, is that the width Auto is represented as a NaN value. see MSDN for documentation on FrameworkElement.Width
With this in mind, you can set your (windows 8.1) style like this:
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FlexibleViewState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Editor" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<x:Double>NaN</x:Double>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
If you are using a different flavour of XAML, you may need to use sys:Double instead of x:Double
I'm trying to achieve a nice animation while switching tabs in the TabControl.
At this point, my style animation xaml looks like this:
<EventTrigger RoutedEvent="SelectionChanged">
<BeginStoryboard x:Name="selectionChangedBeginStoryboard">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="borderScale"
Storyboard.TargetProperty="ScaleX">
<DoubleKeyFrameCollection>
<EasingDoubleKeyFrame Value="0" KeyTime="0:0:0.2"/>
<EasingDoubleKeyFrame Value="1" KeyTime="0:0:0.4"/>
</DoubleKeyFrameCollection>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
What I want to achieve is a rotating effect on the tab transmition. So it would look like the screen is turning away, and returns with the new tab page.
The problem is that when I switch to another tab,
The content is switched right away, and the animation is just rotating the new tab page.
Any Ideas, please? :)
Thank you!
I would recommend that you use a transition library, such as 'Transitionals'. You can download this library from the Transitionals page on CodePlex.
The reason why I say this is because in order to do what you want to do, you will need to capture the Visual of the old TabItem before you switch tabs, animate that instead of the TabItem and then remove that and restore the actual controls.
However, the aforementioned library already does this and provides a number of different transitions for you to use. You can get help with using the library by downloading the 'TransitionalsHelp_1_0.zip' file from the following link:
http://transitionals.codeplex.com/releases/view/12954
Instead of using third party programs, i recommend Blend.
Open your Solution there and work with the VisualStateManager. I did a transitional effect from Unselected to Selected in less than 30 seconds. It was simple (Opacity change), but Blend is very user friendly and you can integrate with Visual Studio natively.
Here's what it generated to be (not what you are asking):
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates"/>
<VisualStateGroup x:Name="SelectionStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.3"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Unselected">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="templateRoot">
<EasingDoubleKeyFrame KeyTime="0" Value="0.8"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Selected"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Good luck.
I want to write a custom control and want it to have different Padding if the page is Portrait or Snapped. I noticed that pages inherit from LayoutAwarePage which creates support for the following view states:
FullScreenLandscape
Filled
FullScreenPortrait
Snapped
Do I need to add similar code to my new control (It inherits from Control). If not, why does LayoutAwarePage have to do this? Also, Can I just stick the following VisualStateManager into the ControlTemplate of my control and get it to respect the page layout or is this too easy.
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0" Value="1,2,3,4"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0" Value="5,6,7,8"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
EDIT : It looks like controls do not support these states by default and they have to be added. It also appears that ButtonBase does support these states because it uses them in its style.
If you look on the default "details" page within the windows 8 projects you'll find that it expects you to subscribe to the events on the page. You'll find the following within the flipview's ItemTemplate:
<FlipView.ItemTemplate>
<UserControl Loaded="StartLayoutUpdates" Unloaded="StopLayoutUpdates">
<ScrollViewer x:Name="scrollViewer" Style="{StaticResource HorizontalScrollViewerStyle}"
<!-- "Child Controls Here" -->
<VisualStateManager.VisualStateGroups>
<!-- "Visual states manuiplating the child controls above" -->
</VisualStateManager.VisualStateGroups>
</ScrollViewer>
</UserControl>
</FlipView.ItemTemplate>
What I do with my user controls is to have them inherit LayoutAwarePage. Next place the content in a grid and move the VisualStateGroup inside this grid. Hope this helps, it works for me.
I have four visual states defined each affecting different child controls within the same silver-light control.
Is it possible for me to create other visual states which invoke a combination of these others?
So if I have Visual_Group_1, Visual_Group_2, Visual_Group_3, Visual_Group_4
Is it possible to make, say a
Visual_Comb_1 group which uses the
states in Visual_Group_1 and
Visual_Group_3?
Then make another one called Visual_Comb_2 which uses Visual_Group_4 and Visual_Group_3?
I'm happy to implement a solution in xaml or codebehind or a combination of both.
The alternative I'm looking at currently involves tonnes of code copy+paste and I'm not too keen to do that.
Some more detail per request:
This is what I roughly have right now:
<VisualState x:Name="State1">
<ColorAnimation Storyboard.TargetName="Path1"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="Blue" Duration="0:0:0.5" />
// fade out the rest of the paths...
<ColorAnimation Storyboard.TargetName="Path2"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
<ColorAnimation Storyboard.TargetName="Path3"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
<ColorAnimation Storyboard.TargetName="Path4"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
</VisualState>
<VisualState x:Name="State2">
<ColorAnimation Storyboard.TargetName="Path3"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="Red" Duration="0:0:0.5" />
// fade out the rest of the paths...
<ColorAnimation Storyboard.TargetName="Path2"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
<ColorAnimation Storyboard.TargetName="Path1"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
<ColorAnimation Storyboard.TargetName="Path4"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
</VisualState>
<VisualState x:Name="State3">
<ColorAnimation Storyboard.TargetName="Path4"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="Pink" Duration="0:0:0.5" />
// fade out the rest of the paths...
<ColorAnimation Storyboard.TargetName="Path2"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
<ColorAnimation Storyboard.TargetName="Path1"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
<ColorAnimation Storyboard.TargetName="Path3"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000" Duration="0:0:0.5" />
</VisualState>
My objective is to have a control which when you click on cycles from state1 to state3, each state fades in a different path while fading out the other paths. My problem is that there is a tonne of copy+paste in the 'fade out the rest of the paths' section, so if I wanted to add a Path5 it would mean adding it to every single visual state already defined, or if I wanted to change the fadeoff colour or animation I would have to do it to every visual state.
Thanks for providing the XAML. This is how I would tackle the problem.
First off, create the VisualStates individually for each Path. (I would recommend using a Style instead to save you re-coding a very similar VisualState into each Path, but I'm not familiar enough with them to know if you can apply different colours to each.)
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Path1States">
<VisualState x:Name="Activate">
<Storyboard>
<ColorAnimation Storyboard.TargetName="Path1"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="Blue"
Duration="0:0:0.5" />
</Storyboard>
</VisualState>
<VisualState x:Name="Deactivate">
<Storyboard>
<ColorAnimation Storyboard.TargetName="Path1"
Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)"
To="#00000000"
Duration="0:0:0.5" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="Path2States">
<!-- ... etc ... -->
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Now, create a List in the code-behind that contains each of the related objects, and then right your own GoToState function such that it turns on the in state for one object, and calls the off state for the rest.
List<Path> pathList;
public Page() // constructor
{
InitializeComponent();
pathList = new List<Path>();
pathList.Add(Path1);
// and so forth
}
// Call this function when you want to change the state
private void ActivatePath(Path p)
{
foreach (Path listItem in pathList)
{
// If the item from the list is the one you want to activate...
if (listItem == p)
VisualStateManager.GoToState(listItem, "Activate", true);
// otherwise...
else
VisualStateManager.GoToState(listItem, "Deactivate", true);
}
}
If I were better at XAML and styling I might have a cleaner way of creating the VisualStates. However, my forte is more on the logic and coding side. That being said, it's much cleaner that writing out the same VisualState four or five times! :)
Hope this helps!
I have created a HeaderTemplate for my accordions where I want to display a text block on one side of the header and a hyperlink on the right side. The display is working correctly, but the click event is not called when the user clicks, I'm guessing b/c the header itself is trapping the click for expand/contract.
<layoutToolkit:Accordion>
<layoutToolkit:AccordionItem IsSelected="True">
<layoutToolkit:AccordionItem.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20">
<TextBlock Margin="0,0,700,0">Cancel Postcards</TextBlock>
<HyperlinkButton Content="Next Call" Foreground="Blue" Click="NextCancel_Click" />
</StackPanel>
</DataTemplate>
</layoutToolkit:AccordionItem.HeaderTemplate>
..... more code ....
Is there a way to get the hyperlink to respond to events without practically creating a new control?
Update: It looks like the header sets all child controls to disabled when expanded which is why the link doesnt work. It will work when you collapse that accordionitem. So, the question now is, how do i prevent the hyperlink from being disabled?
Hey Charlie, I just happened to answer this same question for Epic720. You have to change the Locked VisualState.
Interactive items in Silverlight Accordion Header
Here is the LockedStates VisualStateGroup of the AccordionItem which you should alter. I can post the whole style if you need, though it's quite verbose.
<VisualStateGroup x:Name="LockedStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Locked">
<Storyboard>
<!--
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="IsEnabled" Storyboard.TargetName="ExpanderButton">
<DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
</ObjectAnimationUsingKeyFrames>
-->
</Storyboard>
</VisualState>
<VisualState x:Name="Unlocked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="IsEnabled" Storyboard.TargetName="ExpanderButton">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>