Используйте VisualStateManager для управления состояниями
VisualStateManager отслеживает состояия контрола и отвечает за исполнение логики переходов между состояниями. Когда вы добавляете в ControlTemplate объекты VisualState, вы добавляете их в секцию VisualStateGroup, которая в свою очередь описывается в присоединяемом свойстве VisualStateManger.VisualStateGroups, поэтому VisualStateManager имеет доступ ко всем этим объектам.
Следующий пример повторяет один из предыдущих примеров, демонстрирующих объекты контрола VisualState, соответствующие состояниям Positive и Negative. Storyboard объекта VisualState состояния Negative изменяет свойства цвета Foregorund элемента TextBlock на красный. Когда контрол NumericUpDown находится в состоянии Negative, срабатывает Storyboard этого состояния. А когда контрол возвращается в состояние Positive, Storyboard прекращает действовать. Для Positive VisualState нет нужды описывать Storyboard, поскольку когда Storyboard в состоянии Negative прекращает работать, свойство цвета Foregorund возвращается в своё исходное значение.
<vsm:VisualStateGroup x:Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<vsm:VisualState x:Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(SolidBruch.Color)"/>
</Storyboard>
</vsm:VisualState>
<!--Свойство Foreground получает своё исходное значение.-->
<vsm:VisualState x:Name="Positive"/>
</vsm:VisualStateGroup>
Логика контрола отвечает за изменение его состояния. В следующем примере показано, что контрол NumericUpDown вызывает метод GoToState для перехода в состояния Positive, когда Value больше или равно 0, и в состояние Negative, когда Value меньше 0.
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
Метод GoToState отвечает за логику необходимую для активации и остановки соответствующих объектов Storyboard. Когда контрол вызывает метод GoToState чтобы изменить свое состояние, VisualStateManger делает следующее:
• Если VisualState, к которому собирается перейти контрол, содержит Storyboard, тогда данный Storyboard начинает работу. Если VisualState, в котором контрол находился перед началом перехода, содержит Storyboard, работа этого Storyboard прекращается.
• Если контрол уже находится в нужном состоянии, тогда GoToState не производит никаких действий и возвращает true.
• Если требуемое состояние отсутствует в ControlTempalte, тогда GoToSate не производит никаких действий и возвращает false.
Рекомендованная модель работы с VisualStateManger
Для того, чтобы поддерживать состояния вашего контрола рекомендуется сделать следующее:
• Использовать свойства для контроля состояния контрола.
• Создать вспомогательный метод для перехода между состояниями.
Контрол NumericUpDown использует свойство Value для определения, находится ли он в состоянии Positive или Negative. Помимо состояний Positive и Negative, контрол определяет состояния Focused и UnFocused указывающие, находится ли контрол в фокусе или нет. В следующем примере показано, что контрол описывает закрытое свойство isFocused, как признак того, что контрол в фокусе.
bool isFocused;
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
isFocused = true;
UpdateStates(true);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
isFocused = false;
UpdateStates(true);
}
В следующем примере вы видите вспомогательный метод контрола UpdateStates. Когда Value больше или равно 0, контрол находится в состоянии Positive. А когда Value меньше 0, контрол находится в состоянии Negative. Если значение свойства isFocused = true, контрол находится в состоянии Focused, в противном случае – Unfocused.
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (isFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
Если в функцию GoToState передать название состояние, в котором контрол уже находится, тогда GoToState сработает в холостую, поэтому вам нет нужды проверять текущее состояние контрола. Например, когда Value содержит отрицательное число, а затем получает другое отрицательное число, работа объекта Storyboard в состоянии Negative не прерывается и пользователь не увидит никаких изменений.
VisualStateManager использует объекты VisualStateGroup для выяснения текущего состояния контрола в момент, когда вы вызываете метод GoToState. Контрол всегда находится только в одном состоянии из каждой группы VisualStateGroup и, следовательно, при переходе в новое состояние покидает только одно состояние из той же группы VisualStateGroup. Например, шаблон контрола NumericUpDown описывает два состояния Positive и Negative в одной группе VisualStateGroup, и два состояния Focused и Unfocused в другой группе. Когда контрол переходит из состояния Positive в состояние Negative, или наоборот, состояния других групп остаются незатронутыми.
Вот три характерных места, где состояние контрола может измениться:
• Когда ControlTemplate применяется к контролу при инициализации.
• Когда изменяются значения свойств.
• Когда срабатывают события.
В следующих примерах показано как корректируются состояния контрола в этих случаях.
Вы должны корректировать состояние контрола в методе OnApplyTemplate (он вызывается при инициализации шаблона контрола) так, чтобы контрол появился в правильном состоянии. Для этого в методе OnApplyTemplate вызывается метод UpdateStates. Допустим, вы создаете контрол NumericUpDown и затем задаете свойству Foreground значение «зеленый», а свойству Value – значение -5. Если в момент инициализации шаблона контрола вы не вызовете метод UpdateStates, тогда контрол не войдет в состояние Negative, и будет иметь зеленый цвет вместо красного.
Замечание:
В методе OnApplyTemplate метод UpdateStates вызывается с параметром false для того, чтобы при первом появлении контрола не проигрывалась анимация перехода между состояниями.
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Вам нередко придется корректировать состояния контрола при изменении значений его свойств. В следующем примере показан целиком метод ValueChangedCallback. Поскольку метод ValueChangedCallback вызывается когда свойство Value получает новое значение, внутри него вызывается метод UpdateStates на случай, если значение Value сменилось от положительного к отрицательному числу или наоборот. Допускается вызывать метод UpdateStates даже когда Value получает новое значение, но с прежним знаком, поскольку в этом случае просто ничего не произойдет.
private static void ValueChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Update the TextElement to the new value.
if (ctl.TextElement != null)
{
ctl.TextElement.Text = newValue.ToString();
}
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Raise the ValueChanged event so applications can be alerted
// when Value changes.
ValueChangedEventArgs e = new ValueChangedEventArgs(newValue);
ctl.OnValueChanged(e);
}
Кроме того, вам может понадобиться изменить состояние при срабатывании какого-либо события. В следующем примере показано, что контрол вызывает метод UpdateStates в обработчике OnGotFocus.
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
isFocused = true;
UpdateStates(true);
}
VisualStateManager помогает вам управлять состояниями контрола. С его помощью мы можете гарантировать, что ваш контрол выполняет переходы между состояниями корректно. Если вы соблюдаете рекомендации по работе с VisualStateManger, описанные в данном разделе, в вашем контроле будет проще разобраться другим разработчикам, а также будет легче его сопровождать.