如何使用 MVVM 在 WPF DataGrid 上实现剪贴板副本?

How do I implement a clipboard copy on a WPF DataGrid using MVVM?

我的 ViewModel 中有一个由对象的 ObservableCollection 支持的 DataGrid。我还有一个上下文菜单,其中包含一个使用默认复制命令的复制条目。我希望能够 copy data from the DataGrid,但是当我单击“复制”菜单项时,WPF 抛出此异常:

OpenClipboard Failed (Exception from HRESULT: 0x800301D0 (CLIPBRD_E_CANT_OPEN))

ViewModel

public class ViewModel
{
  public class Person
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
  }

  public ObservableCollection<Person> People { get; set; }

  public ViewModel()
  {
    People = new ObservableCollection<Person>
    {
      new Person {FirstName = "Heir", LastName = "Band"},
      new Person {FirstName = "Rose", LastName = "Anne"},
      new Person {FirstName = "Tim", LastName = "Poral"}
    };
  }
}

XAML

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Grid>
        <DataGrid ItemsSource="{Binding Path=People}">
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Copy" Command="Copy" />
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </Grid>
</Window>

我看过其他 descriptions of this exception。然而:


更多信息 - 2019 年 10 月 17 日

我在 Abin Mathew posted . While his answer is an excellent one, and it works--it doesn't use WPF Commanding 之后做了更多的挖掘,而是使用了 RelayCommand。没关系。这个问题没有指定必须使用 WPF 命令。

但是,我仍然想知道为什么 WPF 命令复制不适用于 DataGrid。事实上,它确实有效——只是取决于时机。如果您 运行 我上面发布的确切代码,但在 System.Windows.Clipbard.FlushSystem.Windows.Controls.DataGrid.OnExecutedCopy 处放置断点,然后每次遇到断点时单击 运行 按钮,副本将成功:

总之:

  1. RelayCommand 用于执行复制。
  2. WPF 命令也 "works"。
  3. WPF 命令管理从选定的 DataGrid 行本身获取发往剪贴板的数据。
  4. 存在某种竞争条件,导致 WPF 命令在从 DataGrid 复制时无法访问刷新剪贴板。
  5. 要么我需要更多代码来防止这种竞争情况,要么 Microsoft 引入了一个错误来破坏 WPF 从 DataGrid 命令复制。

<s><code>ApplicationCommands 就像剪切和复制只作用于 Selection。如果你不能像 TextBox 那样 select 文本那么它会抛出异常。

恐怕WPF推出了新的Bug

例如如下

    <TextBox>
        <TextBox.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Paste" Command="ApplicationCommands.Paste" />                    
                <MenuItem Header="Copy" Command="ApplicationCommands.Copy" />
            </ContextMenu>
        </TextBox.ContextMenu>
    </TextBox>

您可以在没有 selection 的 DataGrid 上使用副本,方法是添加 ICommandBinding Command,如下所示

<MenuItem Header="Copy" Command="{Binding CopyCommand}" CommandParameter="{Binding}"

ViewModel 将是

        public ICommand CopyCommand => new RelayCommand<object>(Copy);

        private static void Copy(object obj)
        {
            Clipboard.SetDataObject(((ViewModel)obj).People);
        }

这会将 People 的集合复制到 Clipboard。如果那是你想要做的。

希望对您有所帮助。