MVVM:运行 调用命令后的只读代码

MVVM: run view-only code after calling command

我有:

我的目标:

每当命令执行时,我想调用DoSthInView,无论哪个控件执行命令。

问题:

由于在 MVVM 中,ViewModel 不了解 View,因此我无法从 ViewModel 调用 DoSthInView。那么这段代码怎么调用呢?

自己的想法:

为了不那么抽象,这是我的用例:我们有一个按钮和一个文本框。该命令获取当前位于 TextBox 中的文本并将其写入模型数据中的某处。写完这篇文章后,我想为一个绿色复选标记的出现和淡出设置动画(这是 DoSthInView),以便用户可以直观地确认数据已更新。

运行命令有两种方式:

  1. 单击按钮
  2. 在文本框获得焦点时按 "Enter"

对于按钮,我知道调用 DoSthInView:

的方法
<Button Content="run command" Command="{Binding MyCommand}" Click={Binding DoSthInView}" />

对于 TextBox,我有一个 KeyBinding 来获取 Enter 键:

<TextBox>
    <TextBox.InputBindings>
        <KeyBinding Command="{Binding MyCommand}" Key="Enter" />
    </TextBox.InputBindings>
</TextBox>

但是InputBindings好像不支持事件,只支持命令。所以在这里我不知道如何调用 DoSthInView.

但即使我找到了一种从输入绑定(类似于按钮)中调用 DoSthInView 的方法,它也感觉不对。我正在寻找一种表达 "whenever MyCommand is executed, run DoSthInView" 的方式,以便并非 MyCommand 的每个调用者都必须单独处理它,但只有一个地方可以处理它。也许这可以在根 FrameworkElement 中完成?

通常你有更好的 MVVM 风格的方法来处理这些事情,但由于我不知道你的情况,我会假设没有其他方法。 因此,您需要在视图中依赖 属性。布尔值会很棒,您可以在更改事件和 运行 DoSthInView 中处理它。在您的视图模型中,您设置此 属性 的值并在更改时调用。

如果你需要,我可以给你演示。还要记住,这是污染 MVVM 的事件驱动编码。如果可能,请尝试使用绑定并将您的 DoSthInView 移动到 ViewModel。

虽然我仍然对我的原始问题的答案感兴趣(在执行命令后调用代码隐藏代码),但 Kirenenko 的建议帮助我解决了我关于动画的实际问题。这个答案不再适合原始问题,因为没有代码隐藏代码(动画完全用 XAML 编写,没有代码隐藏代码可以执行)。我仍然把它放在这里,因为它对我有部分用处。

在 ViewModel 中,我有这个:

...
private bool _triggerBool;
public bool TriggerBool
{
    get { return _triggerBool; }
    set
    {
        if (_triggerBool != value)
        {
            _triggerBool = value;
            NotifyPropertyChanged(nameof(TriggerBool));
        }
    }
}
...
public DelegateCommand MyCommand; // consists of MyCommandExecute and MyCommandCanExecute
...
public void MyCommandExecute()
{
    ... // actual command code
    TriggerBool = true;
    TriggerBool = false;
}
...

这是在 XAML 中编写并由 DataTrigger 调用的动画:

<Image Source="myGreenCheckmark.png" Opacity="0">
    <Image.Style>
        <Style TargetType="Image">
            <Style.Triggers>
                <DataTrigger Binding="{Binding TriggerBool}" Value="True">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                                 From="1" To="0" Duration="0.0:0:0.750"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </DataTrigger.EnterActions>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Image.Style>

TriggerBool 设置为 true,然后再次设置 false 看起来真的很愚蠢,但是有效...

你所要求的是可能的。您需要实施 RelayCommand。 也可以看我的另一个里面有例子

实现 RelayCommand 后,您可以执行以下操作:

在 ViewModel 中:

public ICommand MyCommand { get; set; }
public MyViewModel()
{
    MyCommand = new RelayCommand(MyCommand_Execute);
}

private void MyCommand_Execute(object sender)
{
    var myView = sender as MyView;

    myView?.DoSthInView();
}

在视图中:

<TextBox>
    <TextBox.InputBindings>
        <KeyBinding Command="{Binding Path=MyCommand}" CommandParameter="{Binding}" Key="Enter"/>
    </TextBox.InputBindings>
 </TextBox>

虽然不建议混合使用 view 和 viewModel,但在某些情况下是不可能的。有时它可能是要求。但同样不推荐这样做。

根据 Leonid Malyshev 的提示,这是一个使用事件的非常干净的解决方案("clean" 关于 View 和 ViewModel 的分离):

视图模型代码:

public class MyViewModel
    {
    ...
    public event Action MyCommandExecuted;
    public DelegateCommand MyCommand; // consists of MyCommandExecute, MyCommandCanExecute
    ...
    private void MyCommandExecute()
    {
        ... // actual command code
        MyCommandExecuted.Invoke();
    }
    ...
}

查看代码隐藏:

public partial class MyView : Window
{
    public MyView(MyViewModel vm)
    {
        InitializeComponent();
        DataConext = vm;
        vm.MyCommandExecuted += DoSthInView();
    }
    ...
    private void DoSthInView()
    {
        ...
    }
    ...
}