VisualStateGroup Запуск анимации в другой VisualStateGroup - PullRequest
1 голос
/ 12 июля 2010

В 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 Ответ

2 голосов
/ 13 июля 2010

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

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

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

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

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

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

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

...