双击WPF ListView - 如何触发命令而不使用事件处理程序

时间:2022-11-28 20:56:07

I am a little confused on how to implement an event as a command in my particular situation. I want to honour MVVM, but don't get how in this case.

关于如何在我的特定情况下将事件实现为命令,我有点困惑。我想要尊重MVVM,但在这种情况下不要理解。

I have a WPF 'view' - viewCustomerSearch. This has some text boxes on it, and when the user clicks 'Search' the results are populated in ListView. viewCustomerSearch is bound to viewmodelCustomerSearch, and it works great.

我有一个WPF'视图' - viewCustomerSearch。它上面有一些文本框,当用户点击“搜索”时,结果将填充在ListView中。 viewCustomerSearch绑定到viewmodelCustomerSearch,它运行良好。

viewCustomerSearch is hosted on viewCustomer.

viewCustomerSearch托管在viewCustomer上。

I want to know have viewCustomerSearch expose a custom command - CustomerSelectedCommand - that is 'fired' whenever the ListView in viesCustomerSearch is double clicked, and then handled by the viewmodel behind viewCustomer (which is viewmodelCustomer). This seems the theoretical MVVM pattern implemented correctly.

我想知道viewCustomerSearch公开一个自定义命令 - CustomerSelectedCommand - 只要双击viesCustomerSearch中的ListView,然后由viewCustomer(viewmodelCustomer)后面的viewmodel处理,就会'触发'。这似乎正确地实现了理论上的MVVM模式。

I have broken down the main problem into three smaller problems, but hopefully you can see they are all components of the same challenge.

我已将主要问题分解为三个较小的问题,但希望您可以看到它们都是同一挑战的组成部分。

FIRST PROBLEM: in order to have viewCustomerSearch expose a custom command I seem to have to put this code in viewCustomerSearch - which seems to 'break' MVVM (no code in the view code behind).

第一个问题:为了让viewCustomerSearch公开一个自定义命令,我似乎必须把这个代码放在viewCustomerSearch中 - 这似乎'打破'MVVM(后面的视图代码中没有代码)。

public readonly DependencyProperty CustomerSelectedCommandProperty = DependencyProperty.Register("CustomerSelectedCommand", typeof(ICommand), typeof(viewCustomerSearch));

public ICommand CustomerSelectedCommand
{
    get { return (ICommand)GetValue(CustomerSelectedCommandProperty); }
    set { SetValue(CustomerSelectedCommandProperty, value); }
}

SECOND PROBLEM (and this is the one that is really getting to me): Best explained by showing what I would do which breaks MVVM. I would have an event handler in the view:

第二个问题(这是我真正得到的问题):最好通过展示我将做什么打破MVVM来解释。我在视图中有一个事件处理程序:

private void lstResults_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (CustomerSelectedCommand != null) CustomerSelectedCommand.Execute(((ViewModels.viewmodelCustomerSearchResult)this.lstResults.SelectedItem).CustomerId);
}

Well ... I know that you shouldn't put this event handler here; rather it should have a Command to handle it in the viewmodelCustomerSearch. The two problems here are

嗯......我知道你不应该把这个事件处理程序放在这里;相反它应该有一个Command来在viewmodelCustomerSearch中处理它。这里的两个问题是

  • because the 'CustomerSelectedCommand' ICommand is implemented in viewCustomerSearch, viewmodelCustomerSearch can't see it to fire it.

    因为'CustomerSelectedCommand'ICommand是在viewCustomerSearch中实现的,所以viewmodelCustomerSearch无法看到它来触发它。

  • I cannot see how to bind the MouseDoubleClick event to a command, instead of an event handler in the view code behind. I am reading about Attached Properties, but cannot see how they are to be applied here.

    我看不到如何将MouseDoubleClick事件绑定到命令,而不是后面的视图代码中的事件处理程序。我正在阅读有关附加属性,但无法看到它们如何应用于此处。

(Please note: I am using the common 'RelayCommand' elsewhere in the application; does this come into play here??)

(请注意:我在应用程序的其他地方使用了常见的'RelayCommand';这会在这里发挥作用吗??)

THIRD PROBLEM: When I do use the non-MVVM way of firing the command in the code behind event handler, you can see that I am passing in the Selected Customer Id as an arguement into the command. How do I see that argument in the Command handler in viewCustomer? I create a new RelayCommand to handle it, but it seems the Execute method does not take arguments?

第三个问题:当我使用非MVVM方式在事件处理程序后面的代码中触发命令时,您可以看到我将选定的客户ID作为参数传递给命令。如何在viewCustomer的Command处理程序中看到该参数?我创建了一个新的RelayCommand来处理它,但似乎Execute方法不接受参数?

Given all of the above, I have to say that I do NOT personally subscribe to the 'MVVM means NO CODE IN THE VIEW'. That seems crazy to me; code that is entirely to do with the view, and the view only, should not - IMHO - go in the viewmodel. That said, though, this does seem like logic-y stuff (not view stuff).

考虑到上述所有情况,我不得不说我个人并不赞同“MVVM意味着在视图中没有代码”。这对我来说似乎很疯狂;与视图完全相关的代码,仅视图,不应该 - 恕我直言 - 进入视图模型。虽然如此,这看起来像是逻辑的东西(不是查看东西)。

