Как обновить эффекты стиля 'Focused' VisualState VisualStateManager?

У меня есть стиль ListBoxItem, как показано ниже, и проблема с потерей "сфокусированных" визуальных эффектов, когда listBoxItem остается в упомянутом VisualState. Как заставить соответствующие визуальные эффекты появляться в "Focused" ListBoxItem?

Сценарий для достижения ошибки:

1) Нажмите ListBoxItem -> он получит фокус и стили (ок).

2) Наведите указатель на этот ListBoxItem -> он все еще находится в фокусе и получите стили для MouseOver(он находится в отдельной VisualStateGroup 'CommonStates', как предлагается здесь: https://msdn.microsoft.com/en-us/library/system.windows.controls.listboxitem%28v=vs.95%29.aspx) (хорошо).

3) Заставьте мышь выходить за пределы ListBoxItem -> она теряет стиль для MouseOver(хорошо).

4) Теперь ListBoxItem находится в "нормальном" состоянии "CommonStates" и также "сфокусирован", но не имеет стильного "Focused" (это моя проблема).

5) Атака на нажатие на этот ListBoxItem не имеет никакого эффекта

Не могли бы вы дать мне совет, как с этим бороться?

Я использую Silverlight, поэтому мне запрещены триггеры.

Вот мой стиль:

    <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, он будет вести себя так же. Я хочу, чтобы это было связано с фокусом, а не с выделением, потому что в пользовательском интерфейсе может быть визуальное несоответствие, если пользователь щелкает где-то, и другой элемент управления также будет иметь аналогичную границу.

Кстати, на другом сайте msdn я прочитал: "Элемент управления всегда находится в одном состоянии для каждой VisualStateGroup, определенной в его ControlTemplate, и покидает состояние только тогда, когда он переходит в другое состояние из той же 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 ответ

Решение

Легко. Ваши визуальные состояния из разных групп манипулируют одними и теми же свойствами одних и тех же элементов. Вам всегда нужно иметь отдельные элементы (или использовать отдельные свойства общих элементов), чтобы указывать состояния из разных групп, в противном случае они будут естественным образом конфликтовать и портить настройки друг друга (как вы заметили).

Пример в псевдо-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, вы можете либо использовать какой-то агрегатор с результирующим выходным свойством, либо использовать какой-нибудь механизм сквозного анализа.

Я могу придумать несколько возможных решений для изменения одного и того же свойства Foreground из отдельных VisualStateGroups. Вы можете попробовать их и посмотреть, работают ли они.

Провалиться

<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}"/>
Другие вопросы по тегам