VisualStateManager - display the state of the mouse when control is concentrated

I am creating a WPF button using the style of Windows 8 (formerly Metro).

I would like the focused state of the button to display on a solid background. When the mouse is over the control, I would like the background to turn a little darker to create a visual signal that you can click on the button.

Unfortunately, the XAML I wrote below does not work. The focused state is displayed correctly, but when the mouse is over the control, the background does not darken as I would like.

<Color x:Key="DoxCycleGreen"> #FF8DC63F </Color> <!-- Soft Interface : DoxCycle Green --> <Color x:Key="DoxCycleGreenSoft"> #FFC0DC8F </Color> <Style x:Key="MetroButton" TargetType="{x:Type Button}"> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="RootElement"> <VisualStateManager.VisualStateGroups> <VisualStateGroup Name="CommonStates"> <VisualState Name="Normal" /> <VisualState Name="MouseOver"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="White" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState Name="Focused"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreenSoft}" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="White" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState Name="Pressed"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="Transparent" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState Name="Disabled"> <Storyboard> <ColorAnimation Storyboard.TargetName="BorderColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid Background="Transparent" > <Border BorderThickness="1,1,1,1" Padding="2"> <Border.BorderBrush> <SolidColorBrush x:Name="BorderColor" Color="{StaticResource DoxCycleGreen}"/> </Border.BorderBrush> <Border.Background> <SolidColorBrush x:Name="BackgroundColor" Color="White"/> </Border.Background> <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Content"> <TextBlock.Foreground> <SolidColorBrush x:Name="FontColor" Color="{StaticResource DoxCycleGreen}"/> </TextBlock.Foreground> </ContentPresenter> </Border> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> 
+7
source share
2 answers

Now I have checked your code. Here you have a couple of problems. But the main problem is that the WPF control can only be in one visual state of a certain group of states at a time. And in cases like you here, where the control can be both focused and WPF must make a choice as to which state to apply (it cannot apply both states, since they are in the same state group). Therefore, in this case, it simply keeps it in the Focused state, and does not send it to the MouseOver state.

A control can be in several states if each of these states is in different groups of states. From this documentation :

Each VisualStateGroup contains a collection of VisualState objects that are mutually exclusive. That is, the control is always in the same state in each VisualStateGroup.

So, our first step towards correcting this code is to enable the correct groups of states that will allow the button to display the Focused state and then show its MouseOver state (other features can be fixed by this change, but you noticed, in particular, that you didn’t use your previous approach).

To do this, we must be careful to correctly name our state groups and (especially) our state names. This is because the code internal to the Button class makes a call of type VisualStateManager.GoToState(this, "VerySpecificStateName", true); (I did not check the actual source code of the Button class to verify this, but wrote my own controls where I needed to initiate state changes, I know that this should be something like that). To get a list of the names of state groups and states that we need, we could either use Expression Blend to “edit a copy” of the control template (which will fill in the states we need), or find them here . This documentation shows us that we need a state group called FocusStates and two states in this group called Focused and Unfocused (along with other state groups and states). As an illustration, to illustrate how the Button class initiates its state changes with these specific named states, if you change the source code by replacing the “Focus” state name with “MisspelledFocus”, you will see that your button never enters this state.

By implementing this first change, we could get something like:

 <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="White" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="Transparent" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ColorAnimation Storyboard.TargetName="BorderColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> </Storyboard> </VisualState> </VisualStateGroup> <!-- Focus States --> <VisualStateGroup x:Name="FocusStates"> <VisualState x:Name="Focused"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreenSoft}" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="White" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> 

This solves the problem somewhat. However, if you look at this in Expression Blend, you will notice a warning in the headers of the state groups:

Expression Blend warning about changing the same object-property in more than one state group

We get this warning because we are changing the value of an identical property / object pair in more than one group of states - in this case, the Color property of an object named BackgroundColor. Why could this be a problem? Due to what I said earlier, the fact that a control can be in several states at once if these states are in different groups of states. Therefore, if the user gave the button focus and the user also clicked on the button, it may be ambiguous for WPF as to which animation to use, since both states say that they revive the same property, but in different ways.

In addition, this first change does not fully give us what we want. If you try to give the focus of the button, then hover over it, it correctly switches from "Normal", "Focused", "MouseOver". But if you stop hovering, you will see that the button does not return to the "Focused" state.

