Implement custom copy and paste in WPF DataGrid, which works when there are no rows in it

I need to implement a custom copy + cut + paste for data (not text or CSV) that needs to be copied between grids in a WPF application. Using standard ApplicationCommands and defining CommandBinding works very well, but only if the DataGrid contains at least 1 row of data and when it is selected. When there are no lines or focus is not on any of them, all commands are disabled.

To fix the problem, I tried calling CommandManager.InvalidateRequerySposed () and setting Focusable = true and / or FocusManager.IsFocusScope = true in the DataGrid, but it seems that the DataGrid as a whole is “not interested” in processing copy / paste operations, only its rows represent CanExecute re-query commands and invoke Execute accordingly. It also ignores KeyBindings.

How to force the DataGrid handler to request ApplicationCommands?

Please find an example on which I tested the problem below:

<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> 

And the code behind:

 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>(); } } 

Edit

Ayyappan Subramanian's code-only solution is the closest to the application in which it will be used. In the end, since I already inherit the grid, because it has its own copy + paste format, I added code that ensures that the focus is in the grid in 3 cases:

  • The context menu is displayed.
  • The user clicks in the grid area (empty) when the child images have no focus.
  • (our specific case for the application) The user clicks on the TreeView selector navigation, which is then focused on the grid, so the shortcuts will work immediately.

Relevant Code:

 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(); } } } 
+6
source share
2 answers

When ContextMenu opens, you can set the focus to the grid, which will include all the menu items. A good explanation is given here http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

Also for the implementation of the paste, refer to the link SO post WPF datagrid

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 # code:

 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(); } } 
+5
source

Here is my version of VB.NET. It takes a value and then fills it into all selected cells in the datagrid.

 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 
0
source

Source: https://habr.com/ru/post/985125/


All Articles