从代码后台调用命令

91

我一直在搜索,但无法找到如何实现以下需求。我正在使用 MVVM 创建一个用户控件,并希望在“Loaded”事件上运行一个命令。我知道这需要一些代码来支持,但我还没有完全弄清楚需要哪些代码。该命令位于ViewModel中,而ViewModel被设置为视图的数据上下文,但我不确定如何路由才能从代码后台调用它。基本上,我想要的是这样的东西...

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    //Call command from viewmodel
}

我四处查找,似乎无法找到这个语法的语法。 我需要在xaml中首先绑定命令才能引用它吗? 我注意到用户控件中的命令绑定选项不会像按钮等控件那样允许您绑定命令...

<UserControl.CommandBindings>
    <CommandBinding Command="{Binding MyCommand}" /> <!-- Throws compile error -->
</UserControl.CommandBindings>

我相信有一种简单的方法可以做到这一点,但是我想不出来该怎么做。

6个回答

175

如果 DataContext 已经设置,你可以将其强制转换并调用命令:

var viewModel = (MyViewModel)DataContext;
if (viewModel.MyCommand.CanExecute(null))
    viewModel.MyCommand.Execute(null);

(根据需要更改参数)


1
是的,那正是我需要的,我就知道有一个简单的方法。谢谢! - Kevin DiTraglia
2
@Alain:我的前言里有一条注释,我认为如果在某些情况下这不是保证的话,人们可以引入一个检查。 - H.B.
当ViewModel中的MyCommand被定义为特定的MyMethod时,为什么不直接执行viewModel.MyMethod()呢?除了当然可以直接调用的CanExecute之外。 - Gerard
@Gerard:如果类接口允许直接访问方法,并且该方法执行与命令相同的操作,则可以这样做。但是,您应该记住,命令在语义上可能与方法不同,因此您的代码不再代表“在代码中调用命令”,因为对命令实现的更改将不再反映在代码中。 - H.B.
1
@sanya:这是一个常见的误解。视图必须知道视图模型是什么,否则绑定也无法工作。通常你只是尽量避免使用代码后台,因为它不够声明式且有点混乱。反过来就会违反设计模式(视图模型访问视图),在原始设计模式中,同一视图模型可能具有多个不同的视图,但在实践中往往是1对1。 - H.B.
显示剩余10条评论

10
前言:不了解您的要求,从代码后台加载时执行命令似乎是一种代码气味。从MVVM角度看,应该有更好的方法。
但是,如果您真的需要在代码后台执行此操作,可以尝试以下方法(注意:我目前无法测试此方法):
private void UserControl_Loaded(object sender, RoutedEventArgs e)     
{
    // Get the viewmodel from the DataContext
    MyViewModel vm = this.DataContext as MyViewModel;

    //Call command from viewmodel     
    if ((vm != null) && (vm.MyCommand.CanExecute(null)))
        vm.MyCommand.Execute(null);
} 

Again - try to find a better way...


4
经过搜索,实际上没有简单的方法,我看到的普遍共识是有一些代码并不会伤害任何人。 - Kevin DiTraglia
2
@KDiTraglia - 确实如此,但一般来说,“little code behind”通常指仅影响视图本身的操作(例如,在文本框获得焦点时选择所有文本)。在这里,您正在从视图的代码后台直接与ViewModel进行交互,这违反了MVVM原则。 - Wonko the Sane
@WonkotheSane 我不同意。视图通过绑定其命令和属性已经了解ViewModel,因此从代码后台引用ViewModel与从XAML中引用一样可以。但是,ViewModel不应该了解视图,否则肯定会违反MVVM原则。 - Shahin Dohan
1
@ShahinDohan - 这个可能在过去的8年里有所发展 :) 但我认为基本原则仍然存在。如果您需要在代码后台与确认对话框等进行交互,那是可以的。但是,如果您在代码后台与ViewModel进行交互以执行业务逻辑,那真的违反了这个想法。至少这是我的观点。 - Wonko the Sane

2

我有一个更简洁的解决方案想要分享。因为我经常在我的ViewModels中执行命令,我厌倦了写相同的if语句。所以我为ICommand接口编写了一个扩展。

using System.Windows.Input;

namespace SharedViewModels.Helpers
{
    public static class ICommandHelper
    {
        public static bool CheckBeginExecute(this ICommand command)
        {
            return CheckBeginExecuteCommand(command);
        }

        public static bool CheckBeginExecuteCommand(ICommand command)
        {
            var canExecute = false;
            lock (command)
            {
                canExecute = command.CanExecute(null);
                if (canExecute)
                {
                    command.Execute(null);
                }
            }

            return canExecute;
        }
    }
}

这是在代码中执行命令的方法:

((MyViewModel)DataContext).MyCommand.CheckBeginExecute();

我希望这可以再次加速你的开发。 :)
附注:别忘了也要包含ICommandHelper的命名空间。(在我的情况下,它是SharedViewModels.Helpers)

为什么不直接传递 true 呢?例如:WizardViewModel.OkCommand.Execute(true); - AZ_

1

要在后台代码中调用命令,您可以使用以下代码行

例如:调用按钮命令

Button.Command?.Execute(Button.CommandParameter);

1

试试这个:

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    //Optional - first test if the DataContext is not a MyViewModel
    if( !this.DataContext is MyViewModel) return;
    //Optional - check the CanExecute
    if( !((MyViewModel) this.DataContext).MyCommand.CanExecute(null) ) return;
    //Execute the command
    ((MyViewModel) this.DataContext).MyCommand.Execute(null)
}

2
在调用命令之前,您应该确保它可以被执行。 - H.B.
如果您只是使用CanExecute来确定用户是否可以执行它(即绑定到按钮的启用状态),那么这是没有问题的。 - Alain
3
通过一个按钮,该控件确保命令永远不会被随意执行,如果您手动执行它,则需要自己小心处理。 - H.B.

0

你可能已经将代码嵌入到任何MessaginCenter.Subscribe中,并使用MessagingCenter模型进行工作。

如果你只想从代码后台执行某些操作,而不是通过具有Command属性的视图按钮点击,则对我来说完美地运行。

希望能对某人有所帮助。


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