There are several approaches that you could use to fix this problem and achieve something similar to what you wanted, but as an example, we could do something similar. (This may not be the cleanest implementation for this, but it fixes the issue of the shared object / property.):

 <Color x:Key="DoxCycleGreen"> #FF8DC63F </Color> <SolidColorBrush x:Key="DoxCycleGreenBrush" Color="{StaticResource DoxCycleGreen}" /> <!-- Soft Interface : DoxCycle Green --> <Color x:Key="DoxCycleGreenSoft"> #FFC0DC8F </Color> <SolidColorBrush x:Key="DoxCycleGreenSoftBrush" Color="{StaticResource DoxCycleGreenSoft}" /> <Style x:Key="ButtonStyle1" TargetType="{x:Type Button}"> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="RootElement"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="White" Duration="0:0:0.150" /> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="MouseOverBorder"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="Transparent" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ColorAnimation Storyboard.TargetName="BorderColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> </Storyboard> </VisualState> </VisualStateGroup> <!-- Focus States --> <VisualStateGroup x:Name="FocusStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.15"/> </VisualStateGroup.Transitions> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="FocusBorder"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ContentSiteWhiteForeground"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid Background="Transparent" > <Border x:Name="BaseBorder" BorderThickness="1,1,1,1"> <Border.BorderBrush> <SolidColorBrush x:Name="BorderColor" Color="{StaticResource DoxCycleGreen}"/> </Border.BorderBrush> <Border.Background> <SolidColorBrush x:Name="BackgroundColor" Color="White"/> </Border.Background> </Border> <Border x:Name="FocusBorder" BorderThickness="1,1,1,1" Background="{DynamicResource DoxCycleGreenSoftBrush}" Opacity="0" /> <Border x:Name="MouseOverBorder" BorderThickness="1,1,1,1" Background="{DynamicResource DoxCycleGreenBrush}" Opacity="0" /> <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Content" Margin="2"> <TextBlock.Foreground> <SolidColorBrush x:Name="FontColor" Color="{StaticResource DoxCycleGreen}"/> </TextBlock.Foreground> </ContentPresenter> <ContentPresenter x:Name="ContentSiteWhiteForeground" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Content" Margin="2" Opacity="0"> <TextBlock.Foreground> <SolidColorBrush Color="White" /> </TextBlock.Foreground> </ContentPresenter> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> 

Now you will see that we have removed the ambiguity for WPF. And we see that now it handles the case of state changes from "Normal" to "Focus" to "MouseOver" and back to "Focus" correctly.

+20
source

This is a small edit of Jason's answer. It turns out that his approach, which uses two ContentPresenters, disrupts the operation of the shortcut keys. I did a little tweaking ... short keys now work, but the transition animation is not so nice ...

 <Style x:Key="MetroButton" TargetType="{x:Type Button}"> <Setter Property="SnapsToDevicePixels" Value="true"/> <Setter Property="OverridesDefaultStyle" Value="true"/> <Setter Property="MinHeight" Value="23"/> <Setter Property="MinWidth" Value="75"/> <Setter Property="FocusVisualStyle" Value="{x:Null}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="RootElement"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="White" Duration="0:0:0.150" /> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="MouseOverBorder"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation Storyboard.TargetName="BackgroundColor" Storyboard.TargetProperty="Color" To="Transparent" Duration="0:0:0.150" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="{StaticResource DoxCycleGreen}" Duration="0:0:0.150" /> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ColorAnimation Storyboard.TargetName="BorderColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> <ColorAnimation Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color" To="DarkGray" Duration="0:0:0.1" /> </Storyboard> </VisualState> </VisualStateGroup> <!-- Focus States --> <VisualStateGroup x:Name="FocusStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.15"/> </VisualStateGroup.Transitions> <VisualState x:Name="Focused"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="FocusBorder"> <EasingDoubleKeyFrame KeyTime="0" Value="1"/> </DoubleAnimationUsingKeyFrames> <ColorAnimationUsingKeyFrames Storyboard.TargetName="FontColor" Storyboard.TargetProperty="Color"> <EasingColorKeyFrame KeyTime="0" Value="White"/> </ColorAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Unfocused"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Grid Background="Transparent" > <Border x:Name="BaseBorder" BorderThickness="1,1,1,1"> <Border.BorderBrush> <SolidColorBrush x:Name="BorderColor" Color="{StaticResource DoxCycleGreen}"/> </Border.BorderBrush> <Border.Background> <SolidColorBrush x:Name="BackgroundColor" Color="White"/> </Border.Background> </Border> <Border x:Name="FocusBorder" BorderThickness="1" Background="{DynamicResource DoxCycleGreenSoftBrush}" Opacity="0" /> <Border x:Name="MouseOverBorder" BorderThickness="1" Background="{DynamicResource DoxCycleGreenBrush}" Opacity="0" /> <ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" RecognizesAccessKey="True" ContentSource="Content" Margin="8,4"> <TextBlock.Foreground> <SolidColorBrush x:Name="FontColor" Color="{StaticResource DoxCycleGreen}"/> </TextBlock.Foreground> </ContentPresenter> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> 
+1
source

All Articles