如何使用 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。然而:
- 我使用的是 Visual Studio 2017,而不是 2019。
- 我没有使用 SetText 或 SetDataObject,也没有为此调用任何东西。
- 异常每次都会发生,而不是间歇性发生,无论我 运行ning 是否来自 Visual Studio。
- 我对 "myDataGrid_CopyingRowClipboardContent" 等代码隐藏不感兴趣,因为我使用的是 MVVM。
更多信息 - 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.Flush
和 System.Windows.Controls.DataGrid.OnExecutedCopy
处放置断点,然后每次遇到断点时单击 运行 按钮,副本将成功:
总之:
- RelayCommand 用于执行复制。
- WPF 命令也 "works"。
- WPF 命令管理从选定的 DataGrid 行本身获取发往剪贴板的数据。
- 存在某种竞争条件,导致 WPF 命令在从 DataGrid 复制时无法访问刷新剪贴板。
- 要么我需要更多代码来防止这种竞争情况,要么 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
上使用副本,方法是添加 ICommand
和 Binding
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
。如果那是你想要做的。
希望对您有所帮助。
我的 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。然而:
- 我使用的是 Visual Studio 2017,而不是 2019。
- 我没有使用 SetText 或 SetDataObject,也没有为此调用任何东西。
- 异常每次都会发生,而不是间歇性发生,无论我 运行ning 是否来自 Visual Studio。
- 我对 "myDataGrid_CopyingRowClipboardContent" 等代码隐藏不感兴趣,因为我使用的是 MVVM。
更多信息 - 2019 年 10 月 17 日
我在 Abin Mathew posted
但是,我仍然想知道为什么 WPF 命令复制不适用于 DataGrid。事实上,它确实有效——只是取决于时机。如果您 运行 我上面发布的确切代码,但在 System.Windows.Clipbard.Flush
和 System.Windows.Controls.DataGrid.OnExecutedCopy
处放置断点,然后每次遇到断点时单击 运行 按钮,副本将成功:
总之:
- RelayCommand 用于执行复制。
- WPF 命令也 "works"。
- WPF 命令管理从选定的 DataGrid 行本身获取发往剪贴板的数据。
- 存在某种竞争条件,导致 WPF 命令在从 DataGrid 复制时无法访问刷新剪贴板。
- 要么我需要更多代码来防止这种竞争情况,要么 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
上使用副本,方法是添加 ICommand
和 Binding
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
。如果那是你想要做的。
希望对您有所帮助。