WPF 命令绑定只工作几次

WPF command binding works only a few times

我创建了一个名为 AnswerUserControl 的用户控件。 XAML 文件看起来像这样(只有按钮):

<UserControl
    ...>

    <Button
        DataContext="{Binding Path=ViewModel, 
                      RelativeSource={
                          RelativeSource Mode=FindAncestor,
                          AncestorType={x:Type UserControl}
                      }}"
        Command="{Binding Path=ClickCommand}"
        Content="{Binding Path=Reply}" />

</UserControl>

cs 具有依赖性的文件 属性 到 AnswerViewModel:

public partial class AnswerUserControl : UserControl
{
    public AnswerUserControl()
    {
        InitializeComponent();
    }

    public AnswerViewModel ViewModel
    {
        get
        {
            return (AnswerViewModel)GetValue(ViewModelProperty);
        }
        set
        {
            SetValue(ViewModelProperty, value);
        }
    }

    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(AnswerViewModel), typeof(AnswerUserControl));
}

AnswerViewModel class:

public class AnswerViewModel : ViewModelBase
{
    private string reply;

    public string Reply
    {
        get
        {
            return reply;
        }
        set
        {
            reply = value;
            RaisePropertyChanged(nameof(Reply));
        }
    }

    private ICommand clickCommand;

    public ICommand ClickCommand
    {
        get
        {
            return clickCommand;
        }
        set
        {
            clickCommand = value;
            RaisePropertyChanged(nameof(ClickCommand));
        }
    }

    public AnswerViewModel(Action<int> click, string reply, int index)
    {
        void Click()
        {
            Console.WriteLine($"AnswerViewModel click at {index}");
            click(index);
        }

        Reply = reply;
        ClickCommand = new RelayCommand(Click);
    }
}

在 Window 中,我像这样添加这个 UserContol:

<StackPanel>
    <local:AnswerUserControl
        ViewModel="{Binding Path=VMA}" />
</StackPanel>

VMA 是 Window 的 ViewModel 中的 AnswerViewModel

private AnswerViewModel vma;

public AnswerViewModel VMA
{
    get
    {
        return vma;
    }
    set
    {
        vma = value;
        RaisePropertyChanged(nameof(VMA));
    }
}

static int number = 1;

public HomeViewModel()
{
    VMA = new AnswerViewModel(x => { Console.WriteLine($"Click Number: {number++}"); }, "Reply", 0);
}

我面临的问题是 ClickCommand 只执行了几次。当我在按钮上缓慢单击时,它会执行 4 到 6 次,当我快速单击时,它会执行 20 多次,然后停止工作。我哪里出错了?

我写了一个关于如何实现它的粗略版本。对于主窗口,我们可以使用以下代码。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainWindowDataContext();
    }
}

如上所示,我们为主窗口分配了一个视图模型,在这个视图模型中,我们可以为用户控件定义一个数据上下文。给我们以下代码;

public class MainWindowDataContext : INotifyPropertyChanged
{
    public MainWindowDataContext()
    {
        newDatacontext = new DataContextTest();

    }
    private DataContextTest _newDatacontext;

    public DataContextTest newDatacontext
    {
        get => _newDatacontext;
        set
        {
            if(_newDatacontext == value)
                return;
            _newDatacontext = value;
            OnPropertyChanged(nameof(newDatacontext));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

不要介意接口或 getter/setter,这纯粹是为了演示如何通知绑定 属性 已更改。

在 MainWindowDataContext 中,我们创建了一个 DataContextTest class。这个 viewmodel/datacontext class 包含我们的命令并且可以包含用户控件的其他绑定。

public class DataContextTest
{
    public ICommand ButtonCommand => new RelayCommand(executeThis);


    private void executeThis()
    {
        Console.WriteLine($"VM clicked ");
    }
}

对于 xaml 代码和绑定所在的前端,我写了以下内容; 一个带有按钮的用户控件,我在主窗口中插入了用户控件。

<Window x:Class="Tryout.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:Tryout"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Grid>
    <local:UserControl1 DataContext="{Binding newDatacontext,UpdateSourceTrigger=PropertyChanged}"></local:UserControl1>
</Grid>

主窗口使用 MainWindowDataContext,这是在构造函数中分配的(参见代码块 1)。 MainWindowDataContext 有一个 getter setter 用于需要数据上下文的用户控件。当数据上下文更改时,userControl 会收到通知。

查看用户控件时 xaml 我们看到以下内容

<UserControl x:Class="Tryout.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Tryout"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Grid>
    <Button Command="{Binding ButtonCommand}" Width="200" Height="100" Content="test"/>
</Grid>

使用所有这些代码,它解析为视图模型的以下层次结构。

MainWindow -> MainWindowDataContext -> DataContextTest。

因为 MainWindow 使用 UserControl,所以我们需要为 MainWindow 定义一个视图模型。设置完成后,您可以通过在前端绑定将 datacontext/viewmodel 分配给用户控件。

不需要依赖属性,在大多数情况下数据上下文就足够了 (: