在 WPF DataGrid 中实现自定义复制和粘贴,这在其中没有行时有效
Implement custom Copy and Paste in WPF DataGrid which works when there are no rows in it
我需要为要在 WPF 应用程序中的网格之间复制的数据(不是文本或 CSV)实现自定义复制 + 剪切 + 粘贴。使用标准 ApplicationCommand 和定义 CommandBinding 效果非常好,但前提是 DataGrid 至少包含 1 行数据并且选中它。当没有行或焦点不在任何行上时,所有命令都将被禁用。
为了解决这个问题,我尝试调用 CommandManager.InvalidateRequerySuggested() 并在 DataGrid 上设置 Focusable=true and/or FocusManager.IsFocusScope=true 但似乎在内部 DataGrid 作为一个整体是 "not interested" 在处理 Copy/Paste 操作时,只有它的行是重新查询命令 CanExecute 状态并相应地调用 Execute。它还会忽略 KeyBindings。
如何使 DataGrid 处理重新查询 ApplicationCommands?
请在下面找到我测试问题的示例:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DataGrid x:Name="TheGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding}"/>
</DataGrid.Columns>
<DataGrid.InputBindings>
<KeyBinding Key="A" Command="{x:Static ApplicationCommands.New}"/>
</DataGrid.InputBindings>
<DataGrid.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Paste}" CanExecute="CanPaste" Executed="Paste"/>
<CommandBinding Command="{x:Static ApplicationCommands.Copy}" CanExecute="CanCopy" Executed="Copy"/>
<CommandBinding Command="{x:Static ApplicationCommands.New}" CanExecute="CanAddNew" Executed="AddNew"/>
</DataGrid.CommandBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Copy}" Header="Copy"/>
<MenuItem Command="{x:Static ApplicationCommands.Paste}" Header="Paste"/>
<MenuItem Command="{x:Static ApplicationCommands.New}" Header="New row"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Window>
后面的代码:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
TheGrid.ItemsSource = numbers;
// Following line enables commands when row is selected
numbers.Add(0);
}
private void Copy(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetData(DataFormats.Text, string.Join(",", numbers));
}
private void CanCopy(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = numbers.Count > 0;
}
private void CanPaste(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = numbers.Count > 0;
e.Handled = true;
}
private void Paste(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
private void CanAddNew(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void AddNew(object sender, ExecutedRoutedEventArgs e)
{
numbers.Add(numbers.Count);
}
private readonly ICollection<int> numbers = new ObservableCollection<int>();
}
}
编辑
Ayyappan Subramanian 的纯代码解决方案是最接近将在其中使用的应用程序的解决方案。最终,由于我已经继承了网格,因为它具有自定义的复制+粘贴格式,所以我添加了一些代码来确保在 3 种情况下,焦点位于网格内:
- 显示上下文菜单
- 当子视觉对象没有焦点时,用户在网格(空白)区域中单击
- (我们的应用程序特定案例)用户单击网格导航 TreeView,然后将焦点带到网格,因此快捷方式将立即起作用。
相关代码:
public class MyDataGrid: DataGrid
{
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
base.OnContextMenuOpening(e);
Focus();
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if(e.ChangedButton == MouseButton.Left && !IsKeyboardFocusWithin)
{
Focus();
}
}
}
当上下文菜单打开时,您可以将焦点设置到网格,这将启用所有菜单项。这里给出了很好的解释 http://www.wpftutorial.net/RoutedCommandsInContextMenu.html
也可以参考 SO post WPF datagrid pasting
来实现粘贴
WPF:
<DataGrid x:Name="TheGrid" CanUserAddRows="True"
ContextMenuOpening="TheGrid_ContextMenuOpening">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding}"/>
</DataGrid.Columns>
<DataGrid.InputBindings>
<KeyBinding Key="A" Command="{x:Static ApplicationCommands.New}"/>
</DataGrid.InputBindings>
<DataGrid.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Paste}"
CanExecute="CanPaste" Executed="Paste"/>
<CommandBinding Command="{x:Static ApplicationCommands.Copy}"
CanExecute="CanCopy" Executed="Copy"/>
<CommandBinding Command="{x:Static ApplicationCommands.New}"
CanExecute="CanAddNew" Executed="AddNew"/>
</DataGrid.CommandBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Copy}" Header="Copy"/>
<MenuItem Command="{x:Static ApplicationCommands.Paste}" Header="Paste"/>
<MenuItem Command="{x:Static ApplicationCommands.New}" Header="New row"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
C#代码:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TheGrid.ItemsSource = numbers;
}
private void Copy(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetData(DataFormats.Text, string.Join(",", numbers));
}
private void CanCopy(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CanPaste(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void Paste(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
private void CanAddNew(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void AddNew(object sender, ExecutedRoutedEventArgs e)
{
numbers.Add(numbers.Count);
}
private readonly ICollection<int> numbers = new ObservableCollection<int>();
private void TheGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
TheGrid.Focus();
}
}
这是我的 VB.NET 版本。它获取一个值,然后将其填充到数据网格中的所有选定单元格中。
Private Sub gridpaste(ByVal pasteValue As String)
Dim rowInd As Integer = Nothing
Dim colind As Integer = Nothing
Dim add As Integer = 1
For Each c As DataGridCellInfo In LineListDataGrid.SelectedCells
colind = c.Column.DisplayIndex
rowInd = GetRowIndex(LineListDataGrid, c)
Try
LLDB.LineList.Rows(rowInd)(colind) = pasteValue
Catch err As Exception
MessageBox.Show(err.Message)
End Try
Next
End Sub
Public Shared Function GetRowIndex(dataGrid As DataGrid, dataGridCellInfo As DataGridCellInfo) As Integer
Dim dgrow As DataGridRow = DirectCast(dataGrid.ItemContainerGenerator.ContainerFromItem(dataGridCellInfo.Item), DataGridRow)
If dgrow IsNot Nothing Then
Return dgrow.GetIndex()
Else
Return -1
End If
End Function
我需要为要在 WPF 应用程序中的网格之间复制的数据(不是文本或 CSV)实现自定义复制 + 剪切 + 粘贴。使用标准 ApplicationCommand 和定义 CommandBinding 效果非常好,但前提是 DataGrid 至少包含 1 行数据并且选中它。当没有行或焦点不在任何行上时,所有命令都将被禁用。
为了解决这个问题,我尝试调用 CommandManager.InvalidateRequerySuggested() 并在 DataGrid 上设置 Focusable=true and/or FocusManager.IsFocusScope=true 但似乎在内部 DataGrid 作为一个整体是 "not interested" 在处理 Copy/Paste 操作时,只有它的行是重新查询命令 CanExecute 状态并相应地调用 Execute。它还会忽略 KeyBindings。
如何使 DataGrid 处理重新查询 ApplicationCommands?
请在下面找到我测试问题的示例:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DataGrid x:Name="TheGrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding}"/>
</DataGrid.Columns>
<DataGrid.InputBindings>
<KeyBinding Key="A" Command="{x:Static ApplicationCommands.New}"/>
</DataGrid.InputBindings>
<DataGrid.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Paste}" CanExecute="CanPaste" Executed="Paste"/>
<CommandBinding Command="{x:Static ApplicationCommands.Copy}" CanExecute="CanCopy" Executed="Copy"/>
<CommandBinding Command="{x:Static ApplicationCommands.New}" CanExecute="CanAddNew" Executed="AddNew"/>
</DataGrid.CommandBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Copy}" Header="Copy"/>
<MenuItem Command="{x:Static ApplicationCommands.Paste}" Header="Paste"/>
<MenuItem Command="{x:Static ApplicationCommands.New}" Header="New row"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Window>
后面的代码:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
TheGrid.ItemsSource = numbers;
// Following line enables commands when row is selected
numbers.Add(0);
}
private void Copy(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetData(DataFormats.Text, string.Join(",", numbers));
}
private void CanCopy(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = numbers.Count > 0;
}
private void CanPaste(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = numbers.Count > 0;
e.Handled = true;
}
private void Paste(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
private void CanAddNew(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void AddNew(object sender, ExecutedRoutedEventArgs e)
{
numbers.Add(numbers.Count);
}
private readonly ICollection<int> numbers = new ObservableCollection<int>();
}
}
编辑
Ayyappan Subramanian 的纯代码解决方案是最接近将在其中使用的应用程序的解决方案。最终,由于我已经继承了网格,因为它具有自定义的复制+粘贴格式,所以我添加了一些代码来确保在 3 种情况下,焦点位于网格内:
- 显示上下文菜单
- 当子视觉对象没有焦点时,用户在网格(空白)区域中单击
- (我们的应用程序特定案例)用户单击网格导航 TreeView,然后将焦点带到网格,因此快捷方式将立即起作用。
相关代码:
public class MyDataGrid: DataGrid
{
protected override void OnContextMenuOpening(ContextMenuEventArgs e)
{
base.OnContextMenuOpening(e);
Focus();
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
if(e.ChangedButton == MouseButton.Left && !IsKeyboardFocusWithin)
{
Focus();
}
}
}
当上下文菜单打开时,您可以将焦点设置到网格,这将启用所有菜单项。这里给出了很好的解释 http://www.wpftutorial.net/RoutedCommandsInContextMenu.html
也可以参考 SO post WPF datagrid pasting
来实现粘贴WPF:
<DataGrid x:Name="TheGrid" CanUserAddRows="True"
ContextMenuOpening="TheGrid_ContextMenuOpening">
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding}"/>
</DataGrid.Columns>
<DataGrid.InputBindings>
<KeyBinding Key="A" Command="{x:Static ApplicationCommands.New}"/>
</DataGrid.InputBindings>
<DataGrid.CommandBindings>
<CommandBinding Command="{x:Static ApplicationCommands.Paste}"
CanExecute="CanPaste" Executed="Paste"/>
<CommandBinding Command="{x:Static ApplicationCommands.Copy}"
CanExecute="CanCopy" Executed="Copy"/>
<CommandBinding Command="{x:Static ApplicationCommands.New}"
CanExecute="CanAddNew" Executed="AddNew"/>
</DataGrid.CommandBindings>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="{x:Static ApplicationCommands.Copy}" Header="Copy"/>
<MenuItem Command="{x:Static ApplicationCommands.Paste}" Header="Paste"/>
<MenuItem Command="{x:Static ApplicationCommands.New}" Header="New row"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
C#代码:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TheGrid.ItemsSource = numbers;
}
private void Copy(object sender, ExecutedRoutedEventArgs e)
{
Clipboard.SetData(DataFormats.Text, string.Join(",", numbers));
}
private void CanCopy(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void CanPaste(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void Paste(object sender, ExecutedRoutedEventArgs e)
{
Close();
}
private void CanAddNew(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
e.Handled = true;
}
private void AddNew(object sender, ExecutedRoutedEventArgs e)
{
numbers.Add(numbers.Count);
}
private readonly ICollection<int> numbers = new ObservableCollection<int>();
private void TheGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
TheGrid.Focus();
}
}
这是我的 VB.NET 版本。它获取一个值,然后将其填充到数据网格中的所有选定单元格中。
Private Sub gridpaste(ByVal pasteValue As String)
Dim rowInd As Integer = Nothing
Dim colind As Integer = Nothing
Dim add As Integer = 1
For Each c As DataGridCellInfo In LineListDataGrid.SelectedCells
colind = c.Column.DisplayIndex
rowInd = GetRowIndex(LineListDataGrid, c)
Try
LLDB.LineList.Rows(rowInd)(colind) = pasteValue
Catch err As Exception
MessageBox.Show(err.Message)
End Try
Next
End Sub
Public Shared Function GetRowIndex(dataGrid As DataGrid, dataGridCellInfo As DataGridCellInfo) As Integer
Dim dgrow As DataGridRow = DirectCast(dataGrid.ItemContainerGenerator.ContainerFromItem(dataGridCellInfo.Item), DataGridRow)
If dgrow IsNot Nothing Then
Return dgrow.GetIndex()
Else
Return -1
End If
End Function