Implement custom Copy and Paste in WPF DataGrid which works when there are no rows in it
Asked Answered
I

2

8

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

To fix the problem I tried calling CommandManager.InvalidateRequerySuggested() and setting Focusable=true and/or FocusManager.IsFocusScope=true on the DataGrid but it seems internally DataGrid as a whole is "not interested" in handling Copy/Paste operations, only it's rows are re-querying commands CanExecute state and calling Execute accordingly. It also ignores KeyBindings.

How to make DataGrid handle requerying ApplicationCommands?

Please find the 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

The code only solution by Ayyappan Subramanian is the closest match to the application it will be used in. Eventually as I already inherit the grid because it has custom copy+paste format to work on, I added some code which ensures that focus is within a grid in 3 cases:

  1. Context menu is shown
  2. User clicks in the grid (empty) area when it's child visuals have no focus
  3. (Our app specific case) User clicks on a grid navigation TreeView which then brings focus to the grid so shortcuts will work straight away.

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();
            }
        }
}
Izzard answered 14/4, 2015 at 14:55 Comment(2)
I think it might be due to the line -> e.CanExecute = numbers.Count > 0; try changing the line in CanPaste function to e.CanExecute = true;Biretta
Sorry but no action is enabled without focusing on a row, including ApplicationCommands.New and it has e.CanExecute set always to true.Inconsonant
P
11

When the ContextMenu is openning then you can set the focus to the grid which will enable all the menu items. Good explanation is given in here http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

Also for implementing the paste refer the 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# 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();
    }

}
Phellem answered 17/4, 2015 at 18:56 Comment(2)
Check this link for paste https://mcmap.net/q/724347/-wpf-datagrid-pastingPhellem
Your solution makes most sense considering that context menu steals focus from the grid and re-gaining it every time the context menu is opened will solve the problem probably in every case and did it for me. Thank you.Inconsonant
A
-1

Here is my VB.NET version. 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
Androgen answered 14/9, 2016 at 19:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.