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?
Related
I encountered an issue with a ControlTemplate for ToggleButton I created.
When the button is Checked, a ColorAnimation is triggered and the control's background changes color. However, if the user enters the MouseOver state, another animation is triggered that affects the button's background as well.
When the mouse is no longer in the MouseOver state, the control does not return to the color it should be while it is in the Checked state. I'm not sure why this does not persist when the MouseOver state is triggered.
The VisualStateManager portion of my ControlTemplate looks sorta like this:
<VisualStateManger.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"></VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BackgroundBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="Gold" Duration="0:0:0.3" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="CheckedStates">
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BackgroundBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="PaleGoldenrod" Duration="0:0:0.3" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
My workaround for the issue I was having involved creating a Grid that enclosed the Border.
For the CommonStates I made animation changes to the Border.Background and for the CheckedStates I made animation changes to the Grid.Background.
It achieves the visual effect I was looking for.
I have buttons supplied to me from photoshop for different button states.
It looks like this
<Button x:Name="ResultsBtn" Click="ResultsBtn_Click" FontSize="27" BorderThickness="0" Padding="-10" Margin="-10">
<Grid>
<Image Source="..But_01_Idle.png" Width="496"/>
<TextBlock Text="Results" Margin="174,21,172,23" Width="90" Height="40" Foreground="White" />
</Grid>
</Button>
The button looks right for just one state.
I want to use expression blend to record a state and change the background behind an image (in this instance an outer glow) or change the source on a state change.
I've noticed that blend only seems to record position and transforms and not changes in attributes.
Should I be doing this in code or rather have the photoshop file sent in a particular format so that it can be automatically converted by blend
Okay, I'm actually quite surprised at Blend: it does not seem to allow you to animate the source property. However, Silverlight allows it so I assume WP7 will also allow it; this looks like a bug in Blend 4. However, I still would not recommend it using an image based approach because the images will deform and look bad/pixelated when significantly scaled up or down. A better approach is to edit your button's control template and modify it to match your reference artwork. You can even using File -> Import Adobe Photoshop File ... to pull the basic artwork into Blend. Then it's just a matter of shuffling it into the control template.
If you're dead set on using images (which will increase the size of your XAP and actually cause slower load UserControl load times), you can go about it as follows in Blend:
Create a new project and add a Button to your root visual element.
Create a new project folder called Images and add two images to it. (I used Koala.jpg and Penguins.jpg from the Sample Pictures folder.)
Right click the button and select Edit Template -> Edit a Copy...
The default template will contain a Grid that contains a Border named Background. Inside the Background border is a Grid that contains a Rectangle and another Border. Delete both of those innermost elements.
Now add an Image as a child of the Background border's Grid.
Now switch to the XAML editor and modify your control template's visual state groups to match the following code. (Look for the two "Added" comment blocks.)
Run the project. On mouse over you'll see penguins. Click and hold the left mouse and you'll see a koala.
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<!-- Added -->
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TheImage" Storyboard.TargetProperty="Source">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="/Images/Penguins.jpg"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<!-- End of Added -->
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Border.Background).**(SolidColorBrush.Color)" Storyboard.TargetName="Background"/>
<!-- Added -->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TheImage" Storyboard.TargetProperty="Source">
<DiscreteObjectKeyFrame KeyTime="0:0:0" Value="/Images/Koala.jpg"/>
</ObjectAnimationUsingKeyFrames>**
<!-- End of Added -->
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualElement"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="White" CornerRadius="3">
<Grid Background="{TemplateBinding Background}" Margin="1">
<Image x:Name="TheImage" Source=""/>
</Grid>
</Border>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" RadiusY="3" RadiusX="3"/>
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" RadiusY="2" RadiusX="2" Stroke="#FF6DBDD1" StrokeThickness="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
Visual States are built with animations, so you can only change things that can be animated (and expect typical results). I've never tried it, but my instinct tells me an Image source cannot be animated so the VSM is probably not a viable way to manage that.
Opacity, however, can be animated, so you could have both Image's in ytour button and control their Opacity with States. Just a thought.
You have to create a ControlTemplate:
Code for Button:
<Button Template={DynamicResource ButtonTemplate}/>
In your Resource Dictionary:
<ControlTemplate x:Key="ButtonTemplate" {x:Type Button}>
<Grid Padding="-10" Margin="-10">
<Image x:Name="IdleState" Source="..But_01_Idle.png" Width="496"/>
<Image x:Name="MouseOverState" Source="..But_01_MouseOver.png" Width="496"/>
<Image x:Name="PressedState..." etc/>
<TextBlock Text="Results" Margin="174,21,172,23" Width="90" Height="40" Foreground="White" FontSize="27"/>
</Grid>
</ControlTemplate>
Then, in Blend, Edit the Template and you'll find the States as Mike said. Use the Properties panel to hide / show your images for each state you want to style and you should be done.
I'd like to know if is it possible to apply a Visual State (in WPF 4) to nested controls. I've got a stack panel that contains some elements I'd like to change according to variation state.
<StackPanel x:Name="panPremioRaggiunto">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="StatiComuni">
<VisualState Name="PremioNonRaggiunto" />
<VisualState Name="PremioRaggiunto">
<Storyboard>
<ColorAnimation Storyboard.TargetName="lblPremioRaggiunto" Storyboard.TargetProperty="Foreground" To="Green" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="lblPremioRaggiunto">TEXT</TextBlock>
</StackPanel>
When I try to change the state of entire stack panel with this code
VisualStateManager.GoToState(panPremioRaggiunto, "PremioRaggiunto", False)
nothing happens: nested textblock named lblPremioRaggiunto don't change his color according. Can I apply a visual state in this manner?
Thanks,
Danilo.
Yes, VisualStateManager can change the state of any control. You must have some other issue with your code.
Fixed issue: visual states are defined outside a control template so I have to use VisualStateManager.GoToElementState instead of GoToState.
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?
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?