基本理解:将带参数的方法绑定到控件

本文关键字:方法 绑定 控件 参数 | 更新日期: 2023-09-27 18:09:10

我有一个绑定到List<MyThing>实例的ListBox的窗口。MyThing类包含一个属性Name,在ListBox中显示。

我也有一个Button,我想在点击它时运行以下方法:

void RunMe(MyThing m) { ... }

作为这个方法的参数,我想使用ListBox中的SelectedItem

我该怎么做?

这是MyWindow类的XAML文件:
<Window x:Class="MyProject.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <ListBox x:Name="lb" ItemsSource="{Binding MyThingList}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="Run command!" Command="{Binding RunCommand}"/>
    </StackPanel>
</Window>

MyWindowDataContext被设置为MyVM的一个实例,定义如下。除了InitializeComponent之外,这个XAML的代码后面是空的。

这是我的ViewModel类MyVM:
public class MyVM
{
    public IList<MyThing> MyThingList { get; private set; }
    public MyVM(IList<MyThing> aThingList)
    {
        MyThingList = aThingList;
        RunCommand = // somehow connect it to method "RunMe"
    }
    public void RunMe(MyThing m) { ... }
    public ICommand RunCommand { get; private set; }
}
有经验的人可能会看到,我真的不知道我在做什么。下面是一些具体的问题:
  1. 为什么我不能简单地将按钮绑定到每当我点击时调用的任何方法?为什么一定是命令,有什么好处?(我知道一个命令可以自动跟踪它的可执行性,当它不能执行时按钮变灰,但现在,我不关心这个。我也知道它可以绑定到一个事件,但似乎这个事件必须在代码背后定义,我想保持空。如果我说错了请指正)

  2. 我如何将我的RunMe方法连接到MyVM的构造函数内的命令RunCommand ?(我试过RunCommand = new RoutedUICommand("RunMe", "RunMe", typeof(MyVm));,但后来按钮变灰了。我也尝试了typeOf(MyWindow)typeof(Window),但它仍然是灰色的)

  3. 假设我得到了这个工作,这样一个按钮点击结果调用方法RunMe。如何将SelectedItemListBox传递到RunMe方法?(我知道我可以将按钮的DataContext更改为DataContext="{Binding Path=SelectedItem, ElementName=lb}",但随后命令可能无法找到,并且我仍然不知道如何使按钮将此选定项目移交给方法。)

  4. 我的方法可能有根本错误吗?我仍然在试图掌握任何东西(GUI编程,事件/命令,WPF, MVVM模式-这对我来说都是新的),所以如果你有更好的方法来做到这一点,请告诉我。

PS:请随意将标题改为更有表现力的内容。我有许多问题,很难归结为一个问题。

基本理解:将带参数的方法绑定到控件

ICommand比简单的方法调用灵活和强大得多。如果你想要一个简单的方法调用(嗯,kinda simple…ish),你可以只处理按钮的Click事件:

protected void RunCommandButton_Click(object sender, RoutedEventArgs args)
{
    var vm = (MyVM)DataContext;
    var selectedThing = lb.SelectedItem as MyThing;
    if (selectedThing != null)
    {
        vm.RunMe(selectedThing);
    }
}

这不是"正确的方式",但有时它是足够好的。我们并不总是需要建造帕台农神庙。有时我们只需要一块防水布来防止雨水溅到柴火上。

如果您正在使用命令(并且您应该学习使用命令),您应该使用DelegateCommand来实现您的命令。WPF应该包含这样的东西,但不幸的是他们没有。他们提供的东西是非常痛苦的,你自己去弄清楚。微软Prism就有一个,不过我还没用过。下面我包含了一个非常简单的非通用版本。

private DelegateCommand _runCommand;
public ICommand RunCommand {
    get {
        if (_runCommand == null)
        {
            _runCommand = new DelegateCommand(RunCommandExecute, ExecuteCanExecute);
        }
        return _runCommand;
    }
}
protected void RunCommandExecute(Object parameter)
{
    //  Do stuff
}
protected bool RunCommandCanExecute(Object parameter)
{
    //  Return true if command can be executed
    return parameter != null;
}

像这样绑定:

<Button 
    Content="Run command!" 
    Command="{Binding RunCommand}"
    CommandParameter="{Binding SelectedItem, ElementName=lb}"
    />

同样,对列表项使用ObservableCollection<T>而不是List<T>。这样,当您添加和删除列表项时,列表框将自动更新。

DelegateCommand.cs

using System;
using System.Windows.Input;
namespace HollowEarth.MVVM
{
    public class DelegateCommand : ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _execute;
        public event EventHandler CanExecuteChanged;
        #region Constructors
        public DelegateCommand(Action<object> execute)
            : this(execute, null)
        {
        }
        public DelegateCommand(Action execute)
            : this(o => execute(), null)
        {
        }
        public DelegateCommand(Action execute, Func<bool> canExecute)
        {
            _execute = o => execute();
            _canExecute = o => canExecute();
        }
        public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }
        #endregion Constructors
        public virtual bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }
            return _canExecute(parameter);
        }
        public virtual void Execute(object parameter)
        {
            _execute(parameter);
        }
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
}
  1. 为什么我不能简单地将按钮绑定到每当我点击时调用的任何方法?为什么一定是命令,有什么好处?

Command模式主要用于MVVM,因为正如你所说,它允许视图和视图模型共享信息,如CanExecute类型的方法;你可以在不使用这种模式的情况下,通过使用它的Click事件,将按钮"绑定"到任何方法。

此外,命令是视图和视图模型之间的抽象;如果你可以使用任何方法,你的视图可以。那么,使用视图模型公开的任何方法。

话虽这么说,这只是一个模式,你没有必须使用它。看看这个关于calburn的例子。Micro,它似乎不使用命令。

  • 我如何将我的RunMe方法连接到MyVM的构造函数中的命令RunCommand ?
  • 我们需要你的iccommand实现来回答这个问题。

  • 假设我得到了这个工作,这样一个按钮点击导致调用RunMe方法。我如何将SelectedItem从列表框传递到RunMe方法?
  • 在MVVM中,考虑数据,而不是视图。
    你应该有一个List(或ObservableCollection,如果它需要实时修改),在你的视图模型中包含的项目,你的ListBox的ItemsSource应该绑定到XAML。同样,在视图模型中你需要一个CurrentItem属性,ListBox的SelectedItem将绑定到它。

    <ListBox ItemsSource={Binding MyList} SelectedItem={Binding CurrentItem}/>

  • 我的方法是否有根本错误?我还在试着掌握任何东西
  • 本身没有错,WPF和MVVM是一个陡峭的学习。你需要一些时间来适应它们。