将 [VisualStateManager] 视图状态绑定到 MVVM 视图模型?

34

如何将控件的VisualStateManager状态绑定到ViewModel中的属性?这个操作是否可行?


您可以使用GoToStateAction来控制状态,然后只需要将该行为附加到按钮或其他控件上即可。 - Justin XL
有趣,即使没有 Blend,你能使用它吗? - aL3891
1
但是GoToStateAction不在.NET框架中,对吧?它是否作为免费的dll /源代码在某个地方可用? - aL3891
5个回答

31

实际上你是可以的。窍门在于创建一个附加属性并添加一个属性变更回调函数,该函数实际上调用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中设置此属性,并像其他属性一样将其绑定到ViewModel:
<Window .. xmlns:local="clr-namespace:mynamespace" ..>
    <TextBox Text="{Binding Path=Name, Mode=TwoWay}"
             local:StateHelper.State="{Binding Path=State, Mode=TwoWay}" />
</Window>

NameState是视图模型中的常规属性。当通过绑定或其他方式设置Name时,它可以更改State,从而更新视觉状态。 State也可以由任何其他因素设置,并且仍将在文本框上更新视图状态。

由于我们使用正常绑定绑定到Status,因此我们可以应用转换器或任何其他我们通常能够做的事情,因此视图模型不必意识到它实际上正在设置视觉状态名称,State可以是布尔值、枚举或其他任何内容。

您还可以使用wpftoolkit在.net 3.5上使用这种方法,但您必须将target强制转换为Control而不是FrameworkElement

关于视觉状态的另一个快速注释,请确保不要命名您的视觉状态,以使其与内置状态发生冲突,除非您知道自己在做什么。对于验证尤其如此,因为验证引擎将尝试在每次更新绑定时(以及其他某些时间)设置其状态。有关各种控件的视觉状态名称的参考,请转到此处


3
我发现了以下几点:1. StateHelper必须继承DependencyObject。2. WinRT中的UIPropertyMetadata被命名为PropertyMetadata。3. 将属性附加到Page或UserControl的第一个<Grid>元素,而不是Page或UserControl本身。 - Nilzor
1
@Nilzor - 你是怎么解决“未知可附加成员”问题的?我按照你最近的帖子建议去做,但仍然出现了这个错误。 - Brent Traut
1
@brent-traut - 很好的问题(我不记得了)。将你的StateHelper.cs与这个进行比较:https://gist.github.com/4078545。 - Nilzor
1
@Nilzor 感谢您的代码片段。我正在尝试在C++中重新创建所有内容,但我认为在翻译过程中有些地方出了问题。据我所知,XAML编译器知道DependancyProperty是因为您在类定义中将其声明为public static。我不确定如何在C++中做同样的事情。这表明我的问题更多地涉及如何声明附加属性,而不是关于原始问题的。我将创建一个新帖子。 - Brent Traut
我遇到了一个问题,即GoToState方法在第一次失败,因为状态组尚未加载。还有其他人遇到过这个问题吗? - Benjamin
显示剩余3条评论

28

我是WPF的新手,但是经过一段时间的在MVVM层中奇怪的方式扭曲状态后,我终于找到了一个让我满意的解决方案。在ViewModel逻辑中更改状态并在XAML View中监听它。不需要转换器或代码后台"桥接"方法等。

View Code behind constructor

// 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 属性值相对应的名称,否则可能会使用枚举、转换器等使其复杂化。


1
请注意,这需要混合重定向程序集,虽然不是什么大问题,但需要注意 :) - aL3891
目前为止,最佳解决方案。 - Cabuxa.Mapache

12

阅读此文章: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这样的框架,这将变得更加简单。


我现在有了,但是几天前希望我已经有了;)非常有趣,我之前没有想过使用命令来监听事件。 - aL3891
@al3891:我已经更新了答案,提供了另一种可能的解决方案,具体取决于你想要做什么。那篇文章看起来非常不错 - 我必须承认我还没有实现其中的解决方案,但它已经在我的清单上有一段时间了! - Town
很棒 :) 不过我没有 Blend,这些程序集在哪里可以获取到? - aL3891
1
@al3891:好问题...我不确定,但是这个问题的被接受答案似乎肯定了这一点。 - Town
Microsoft.Expression.Interaction.dll和Microsoft.Windows.Interactivity.dll这两个程序集随Visual Studio 2015(以及可能更新的版本)一起发布。 - pinki

1
这是我在WPF中使用的支持MVVM的VisualStateManager状态的类:
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}">
    ...

0
这是一个与 .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}"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接