如何刷新 VisualStateManager 的 'Focused' VisualState 的样式效果?

Posted

技术标签:

【中文标题】如何刷新 VisualStateManager 的 \'Focused\' VisualState 的样式效果?【英文标题】:How to refresh style effects of 'Focused' VisualState of VisualStateManager?如何刷新 VisualStateManager 的 'Focused' VisualState 的样式效果? 【发布时间】:2016-09-27 11:32:40 【问题描述】:

我的 ListBoxItem 的样式如下,当 listBoxItem 保持在提到的 VisualState 中时,会出现失去“聚焦”视觉效果的问题。如何强制在“Focused”ListBoxItem 上出现适当的视觉效果?

实现bug的场景:

1) 单击 ListBoxItem -> 它接收焦点和样式(ok)。

2) 将指针移到此 ListBoxItem 上 -> 它仍处于焦点状态并接收“MouseOver”的样式(它位于单独的 VisualStateGroup 的“CommonStates”中,如此处建议:https://msdn.microsoft.com/en-us/library/system.windows.controls.listboxitem%28v=vs.95%29.aspx)(ok)。

3) 使鼠标离开 ListBoxItem 的边界 -> 它失去了 MouseOver(ok) 的样式。

4) 现在,ListBoxItem 处于“CommonStates”的“Normal”状态,也是“Focused”,但没有“Focused”的样式(这是我的问题)。

5) 尝试点击此 ListBoxItem 无效

你能给我一些建议,如何处理它?

我正在使用 Silverlight,所以 4 我禁止使用触发器。

这是我的风格:

    <Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
    <Setter Property="HorizontalContentAlignment" Value="Center" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="ContentControl.Foreground" Value="StaticResource ViewModeButtonForeground" />
    <Setter Property="Focusable" Value="True" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListBoxItem">
                <Border x:Name="RootElement">
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="(ContentControl.Foreground)">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="StaticResource ViewModeButtonForeground" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CheckOuterBorder" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="StaticResource ViewModeButtonOuterBorder_MouseOver" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CheckOuterBorder" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="StaticResource ViewModeButtonBackground_MouseOver" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="(ContentControl.Foreground)">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="StaticResource ViewModeButtonForeground_Focused" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CheckOuterBorder" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="StaticResource ViewModeButtonOuterBorder_Focused" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CheckOuterBorder" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="StaticResource ViewModeButtonBackground_Normal" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused"></VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid>
                        <Border x:Name="CheckOuterBorder" CornerRadius="2" BorderThickness="1" BorderBrush="Transparent" Background="Transparent">
                        </Border>
                        <Grid x:Name="GridContent" Margin="TemplateBinding Padding" VerticalAlignment="TemplateBinding VerticalContentAlignment" Width="Auto">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"></ColumnDefinition>
                                <ColumnDefinition Width="Auto"></ColumnDefinition>
                            </Grid.ColumnDefinitions>
                            <Image Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" HorizontalAlignment="Left" Source="Binding ImgSource, Mode=OneWay"/>
                            <ContentControl x:Name="Content" Grid.Column="1" Margin="3,0,0,0" Foreground="TemplateBinding Foreground" Content="Binding Title" ContentTemplate="TemplateBinding ContentTemplate"  VerticalAlignment="Center" Width="Auto"/>
                        </Grid>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="ViewModeSelectionListBoxStyle" TargetType="ListBox">
    <Setter Property="BorderThickness" Value="0" />
    <Setter Property="IsTabStop" Value="True" />
    <Setter Property="ItemContainerStyle" Value="StaticResource ListBoxItemStyle1" />
    <Setter Property="Focusable" Value="True" />
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>    

如果我将 VisualStateGroup 从“FocusStates”更改为 SelectionStates,它的行为是相同的。我希望它与焦点而不是选择相关联,因为如果用户单击某处并且另一个控件也将具有类似的边框,则 UI 中可能会出现视觉不匹配。

顺便说一句:在我读过的另一个 msdn 网站上: “对于在其 ControlTemplate 中定义的每个 VisualStateGroup,控件始终处于一种状态,并且仅在从同一 VisualStateGroup 进入另一种状态时才离开一个状态。” 从我的代码隐藏中,我确保 listBoxItem 仍然是重点。此外,我试图在鼠标离开我的 listBoxItem 后强制进入该状态 - 没有结果(简化:ActiveViewDefinition 是列表框中实际焦点项目的替代品):

        private void It_MouseLeave(object sender, MouseEventArgs e)
        
            ListBoxItem lbItem = sender as System.Windows.Controls.ListBoxItem;
            if (lbItem != null && lbItem.Content == ActiveViewDefinition)
            
                VisualStateManager.GoToState(lbItem, "Focused", true);
            
        

