Как вы привязываете состояние VisualStateManager элемента управления к свойству в вашей модели просмотра? Это можно сделать?
Привязка состояния представления [VisualStateManager] к модели представления MVVM?
Ответы (5)
На самом деле можно. Хитрость заключается в том, чтобы создать прикрепленное свойство и добавить обратный вызов измененного свойства, который фактически вызывает 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
- обычные свойства в модели просмотра. Когда Name
установлен в модели просмотра, привязкой или чем-то еще, это может изменить State
, ведьма обновит визуальное состояние. State
также может быть установлен любым другим фактором, и все же он будет обновлять состояние просмотра в текстовом поле.
Поскольку мы используем обычную привязку для привязки к Status, мы можем применять преобразователи или что-то еще, что мы обычно могли бы сделать, поэтому модель просмотра не должна знать, что она фактически устанавливает имя визуального состояния, 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>
Код модели просмотра
// 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}"