Связывание состояния представления [VisualStateManager] с моделью представления MVVM?
Как связать состояние элемента управления VisualStateManager со свойством в вашей модели представления? Это можно сделать?
4 ответа
На самом деле вы можете. Хитрость заключается в том, чтобы создать свойство Attached и добавить свойство, изменяющее обратный вызов, который фактически вызывает GoToState
:
public class StateHelper {
public static readonly DependencyProperty StateProperty = DependencyProperty.RegisterAttached(
"State",
typeof( String ),
typeof( StateHelper ),
new UIPropertyMetadata( null, StateChanged ) );
internal static void StateChanged( DependencyObject target, DependencyPropertyChangedEventArgs args ) {
if( args.NewValue != null )
VisualStateManager.GoToState( ( FrameworkElement )target, args.NewValue, true );
}
}
Затем вы можете установить это свойство в xaml и добавить привязку к вашей модели представления, как и любой другой:
<Window .. xmlns:local="clr-namespace:mynamespace" ..>
<TextBox Text="{Binding Path=Name, Mode=TwoWay}"
local:StateHelper.State="{Binding Path=State, Mode=TwoWay}" />
</Window>
Name
а также State
являются обычными свойствами в viewmodel. когда Name
устанавливается в viewmodel, либо связыванием, либо чем-то еще, это может изменить State
Ведьма обновит визуальное состояние. State
может также быть установлен любым другим фактором, и все же это обновит состояние просмотра в текстовом поле.
Поскольку мы используем обычную привязку для привязки к Status, мы можем применять конвертеры или что-либо еще, что мы обычно можем сделать, поэтому viewmodel не должен знать, что он фактически устанавливает визуальное имя состояния, State может быть bool или enum или что-то еще.
Вы также можете использовать этот подход, используя wpftoolkit на.net 3.5, но вы должны привести target
к Control
вместо FrameworkElement
,
Еще одно краткое замечание о визуальных состояниях. Убедитесь, что вы не называете свои визуальные состояния, чтобы они конфликтовали со встроенными, если только вы не знаете, что делаете. Это особенно верно для проверки, так как механизм проверки будет пытаться устанавливать свои состояния каждый раз, когда привязка обновляется (и в некоторых других случаях). Перейдите сюда для ссылки на имена визуальных состояний для различных элементов управления.
Я новичок в WPF, но после некоторого странного скручивания состояний через слои MVVM в течение некоторого времени я наконец-то нашел решение, которое меня устраивает. Измените состояние как часть логики ViewModel и прослушайте его в представлении XAML. Нет необходимости в конвертерах или коде за "мостовыми" методами или подобными.
Посмотреть код за конструктором
// Set ViewModel as the views DataContext
public ExampleView(ExampleViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
Пространства имен XAML
// Reference expression namespaces
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Привязки XAML
// Bind GoToStateAction directly to a ViewModel property
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding State}" Value="{Binding State}">
<ei:GoToStateAction StateName="{Binding State}" />
</ei:DataTrigger>
</i:Interaction.Triggers>
ViewModel Code
// Update property as usual
private string _state;
public string State
{
get { return _state; }
set
{
_state = value;
NotifyPropertyChanged("State");
}
}
Теперь установка свойства State для ExampleViewModel вызовет соответствующее изменение состояния в представлении. Убедитесь, что визуальные состояния имеют имена, соответствующие значениям свойств State, или усложните их перечислениями, преобразователями и т. Д.
Прочитайте эту статью: Silverlight 4: использование VisualStateManager для анимации состояния с MVVM
В качестве альтернативы, если вы только что переключились между двумя состояниями, вы можете использовать DataStateBehaviour. Я использовал это, чтобы переключить фон, когда отображается страница входа.
Пространства имен
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
XAML
<i:Interaction.Behaviors>
<ei:DataStateBehavior TrueState="LoginPage" FalseState="DefaultPage"
Binding="{Binding IsLoginPage}" Value="true" />
</i:Interaction.Behaviors>
Это стало еще проще благодаря использованию такой инфраструктуры, как Caliburn.Micro.
Вот класс, который я использую для поддержки MVVM VisualStateManager
состояния в WPF:
public static class MvvmVisualState
{
public static readonly DependencyProperty CurrentStateProperty
= DependencyProperty.RegisterAttached(
"CurrentState",
typeof(string),
typeof(MvvmVisualState),
new PropertyMetadata(OnCurrentStateChanged));
public static string GetCurrentState(DependencyObject obj)
{
return (string)obj.GetValue(CurrentStateProperty);
}
public static void SetCurrentState(DependencyObject obj, string value)
{
obj.SetValue(CurrentStateProperty, value);
}
private static void OnCurrentStateChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var e = sender as FrameworkElement;
if (e == null)
throw new Exception($"CurrentState is only supported on {nameof(FrameworkElement)}.");
VisualStateManager.GoToElementState(e, (string)args.NewValue, useTransitions: true);
}
}
В вашем XAML:
<TargetElement utils:MvvmVisualState.CurrentState="{Binding VisualStateName}">
...
Вот вспомогательный класс, который работает с.NET 4.7.2.
Очевидно, в какой-то момент Microsoft прекратила поддержку настраиваемых вложенных свойств в статических классах. Другие ответы приводят к ошибкам компилятора XAML о невозможности найти что-то в пространстве имен.
public sealed class VisualStateHelper: DependencyObject
{
public static readonly DependencyProperty visualStateProperty = DependencyProperty.RegisterAttached
(
"visualState",
typeof( object ),
typeof( VisualStateHelper ),
new UIPropertyMetadata( null, onStateChanged )
);
static void onStateChanged( DependencyObject target, DependencyPropertyChangedEventArgs args )
{
if( args.NewValue == null )
return;
if( target is FrameworkElement fwe )
VisualStateManager.GoToElementState( fwe, args.NewValue.ToString(), true );
}
public static void SetvisualState( DependencyObject obj, string value )
{
obj.SetValue( visualStateProperty, value );
}
public static string GetvisualState( DependencyObject obj )
{
return (string)obj.GetValue( visualStateProperty );
}
}
Пример использования:
local:VisualStateHelper.visualState="{Binding visualState}"