如何在 DataTemplate 中为 Command 和 CommandParameter 设置绑定顺序

How can I set the Binding Order in a DataTemplate for Command and CommandParameter

我正在寻找指定 DataTemplate 绑定的正确方法。

为了正确初始化命令,与命令参数和命令绑定发生的顺序有关。

这里有一个 xaml 和一些源代码来说明这一点。 该示例包含 4 个命令,其中 2 个预计无法正确初始化。 2应该正确初始化。 这两个微不足道的案例表现得符合预期。 这两个 DataTemplate 案例的行为与我预期的不同。

xaml:

<Window x:Class="WPFCommand.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:WPFCommand"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <Button 
            Command="{Binding CMD1}"
            CommandParameter="{Binding Parameter1}"
            >CMD->PAR</Button>
        <Button 
            CommandParameter="{Binding Parameter2}"
            Command="{Binding CMD2}"
            >PAR->CMD</Button>
        <DataGrid
            ItemsSource="{Binding DataGridItemsSource}"
            AutoGenerateColumns="False"
            HeadersVisibility="None"
            >
            <DataGrid.Columns >
                <DataGridTemplateColumn Width="1*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button
                                Command="{Binding CMD4}"
                                CommandParameter="{Binding Parameter4}"
                                >Grid CMD->PAR</Button>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Width="1*">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button
                                CommandParameter="{Binding Parameter3}"
                                Command="{Binding CMD3}"
                                >Grid PAR->CMD</Button>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>

代码隐藏

namespace WPFCommand
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Input;

    public class DemoCommand : ICommand
    {
        bool? oldCanEx;
        private string myname;

        public DemoCommand(string myname)
        {
            this.myname = myname;
        }

        public event EventHandler CanExecuteChanged = delegate { };

        public bool CanExecute(object parameter)
        {
            bool newCanEx;
            if (parameter == null)
            {
                Debug.WriteLine($"{myname} CanExecute called with null");
                newCanEx = false;
            }
            else
            {
                Debug.WriteLine($"{myname} CanExecute called with {parameter}");
                newCanEx = true;
            }

            if (oldCanEx != newCanEx)
            {
                Debug.WriteLine($"{myname} CanExecute changed");
                oldCanEx = newCanEx;
                CanExecuteChanged(this, EventArgs.Empty);
            }
            return newCanEx;
        }

        public void Execute(object parameter)
        {
            Debug.WriteLine($"{myname} Execute {parameter}");
        }
    }

    public class Item
    {
        internal Item() { }
        public DemoCommand CMD3 { get; private set; } = new DemoCommand("CMD3");
        public DemoCommand CMD4 { get; private set; } = new DemoCommand("CMD4");
        public int Parameter3 { get; private set; } = 7;
        public int Parameter4 { get; private set; } = 13;
    }

    public class MainWindowViewModel
    {
        public IEnumerable<Item> DataGridItemsSource { get; private set; } = new List<Item> { new Item() };
        public DemoCommand CMD1 { get; private set; } = new DemoCommand("CMD1");
        public DemoCommand CMD2 { get; private set; } = new DemoCommand("CMD2");
        public int Parameter1 { get; private set; } = 42;
        public int Parameter2 { get; private set; } = 5;
    }

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

CMD1 和 CMD2 在数据模板之外。 CMD3 和 CMD4 在里面。 两者在绑定顺序上表现出相同的差异。

这是调试输出:

CMD1 CanExecute called with null
CMD1 CanExecute changed
CMD1 CanExecute called with null
CMD2 CanExecute called with 5
CMD2 CanExecute changed
CMD2 CanExecute called with 5
CMD4 CanExecute called with null
CMD4 CanExecute changed
CMD4 CanExecute called with null
CMD3 CanExecute called with null
CMD3 CanExecute changed
CMD3 CanExecute called with null

CMD1 未能按预期正确初始化。 CMD2 按预期成功。 CMD4 按预期失败。 CMD3 失败,我没想到。

为什么CMD3的参数没有在Command之前绑定?

为 DataTemplated Command/CommandParameter 绑定编写 xaml 的正确方法是什么(最好不要触及后面的代码)?

可以通过将 DataTemplate 内容放入自己的视图来解决此问题。

使用

<DataTemplate>
    <local:CommandView />
</DataTemplate>

使用 CommandView 作为自定义 UserControl。

<UserControl x:Class="WPFCommand.CommandView"
             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:WPFCommand"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Button
        CommandParameter="{Binding Parameter3}"
        Command="{Binding CMD3}"
        >Grid PAR->CMD</Button>
</UserControl>