MVVM:调用命令后执行view-only code



我有什么:

  • 使用MVVM模式
  • 用XAML编写的视图
  • ViewModel中的命令MyCommand,从视图中的几个地方调用
  • 一个在视图上操作的方法DoSthInView,在codebehind
  • 中定义

我的目标:

无论何时执行命令,我都要调用DoSthInView,无论哪个控件执行该命令。

问题:

因为在MVVM的ViewModel不知道视图,我不能从ViewModel调用DoSthInView。那么如何调用这段代码呢?

自己的想法:

不那么抽象,这是我的用例:我们有一个按钮和一个文本框。该命令获取当前在TextBox中的文本,并将其写入模型数据的某个位置。当这篇文章完成后,我想让一个绿色的复选标记出现并逐渐消失(这是DoSthInView),这样用户就可以看到数据被更新了。

有两种运行命令的方式:

  1. 点击按钮
  2. 文本框聚焦时按"Enter"键

对于按钮,我知道一种方法来调用DoSthInView:

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

对于TextBox,我有一个KeyBinding来输入键:

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

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

但是,即使我找到了一种从输入绑定中调用DoSthInView的方法(类似于按钮),它也不会感觉正确。我正在寻找一种方法来说"每当MyCommand被执行时,运行DoSthInView",这样MyCommand的每个调用者都不必单独关心它,但只有一个地方可以处理它。也许这可以在根FrameworkElement中完成?

你所要求的是可能的。您需要实现RelayCommand。你也可以看到我的另一个SO答案,那里有一个例子。

一旦你实现了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,但在某些情况下,不使用viewModel是不可能的。有时它可能是需求。但这是不推荐的。

虽然我仍然对我最初的问题(在执行命令后调用codebehind-code)的答案感兴趣,但Kirenenko的建议帮助我解决了有关动画的实际问题。这个答案不再符合最初的问题,因为没有codebehind-code(动画完全用XAML编写,没有留下codebehind-code来执行)。我仍然把它放在这里,因为它对我来说部分有用。

在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看起来真的很愚蠢,但工作…

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

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()
    {
        ...
    }
    ...
}

通常您有更好的更MVVM-ish的方法来处理这些事情,但由于我不知道您的情况,我假设没有其他方法。为此,你需要在视图中有一个依赖属性。布尔值会很好你处理它的on changed事件并在其中运行DoSthInView。在你的视图模型中,你设置了这个属性的值,并在更改时被调用。

如果你需要的话,我可以给你示范一下。还要记住,这是事件驱动的编码,它会破坏MVVM。如果可能的话,尝试使用绑定并将DoSthInView移动到ViewModel。

相关内容

最新更新