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)