VisualStateGroup, инициировавший анимацию в другом VisualStateGroup

Существует что-то фундаментальное о VisualStateGroups, что я не понимаю. Все, что я считал, привело меня полагать, что они являются ортогональными. Таким образом, изменение состояния в одной группе не будет влиять на другие группы. Действительно, они были бы довольно бессмысленны, если бы это не имело место.

Однако в попытке понять некоторое нечетное поведение я встречался, я соединил простой пример, который показывает, что изменение состояния в одной группе может инициировать анимацию в другом. Я пытаюсь понять, как это может быть.

Все, что я хочу, является a ToggleButton- основанное управление, чтобы иметь одно появление при переключении (IsChecked == true) независимо от того, фокусируется ли это или является ли мышь по нему, например.

Во-первых, я имею очень простой контроль что переходы между пользовательскими состояниями в пользовательской группе:

using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

[TemplateVisualState(Name = normalState, GroupName = activationGroupName)]
[TemplateVisualState(Name = hoverState, GroupName = activationGroupName)]
[TemplateVisualState(Name = activatedState, GroupName = activationGroupName)]
public class CustomControl : ToggleButton
{
    private const string activationGroupName = "Activation";
    private const string normalState = "Normal";
    private const string hoverState = "Hover";
    private const string activatedState = "Activated";

    public CustomControl()
    {
        this.DefaultStyleKey = typeof(CustomControl);

        this.Checked += delegate
        {
            this.UpdateStates();
        };
        this.Unchecked += delegate
        {
            this.UpdateStates();
        };
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        this.UpdateStates();
    }

    protected override void OnMouseEnter(MouseEventArgs e)
    {
        base.OnMouseEnter(e);
        this.UpdateStates();
    }

    protected override void OnMouseLeave(MouseEventArgs e)
    {
        base.OnMouseLeave(e);
        this.UpdateStates();
    }

    private void UpdateStates()
    {
        var state = normalState;

        if (this.IsChecked.HasValue && this.IsChecked.Value)
        {
            state = activatedState;
        }
        else if (this.IsMouseOver)
        {
            state = hoverState;
        }

        VisualStateManager.GoToState(this, state, true);
    }
}

Во-вторых, у меня есть шаблон для этого управления, которое изменяет цвет фона на основе текущего состояния:

<Style TargetType="local:CustomControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:CustomControl">
                <Grid>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="Activation">
                            <VisualStateGroup.Transitions>
                                <VisualTransition To="Normal" GeneratedDuration="00:00:0.2"/>
                            </VisualStateGroup.Transitions>

                            <VisualState x:Name="Normal"/>

                            <VisualState x:Name="Hover">
                                <Storyboard>
                                    <ColorAnimation Duration="00:00:0.2" To="Red" Storyboard.TargetName="grid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"/>
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="Activated">
                                <Storyboard>
                                    <ColorAnimation Duration="00:00:0.2" To="Blue" Storyboard.TargetName="grid" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>

                    <Grid x:Name="grid" Background="White">
                        <TextBlock>ToggleButton that should be white normally, red on hover, and blue when checked</TextBlock>
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

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

Я могу обойти это путем явной обработки изменений фокуса в моем управлении и предписания обновления состояния:

public CustomControl()
{
    this.DefaultStyleKey = typeof(CustomControl);

    this.Checked += delegate
    {
        this.UpdateStates();
    };
    this.Unchecked += delegate
    {
        this.UpdateStates();
    };

    // explicitly handle focus changes
    this.GotFocus += delegate
    {
        this.UpdateStates();
    };
    this.LostFocus += delegate
    {
        this.UpdateStates();
    };
}

Однако это не имеет никакого смысла мне, почему я должен сделать это.

Почему изменение состояния в одном VisualStateGroup порождение анимации, определенной другим для выполнения? И каков самый простой, самый корректный способ для меня достигнуть моей установленной цели?

Спасибо

1
задан Kent Boogaart 12 July 2010 в 18:02
поделиться

1 ответ

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

Обратите также внимание, что ToggleButton, от которого вы произошли, имеет следующий атрибут, связанный с классом:

[TemplateVisualStateAttribute(Name = "Normal", GroupName = "CommonStates")]

Это указывает на то, что унаследованная вами логика в какой-то момент вызовет GotoState с состоянием "Normal". Обычно элементы управления имеют только один метод UpdateStates (как у вас), и в любой момент, когда любое из состояний может измениться, будет вызван этот метод. Эта функция, в свою очередь, вызовет GotoState несколько раз с одним именем состояния из каждой группы. Таким образом, в момент, когда элемент управления теряет фокус, ToggleButton вызывает GotoState для установки состояния "Unfocused", но вместе с этим будет вызывать и "Normal" и либо "Checked", либо "Unchecked".

Теперь у вас есть собственный шаблон, в котором состояния "Unfocused" и "Checked" отсутствуют, поэтому вызовы GotoState из ToggleButton ничего не делают. Однако у вас есть "Normal". У VSM нет способа узнать, что ToggleButton, использующий "Normal", предназначен для выбора из группы "CommonStates" шаблона по умолчанию. Вместо этого он просто находит VisualState с именем "Normal", которое в данном случае находится в группе "Activiation". Следовательно, существующее состояние "Активировано" заменяется на "Нормальное".

Измените "Нормальный" на "Бла", и все будет работать так, как вы ожидаете.

(Это помогает помнить, что имена групп не делают многого, несмотря на их присутствие в TemplateVisualStateAttribute, их единственное использование, о котором я могу думать, это в Blend UI)

2
ответ дан 2 September 2019 в 23:07
поделиться
Другие вопросы по тегам:

Похожие вопросы: