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?
Related
I have a strange behaviour... I have a button
<Button Template="{StaticResource FlatButton}">ASDF</Button>
And I have the template
<ControlTemplate x:Key="FlatButton" TargetType="Button">
<Grid Background="{TemplateBinding Background}" TextBlock.Foreground="{TemplateBinding Foreground}">
<Rectangle Fill="White" Opacity="0." x:Name="Overlay" />
<Rectangle Fill="Gray" Opacity="0." x:Name="OverlayDark" />
<ContentPresenter Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal">
<VisualState.Storyboard>
<Storyboard TargetName="Overlay" TargetProperty="Opacity">
<DoubleAnimation To="0.0" Duration="0:0:0.1" />
</Storyboard>
</VisualState.Storyboard>
</VisualState>
<VisualState Name="MouseOver">
<VisualState.Storyboard>
<Storyboard TargetName="Overlay" TargetProperty="Opacity">
<DoubleAnimation To="0.5" Duration="0:0:0.1" />
</Storyboard>
</VisualState.Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard TargetName="OverlayDark" TargetProperty="Opacity">
<DoubleAnimation To="0.5" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
<VisualState Name="Disabled">
<Storyboard TargetName="OverlayDark" TargetProperty="Opacity">
<DoubleAnimation To="1" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
so far so good. Now I start the application and as soon as I hover over the button the application halts. It also tells me that there are no source lines to show because all threads are currently working on framework code.
Now comes the thing. If I remove the normal state, everything is fine. For hovering at least. When I try to click the button the app crashes again. If I use DataTemplate Triggers, everything is fine.
If I use the Microsoft reference template for buttons the app doesn't even pop up, it immediately crashes. Using only the control template without the style or using it embedded inside the style doesn't make a difference
Using Visual States with a check box does not crash the app...
Any ideas on what exactly is happening here? To my knowledge this should be working without flaw.
Also I'm on .NET 4.7 and have some UWP, some .NET Standard and some .NET Framework projects in my solution, though it shouldn't matter.
I was getting a NullReferenceException when I moused over the button with your code. With the following change, it works correctly. You say you're not able to see what the exception is, so it's possible that I'm seeing a different problem, but it's worth a try.
<VisualState Name="MouseOver">
<VisualState.Storyboard>
<!--
WARNING
This was throwing an exception when TargetProperty was an
attribute of Storyboard. The reason for that is not known.
-->
<Storyboard>
<DoubleAnimation
To="0.5"
Duration="0:0:0.1"
Storyboard.TargetName="Overlay"
Storyboard.TargetProperty="Opacity"
/>
</Storyboard>
</VisualState.Storyboard>
</VisualState>
TargetName can be on the Storyboard element or as an attached property on the DoubleAnimation element; it makes no difference for me. But I get the exception if TargetProperty is not used as an attached property on the DoubleAnimation.
The other visual states work for me just as you have them. I don't have any theory about why this might be the case.
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?
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
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?
I've video player with two button: Play and Pause.
I want to use only one button. when user clicks on Play, the button appearance will changed to Pause and vice versa.
What is the better approach to achieve that task without using cs code behind?
I've tried to use DataTrigger to my IsPlaying property, but with no much success....
Here is my code:
<Window.Resources>
<Viewbox x:Key="viewboxSource" >
<Viewbox.Triggers>
<DataTrigger Binding="{Binding IsPlaying}" Value="True">
<Setter Property="Path">
<Setter.Value>
<Path Stroke="Black" StrokeThickness="1" Fill="AliceBlue">
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center="100,100" RadiusX="100" RadiusY="100"/>
</GeometryGroup>
</Path.Data>
</Path>
</Setter.Value>
</Setter>
</DataTrigger>
</Viewbox.Triggers>
</Viewbox>
</Window.Resources>
<StackPanel>
<Button Content="{StaticResource viewboxSource}"></Button>
</StackPanel>
But I gut an error that says " 'Path' member is not valid because it does not have a qualifying type name " .
Can anyone can help or give me a better solution?
These kind of behaviour fits toggle button patern.
Make a style in your resources
<Style x:Key="PlayToggleButtonStyle" TargetType="ToggleButton" >
and then define a templeate in it
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
What is the most important here is to use VisualStateManager.
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled"/>
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver"/>
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="border" />
<ColorAnimation Duration="0:0:0.2" To="#FF392929" Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="border"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
I use 2 animation. One moves button for 2 pixels and second change the gradient which gives a nice experience.
The only drawback is you need to use storyboards to handle these states. You need to add a Path object which I called Geometry nad mainupulate it.
<Storyboard Storyboard.TargetName="Geometry"
Storyboard.TargetProperty="Data">
<ObjectAnimationUsingKeyFrames>
<DiscreteObjectKeyFrame KeyTime="0" Value=""/> <!-- place the data here (how your button looks like) -->
</ObjectAnimationUsingKeyFrames>
</Storyboard>
But IMHO the better solution is to place 2 Path object in the template that on is over another and change the opacity of the top-most one.
<Storyboard>
<DoubleAnimation Storyboard.TargetName="TopGeometry" Storyboard.TargetProperty="Opacity" Duration="0:0:0.5" To="0.0">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseIn"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
You would have a nice transition between these two states. What is more, no data is needed f.e IsPLaying property.