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

Posted

技术标签:

【中文标题】将 [VisualStateManager] 视图状态绑定到 MVVM 视图模型?【英文标题】:Binding [VisualStateManager] view state to a MVVM viewmodel? 【发布时间】:2011-08-25 12:20:42 【问题描述】:

如何将控件的 VisualStateManager 状态绑定到视图模型中的属性? 能做到吗?

【问题讨论】:

您可以使用 GoToStateAction 来控制状态。然后你只需要将行为附加到按钮或其他东西上。 有意思,没有blend也能用吗? 但是 GoToStateAction 不在 .net 框架中,对吧?它是否可以作为免版税的 dll/源在某个地方使用? 【参考方案1】:

其实你可以。 诀窍是制作一个附加属性并添加一个实际调用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>

NameState 是视图模型中的常规属性。在视图模型中设置Name 时,无论是通过绑定还是其他方式,它都可以更改State 女巫将更新视觉状态。 State 也可以由任何其他因素设置,它仍然会更新文本框上的视图状态。

由于我们使用普通绑定来绑定到 Status,我们可以应用转换器或我们通常能够执行的任何其他操作,因此 viewmodel 不必知道它实际上设置了视觉状态name, State 可以是 bool 或 enum 或其他。

您也可以使用 .net 3.5 上的 wpftoolkit 使用此方法,但您必须将 target 转换为 Control 而不是 FrameworkElement

另一个关于视觉状态的快速说明,请确保不要命名视觉状态,以免它们与内置状态冲突,除非您知道自己在做什么。这对于验证尤其如此,因为验证引擎将尝试设置其状态每次绑定更新(以及在其他一些时间)。 Go here 参考不同控件的视觉状态名称。

【讨论】:

有人知道如何在 WinRT 上编译吗?我收到 XamlCompiler 错误 WMC0010: Unknown attachable member 'StateHelper.State' on element 'TextBox' 我发现了。 1:StateHelper 必须继承 DependencyObject。 2:UIPropertyMetadata 在 WinRT 中被命名为 PropertyMetadata。 3:将属性附加到 Page 或 UserControl 的第一个 元素,而不是 Page 或 UserControl 本身。 @Nilzor - 你是如何解决“未知的可附加成员”问题的?我听从了您最近帖子中的建议,但仍然出现该错误。 @brent-traut - 好问题(我不记得了)。将您的 StateHelper.cs 与此进行比较:gist.github.com/4078545。 @Nilzor 感谢您的要点。我正在尝试用 C++ 重新创建所有这些,但我想我在翻译中迷失了方向。据我所知,XAML 编译器知道 DependancyProperty,因为您在类定义中将其声明为 public static。我不确定如何在 C++ 中做同样的事情。这表明我的问题更多是关于如何声明附加属性,而不是关于这里的原始问题。我将创建一个新帖子。【参考方案2】:

我是 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 代码

// Update property as usual
private string _state;
public string State

  get  return _state; 
  set
  
    _state = value;
    NotifyPropertyChanged("State");
  

现在设置 ExampleViewModel 的 State 属性将触发视图中相应的状态更改。确保视觉状态具有与 State 属性值对应的名称,或者使用枚举、转换器等使其复杂化。

【讨论】:

请注意,虽然这需要混合 redist 程序集,但不是什么大问题,但需要注意 :) 目前最好的解决方案。【参考方案3】:

阅读这篇文章:Silverlight 4: using the VisualStateManager for state animations with 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:我已经用另一种可能的解决方案更新了答案,具体取决于您想要做什么。那篇文章看起来非常好 - 我必须承认我还没有在其中实施解决方案,但它已经在我的列表中一段时间​​了! nifty :) 我没有混合,这些组件在某处可用吗? @al3891:好问题...我不确定,但this question 的公认答案似乎暗示了这一点。 Microsoft.Expression.Interaction.dll 和 Microsoft.Windows.Interactivity.dll 程序集随 Visual Studio 2015(可能更新)一起提供。【参考方案4】:

这是我用于 MVVM 支持 WPF 中 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">
    ...

【讨论】:

【参考方案5】:

这是一个适用于 .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"

【讨论】:

以上是关于将 [VisualStateManager] 视图状态绑定到 MVVM 视图模型?的主要内容,如果未能解决你的问题,请参考以下文章

Silverlight & Blend动画设计系列九:动画(Animation)与视图状态管理(Visual State Manager)

Silverlight & Blend动画设计系列九:动画(Animation)与视图状态管理(Visual State Manager)

控件 UI: VisualState, VisualStateManager, 控件的默认 UI

如何刷新 VisualStateManager 的 'Focused' VisualState 的样式效果?

使用 VisualStateManager 更改按钮样式

VisualStateManager.SetVisualStateGroups UWP