【问题讨论】:

【参考方案1】:

简单。来自不同组的 VisualStates 操作相同元素的相同属性。 您总是需要有单独的元素(或使用共享元素的单独属性)来指示来自不同组的状态,否则它们自然会相互冲突和混淆(正如您所观察到的)。

伪xaml中的一个例子:

<VisualStateGroup x:Name="CommonStates">
    <VisualState x:Name="Normal"><NoChange/></VisualState>
    <VisualState x:Name="MouseOver"><Change What="TheBorder" Color="Red"/></VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
    <VisualState x:Name="Focused"><Change What="TheBorder" Color="Blue"/></VisualState>
</VisualStateGroup>

<Border x:Name="TheBorder" Color="Transparent"/>

最初边框是透明的。 现在当我们将鼠标移到边框上时会发生什么? mouseOver 状态将其更改为红色。现在我们单击我们的控件(假设它被选中并且可以聚焦)。 focusState 将其更改为蓝色。现在我们将鼠标移离它。 normalState 将其更改为初始状态:透明。 focusState 效果丢失。

我们看一下修改后的例子:

<VisualStateGroup x:Name="CommonStates">
    <VisualState x:Name="Normal"><NoChange/></VisualState>
    <VisualState x:Name="MouseOver"><Change What="TheMouseoverBorder" Color="Red"/></VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
    <VisualState x:Name="Focused"><Change What="TheFocusBorder" Color="Blue"/></VisualState>
</VisualStateGroup>

<Border x:Name="TheFocusBorder" Color="Transparent"/>
<Border x:Name="TheMouseoverBorder" Color="Transparent"/>

最初两个边框都是透明的。 现在当我们将鼠标移到它们上面时会发生什么? mouseOver 状态将 TheMouseoverBorder 更改为红色。现在我们单击我们的控件(假设它被选中并且可以聚焦)。 focusState 将 TheFocusBorder 更改为蓝色。现在我们将鼠标移离它。 normalState 将 TheMouseoverBorder 更改回透明。 focusState 仍然可见,因为 TheFocusBorder 不受其他状态更改的影响并且仍然是蓝色的。

[编辑]

如果您绝对必须从不同的 VisualStateGroups 修改同一元素的相同属性,您可以使用某种具有结果输出属性的聚合器,或使用某种失败机制。

我可以想到几种可能的解决方案来从不同的 VisualStateGroup 修改相同的 Foreground 属性。您可以尝试一下,看看它们是否有效。

穿越

<VisualState x:Name="MouseOver"><Change What="Outer" ForegroundColor="Red"/></VisualState>
...
<VisualState x:Name="Focused"><Change What="Inner" ForegroundColor="Blue"/></VisualState>

<ContentControl x:Name="Outer">
    <ContentControl x:Name="Inner">
    ...
    </ContentControl>
</ContentControl>

理论上:如果两个状态都处于活动状态,则内部状态获胜(重点:蓝色),如果只有鼠标悬停状态处于活动状态,则该属性不会显式设置在内部,而是从外部继承。未测试。试试看。

聚合器

<VisualState x:Name="MouseOver"><Change What="Aggregator" MouseOverColor="Red"/></VisualState>
...
<VisualState x:Name="Focused"><Change What="Aggregator" FocusedColor="Blue"/></VisualState>

<InvisibleAggregator x:Name="Aggregator"/>
<ContentControl Foreground="Binding ElementName=Aggregator, Path=EffectiveColor"/>

【讨论】:

感谢回复。是的,这很棘手,而且效果很好,但我还有另一个问题,因为如您所见,我还打算操作属性 ContentControl.Foreground,但 ContentControl 只是一个。如何处理? (我标记为答案) 我可以想到几种可能的解决方案来从不同的 VisualStateGroups 修改相同的 Foreground 属性。我会在我的答案中附加一些东西。给我 30 分钟。 我尝试添加具有折叠可见性的附加 ContentControl,并将内容绑定为:Content="Binding ElementName=Content, Path=Content",但这似乎是非常不必要的代码。 .,你怎么看? 我会在有时间的时候随时查看 ;) 而我对您当前的回复感到满意。我可以问你,如果出现与这个问题相关的东西,例如。明天? 如果您遇到任何问题并且无法理解,请随时询问。

以上是关于如何刷新 VisualStateManager 的 'Focused' VisualState 的样式效果?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过Behavior和VisualStateManager将TextBox设置为特定的内置VisualState

WPF: WPF 中的 Triggers 和 VisualStateManager

WPF: WPF 中的 Triggers 和 VisualStateManager

控件 UI: VisualState, VisualStateManager, 控件的默认 UI

使用 VisualStateManager 更改按钮样式

VisualStateManager.SetVisualStateGroups UWP