如何将控件的 VisualStateManager 状态绑定到视图模型中的属性?可以做到吗?
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,我们可以应用转换器或我们通常能够做的任何其他事情,因此 viewmodel 不必知道它实际上设置了一个可视状态名称 State可以是 bool 或 enum 或其他。
您也可以使用 .net 3.5 上的 wpftoolkit 使用这种方法,但您必须转换target
为 aControl
而不是 a 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");
}
}
现在设置 ExampleViewModel 的 State 属性将触发视图中相应的状态更改。确保视觉状态具有与 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 的帮助程序类。
显然,微软在某个时候打破了对静态类中自定义附加属性的支持。其他答案导致 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}"