wpf VisualStateManager -- 当控件获得焦点时显示鼠标悬停状态

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14632796/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 07:20:09  来源:igfitidea点击:

VisualStateManager -- showing mouseover state when control is focused

wpfstylingcontroltemplatevisualstatemanager

提问by Cameron Peters

I am creating a WPF button using Windows 8 styling (formerly metro).

我正在使用 Windows 8 样式(以前称为 Metro)创建 WPF 按钮。

I would like the focused state of the button to show with a solid background. When the mouse is over the control, I would like th background to darken slightly to create the visual cue that the button can be clicked.

我希望按钮的聚焦状态以纯色背景显示。当鼠标悬停在控件上时,我希望背景稍微变暗以创建可以单击按钮的视觉提示。

Unfortunately, the XAML I've written below does not work. The focused state shows correctly, but when the mouse is over the control, the background does not darken as I would like it to.

不幸的是,我在下面编写的 XAML 不起作用。聚焦状态正确显示,但是当鼠标悬停在控件上时,背景不会像我希望的那样变暗。

<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>

回答by Jason Frank

I've now tested your code. You've got a couple of issues at play here. But the main issue is that a WPF control can only be in one Visual State of a particular State Group at a time.And in cases like you've got here, where the control can be bothfocused and moused-over, WPF has to make choice of which State to apply (it cannot apply both States since they are in the same State Group). So in this case it is just keeping it in the Focused state and not sending it to the MouseOver state.

我现在已经测试了你的代码。您在这里遇到了几个问题。但主要问题是 WPF 控件一次只能处于特定状态组的一种视觉状态。在像您这样的情况下,控件可以同时聚焦和鼠标悬停,WPF 必须选择应用哪个状态(它不能同时应用两个状态,因为它们在同一个状态组中)。所以在这种情况下,它只是将它保持在 Focused 状态,而不是将它发送到 MouseOver 状态。

A control canbe in multiple states if each of those states are in differentstate groups. From this documentation:

如果每个状态都在不同的状态组中,则控件可以处于多个状态。从这个文档

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

每个 VisualStateGroup 都包含一组互斥的 VisualState 对象。也就是说,控件始终处于每个 VisualStateGroup 中的一种状态。

So our first step to correcting this code is to include the proper state groups that will allow the button to be able to show its Focused state and then show its MouseOver state (other possibilities can be corrected by this change but that's the one you noticed in particular that you didn't get with your previous approach).

因此,我们更正此代码的第一步是包含适当的状态组,使按钮能够显示其焦点状态,然后显示其鼠标悬停状态(此更改可以纠正其他可能性,但这是您在特别是您以前的方法没有得到的)。

To do this, we need to be careful to name our state groups and (especially) our state names correctly. This is because the code internal to the Button class likely makes a call like VisualStateManager.GoToState(this, "VerySpecificStateName", true);(I have not inspected the actual source code of the Button class to verify this, but having written custom controls where I've needed to initiate the state changes, I know it must be something like that). In order to get a list of the state group and state names that we'll need, we could either use Expression Blend to "edit a copy" of the control template (which will populate the needed states for us), or find them here. That documentation shows us that we need a state group called "FocusStates" and two states in that group called "Focused" and "Unfocused" (along with other state groups and states). As an aside, to illustrate how the Button class is initiating its state changes by these specific named states, if you change your original code by replacing the "Focus" state name with "MisspelledFocus", you'll see that your button never enters that state.

为此,我们需要小心地正确命名我们的州组和(尤其是)我们的州名。这是因为 Button 类的内部代码可能会调用类似VisualStateManager.GoToState(this, "VerySpecificStateName", true);(我没有检查过 Button 类的实际源代码来验证这一点,但是在需要启动状态更改的地方编写了自定义控件,我知道它一定是这样的)。为了获得我们需要的状态组和状态名称的列表,我们可以使用 Expression Blend 来“编辑一个副本”控制模板(它将为我们填充所需的状态),或者在这里找到它们. 该文档向我们展示了我们需要一个名为“FocusStates”的状态组以及该组中名为“Focused”和“Unfocused”的两个状态(以及其他状态组和状态)。顺便说一句,为了说明 Button 类如何通过这些特定命名状态启动其状态更改,如果您通过将“Focus”状态名称替换为“MisspelledFocus”来更改原始代码,您将看到您的按钮永远不会进入状态。

Implementing this first change, we could end up with 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 somewhat solves the problem. However, if you're looking at this in Expression Blend, you'll notice a warning in the state group headings:

这在一定程度上解决了问题。但是,如果您在 Expression Blend 中查看此内容,您会注意到状态组标题中的警告:

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

关于在多个状态组中更改相同对象属性的 Expression Blend 警告

We are getting this warning because we are changing the value of an identical property/object pair in more than one state group - in this case the "Color" property of the object named "BackgroundColor". Why could this be an issue? Because of what I said earlier - the fact that a control can be in multiple states at once if those states are in different state groups. So if the user has given the button focus and the user has also moused-over the button, it could be ambiguous to WPF as to which animation to apply, since both states say to animate the same exact property but in different ways.

我们收到此警告是因为我们正在更改多个状态组中相同属性/对象对的值 - 在本例中为名为“BackgroundColor”的对象的“Color”属性。为什么这会成为一个问题?因为我之前说过的 - 如果这些状态在不同的状态组中,则控件可以同时处于多个状态。因此,如果用户已给予按钮焦点并且用户也将鼠标悬停在按钮上,则 WPF 可能不清楚应用哪个动画,因为这两种状态都表示以不同的方式为相同的属性设置动画。

Also, this first change does not completely get us what we want. If you try giving the button focus, then hover over it, it correctly goes from "Normal", to "Focused", to "MouseOver". But if you now discontinue hovering, you'll see that the button does not return to its "Focused" state.

此外,这第一个变化并没有完全让我们得到我们想要的。如果您尝试赋予按钮焦点,然后将鼠标悬停在它上面,它会正确地从“正常”变为“聚焦”,再到“鼠标悬停”。但是,如果您现在停止悬停,您将看到该按钮不会返回到其“聚焦”状态。

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

您可以使用多种方法来解决此问题并实现与您想要的类似的效果,但仅作为示例,我们可以执行类似的操作。(这可能不是最干净的实现,但它修复了常见的对象/属性问题。):

<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'll see that we have removed the ambiguity for WPF. And we see that it now handles the case of state changes from "Normal" to "Focus" to "MouseOver" and back to "Focus" correctly.

现在您将看到我们已经消除了 WPF 的歧义。我们看到它现在可以正确处理从“Normal”到“Focus”再到“MouseOver”再回到“Focus”的状态变化情况。

回答by Cameron Peters

This is a small edit to Jason's answer. It turns out that his approach which uses two ContentPresenters breaks the operation of the short cut keys. I've made a small adjustment... the short cut keys now work, but the transition animation isn't quite as nice...

这是对杰森回答的一个小编辑。事实证明,他使用两个 ContentPresenter 的方法破坏了快捷键的操作。我做了一个小调整……快捷键现在可以工作了,但过渡动画不太好……

<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>