VisualStateGroup Запуск анимации в другой VisualStateGroup

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

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

Все, что я хочу, это 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 ответ

Решение

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

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

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

Это указывает на то, что логика, которую вы наследуете, будет в какой-то момент GotoState с состоянием "Нормальный". Как правило, элементы управления имеют только один UpdateStates метод (как у вас есть), и в любой момент, когда любое из состояний могло измениться, он будет вызван. Эта функция в свою очередь вызовет GotoState несколько раз с одним именем штата из каждой группы. Следовательно, в тот момент, когда контроль теряет фокус, ToggleButton звонки GotoState чтобы установить состояние "Несфокусировано", но вместе с этим оно также будет вызываться с "Нормальным" и либо "Проверено", либо "Не проверено".

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

Измените "Normal" на "Blah", и все будет работать так, как вы ожидаете.

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

Другие вопросы по тегам