Many thanks for some insight. Sorry for the long post; trying to balance enough information for you to help me with 'War and Peace'.

非常感谢您的一些见解。对不起,很长的帖子;试图平衡足够的信息,帮助我“战争与和平”。

DS

2 个解决方案

#1


0  

In your view you can add a "Command" property in xaml and bind it to your ViewModel's command

在您的视图中,您可以在xaml中添加“Command”属性并将其绑定到ViewModel的命令

Command="{Binding CustomerSelectedCommand}"

Parameters can be passed in multiple ways. Most of the time, I just have other items bound to my ViewModel and I can just use them directly. However there is also a property called CommandParameter, here's an example of specifying it in XAML.

参数可以以多种方式传递。大多数时候,我只有其他项目绑定到我的ViewModel,我可以直接使用它们。但是,还有一个名为CommandParameter的属性,这是在XAML中指定它的示例。

 CommandParameter="{Binding ElementName=txtPassword}"

then in my ViewModel the definition of my Command looks like this

然后在我的ViewModel中,我的Command的定义如下所示

private void UserLogonCommandExecute(object parameter)
{
...
       var password_box = parameter as PasswordBox;
...
}

It sounds like you already know how to set up a RelayCommand in your ViewModel so I won't go into that. I found How Do I: Build Data-driven WPF Application using the MVVM pattern helpful when I was getting started.

听起来你已经知道如何在ViewModel中设置RelayCommand所以我不会进入那个。我发现如何:使用MVVM模式构建数据驱动的WPF应用程序在我开始时很有用。

Per Comment Request Command Property Example

I'm just going to grab some working code, here's how you add a Command property to a button in XAML.

我只想获取一些工作代码,这里是如何在XAML中向按钮添加Command属性。

<Button Command="{Binding ConnectCommand}">
//Your button content and closing </Button> here

This assume you have set your DataContext to a ViewModel that has a Command called ConnectCommand. Here's an example for ConnectCommand. You'll need to replace the contents of ConnectCommandCanExecute and ConnectCommandExecute with whatever work you want done.

这假设您已将DataContext设置为具有名为ConnectCommand的Command的ViewModel。这是ConnectCommand的一个例子。您需要将ConnectCommandCanExecute和ConnectCommandExecute的内容替换为您想要完成的任何工作。

public ICommand ConnectCommand
{
    get
    {
        if (_connectCommand == null)
        {
            _connectCommand = new RelayCommand(param => ConnectCommandExecute(),
                                               param => ConnectCommandCanExecute);
        }
        return _connectCommand;
    }
}

private bool ConnectCommandCanExecute
{
    get { return !_instrumentModel.IsConnected; }
}

private void ConnectCommandExecute()
{
    if (TcpSettingsChanged()) SaveTcpSettings();
    _instrumentModel.Connect(_tcpData);
}

RelayClass

One part of making this simple is the RelayClass I have in one of my core library .dlls. I probably got this from one of the videos I watched. This can be cut and pasted in it's entirety, there is nothing here you need to customize, except you'll probably want to change the namespace this is in.

使这个简单的一部分是我在我的一个核心库.dll中的RelayClass。我可能从我看过的一个视频中得到了这个。这可以完整地剪切和粘贴,这里没有你需要自定义的东西,除了你可能想要更改它所在的命名空间。

using System;
using System.Diagnostics;
using System.Windows.Input;

namespace Syncor.MvvmLib
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;

public event EventHandler CanExecuteChanged
{
  add
  {
    CommandManager.RequerySuggested += value;
  }
  remove
  {
    CommandManager.RequerySuggested -= value;
  }
}

public RelayCommand(Action<object> execute)
  : this(execute, (Predicate<object>) null)
{
  this._execute = execute;
}

public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
  if (execute == null)
    throw new ArgumentNullException("execute");
  this._execute = execute;
  this._canExecute = canExecute;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
  if (this._canExecute != null)
    return this._canExecute(parameter);
  else
    return true;
}

public void Execute(object parameter)
{
  this._execute(parameter);
}
}
}

#2


0  

Why don't you name it "DoubleClickCommand" that way you don't put business logic in your control. And then bind this command to your viewmodel, Like Tod explained. Regarding your code behind, there is a pure xaml solution, to be more precise it involves attached behaviors, but does not need to override a WPF class(which i like to avoid), search for "fire command on event" for example this. One final thing: Code Behind does NOT break MVVM in any way, i wonder where this myth came from. Code behind is perfectly fine! MVVM is to separate view and logic, not telling you where to put your code. Design principles should help, not hinder you.

为什么不将它命名为“DoubleClickCommand”,因为您没有将业务逻辑置于您的控制之下。然后将此命令绑定到您的viewmodel,就像Tod解释的那样。关于你的代码背后,有一个纯xaml解决方案,更准确地说它涉及附加行为,但不需要覆盖WPF类(我想避免),搜索“事件上的fire命令”,例如这个。最后一件事:Code Behind不会以任何方式打破MVVM,我想知道这个神话来自哪里。代码背后完全没问题! MVVM是分离视图和逻辑,而不是告诉你在哪里放置你的代码。设计原则应该有所帮助,而不是阻碍你。

#1


0  

In your view you can add a "Command" property in xaml and bind it to your ViewModel's command

在您的视图中,您可以在xaml中添加“Command”属性并将其绑定到ViewModel的命令

Command="{Binding CustomerSelectedCommand}"

Parameters can be passed in multiple ways. Most of the time, I just have other items bound to my ViewModel and I can just use them directly. However there is also a property called CommandParameter, here's an example of specifying it in XAML.

参数可以以多种方式传递。大多数时候,我只有其他项目绑定到我的ViewModel,我可以直接使用它们。但是,还有一个名为CommandParameter的属性,这是在XAML中指定它的示例。

 CommandParameter="{Binding ElementName=txtPassword}"

then in my ViewModel the definition of my Command looks like this

然后在我的ViewModel中,我的Command的定义如下所示

private void UserLogonCommandExecute(object parameter)
{
...
       var password_box = parameter as PasswordBox;
...
}

It sounds like you already know how to set up a RelayCommand in your ViewModel so I won't go into that. I found How Do I: Build Data-driven WPF Application using the MVVM pattern helpful when I was getting started.

听起来你已经知道如何在ViewModel中设置RelayCommand所以我不会进入那个。我发现如何:使用MVVM模式构建数据驱动的WPF应用程序在我开始时很有用。

Per Comment Request Command Property Example

I'm just going to grab some working code, here's how you add a Command property to a button in XAML.

我只想获取一些工作代码,这里是如何在XAML中向按钮添加Command属性。

<Button Command="{Binding ConnectCommand}">
//Your button content and closing </Button> here

This assume you have set your DataContext to a ViewModel that has a Command called ConnectCommand. Here's an example for ConnectCommand. You'll need to replace the contents of ConnectCommandCanExecute and ConnectCommandExecute with whatever work you want done.

这假设您已将DataContext设置为具有名为ConnectCommand的Command的ViewModel。这是ConnectCommand的一个例子。您需要将ConnectCommandCanExecute和ConnectCommandExecute的内容替换为您想要完成的任何工作。

public ICommand ConnectCommand
{
    get
    {
        if (_connectCommand == null)
        {
            _connectCommand = new RelayCommand(param => ConnectCommandExecute(),
                                               param => ConnectCommandCanExecute);
        }
        return _connectCommand;
    }
}

private bool ConnectCommandCanExecute
{
    get { return !_instrumentModel.IsConnected; }
}

private void ConnectCommandExecute()
{
    if (TcpSettingsChanged()) SaveTcpSettings();
    _instrumentModel.Connect(_tcpData);
}

RelayClass

One part of making this simple is the RelayClass I have in one of my core library .dlls. I probably got this from one of the videos I watched. This can be cut and pasted in it's entirety, there is nothing here you need to customize, except you'll probably want to change the namespace this is in.

使这个简单的一部分是我在我的一个核心库.dll中的RelayClass。我可能从我看过的一个视频中得到了这个。这可以完整地剪切和粘贴,这里没有你需要自定义的东西,除了你可能想要更改它所在的命名空间。

using System;
using System.Diagnostics;
using System.Windows.Input;

namespace Syncor.MvvmLib
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;

public event EventHandler CanExecuteChanged
{
  add
  {
    CommandManager.RequerySuggested += value;
  }
  remove
  {
    CommandManager.RequerySuggested -= value;
  }
}

public RelayCommand(Action<object> execute)
  : this(execute, (Predicate<object>) null)
{
  this._execute = execute;
}

public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
  if (execute == null)
    throw new ArgumentNullException("execute");
  this._execute = execute;
  this._canExecute = canExecute;
}

[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
  if (this._canExecute != null)
    return this._canExecute(parameter);
  else
    return true;
}

public void Execute(object parameter)
{
  this._execute(parameter);
}
}
}

#2


0  

Why don't you name it "DoubleClickCommand" that way you don't put business logic in your control. And then bind this command to your viewmodel, Like Tod explained. Regarding your code behind, there is a pure xaml solution, to be more precise it involves attached behaviors, but does not need to override a WPF class(which i like to avoid), search for "fire command on event" for example this. One final thing: Code Behind does NOT break MVVM in any way, i wonder where this myth came from. Code behind is perfectly fine! MVVM is to separate view and logic, not telling you where to put your code. Design principles should help, not hinder you.

为什么不将它命名为“DoubleClickCommand”,因为您没有将业务逻辑置于您的控制之下。然后将此命令绑定到您的viewmodel,就像Tod解释的那样。关于你的代码背后,有一个纯xaml解决方案,更准确地说它涉及附加行为,但不需要覆盖WPF类(我想避免),搜索“事件上的fire命令”,例如这个。最后一件事:Code Behind不会以任何方式打破MVVM,我想知道这个神话来自哪里。代码背后完全没问题! MVVM是分离视图和逻辑,而不是告诉你在哪里放置你的代码。设计原则应该有所帮助,而不是阻碍你。