Wednesday, 25 April 2007

Implementing Copy & Paste between cells in Xceed WPF DataGrid

I am using the Xceed grid in my application to allow the user to enter monetary values into cells. They would like to be able to copy and paste values between cells in the grid, but this is not supported by the grid natively. Support from the Clipboard is very good in WPF (via the System.Windows.Clipboard static class) so this actually is not a particularly difficult thing to implement. My aim is to write a generic reusable solution here that will work for any Xceed grid in my application, rather than a specific one, so I neither want to do any copying or updating of actual data. What we first need to do is register execution handlers for the ApplicationCommands.Copy and the ApplicationCommands.Paste commands against the DataGrid. We then either copy the content of the current cell or paste into it depending on which command is executed. I think the best place to perform generic event handling against a class is in the static initialiser of the App class, like follows:

        static App()
        {
            CommandManager.RegisterClassCommandBinding(typeof(DataGridControl), newCommandBinding(ApplicationCommands.Paste, HandleXceedPasteCommandExecuted)); 
            CommandManager.RegisterClassCommandBinding(typeof(DataGridControl), newCommandBinding(ApplicationCommands.Copy, HandleXceedCopyCommandExecuted)); 
        }
We'll start with the Copy handler. This is quite simple - we first work out which cell has the focus, then copy the textual version of its content to the clipboard.
        private static void HandleXceedCopyCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            DataGridControl dataGridControl = sender as DataGridControl;
 
            if (dataGridControl != null)
            {
                Xceed.Wpf.DataGrid.Row row = (Xceed.Wpf.DataGrid.Row) dataGridControl.ItemContainerGenerator.ContainerFromItem(dataGridControl.CurrentItem);
                Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[dataGridControl.CurrentColumn.Index];
 
                if (currentCell != null && currentCell.HasContent)
                {
                    Clipboard.SetText(currentCell.Content.ToString());
                }
            }
        }
Note that we copy the ToString() of the content to the clipboard, so this technique would not work if the Cell's content was an object whose string was not valid to be entered into a Cell. As we are copying and pasting between Cells, this shouldn't be a problem. Now onto the paste command. The paste command is more tricky and I will explain why. We could just update the content directly, but that would bypass any text input handling and formatting that we may be doing in the Xceed grid. For example, one grid in my application uses the NumericInputScope class to block all non numbers from being typed into the Grid. If we just updated the grid at this point, the PreviewKeyDown and PreviewTextInput would never be fired, therefore we would be allowing invalid characters to be typed into the grid. The solution I came up with for this is to switch the cell into Edit Mode, simulate keypresses for each character being pasted (via raising the TextInput event), and the commit the changes back to the cell. This should ensure that the paste is essentially doing exactly the same thing as it would do if the user pressed the keys themselves! Note we dispatch the work back to the Dispatcher... this is to allow the cell's editor to activate and the cell to get focus before we simulate the key presses.
        public delegate void Callback();
 
        private static void HandleXceedPasteCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            DataGridControl dataGridControl = sender as DataGridControl;
 
            if (dataGridControl != null && Clipboard.ContainsText())
            {
                Xceed.Wpf.DataGrid.Row row = (Xceed.Wpf.DataGrid.Row)dataGridControl.ItemContainerGenerator.ContainerFromItem(dataGridControl.CurrentItem);
                Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[dataGridControl.CurrentColumn.Index];
 
                if (!currentCell.ReadOnly)
                {
                    string text = Clipboard.GetText();
 
                    currentCell.BeginEdit();
 
                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Input, (Callback)delegate
                    {
                        if (Keyboard.FocusedElement != null)
                        {
                            foreach (char c in text)
                            {
                                TextComposition textComposition = new TextComposition(InputManager.Current, Keyboard.FocusedElement, new string(c, 1));
 
                                TextCompositionEventArgs textCompositionEventArgs = new TextCompositionEventArgs(Keyboard.PrimaryDevice, textComposition);
                                textCompositionEventArgs.RoutedEvent = UIElement.TextInputEvent;
 
                                Keyboard.FocusedElement.RaiseEvent(textCompositionEventArgs);
                            }
                        }
 
                        currentCell.EndEdit();
                    });
                }
            }
        }
I'd be interested to know what people think of this method, and if anyone has tried any other solutions to implement copy and paste in the grid let me know. The next requirement is to support selection of and pasting to multiple cells within the Grid. Again, this is not currently supported in the Xceed DataGrid, but I will hopefully come up with a way of doing it!

5 comments:

Anonymous said...

How can i change the content of the cell

I have write the following code (It enter the edit mode, but the content still the same:

currentCell.BeginEdit();
currentCell.Content = new System.Windows.Controls.Button();
currentCell.EndEdit();

Neil Mosafi said...

Hmm, I would have thought setting the content of the cell should just work. I would need more info, you may be better off posting your question to the XCeed forum - http://forums.xceed.com/forums/ShowForum.aspx?ForumID=40

Anonymous said...

How do you get around custom Cell Editors?

Anonymous said...

Hi
Thanks,your article is helping me a lot.
I have only one problem call currentcolumn
Always i am receiving a null value when i do click over the datagrid,right,left,doesn´t matter,a selected row or not doesn´t matter...always null
How can i use the property with that code

void tradesGrid_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridControl dataGridControl = sender as DataGridControl;
if (dataGridControl != null)
{
Xceed.Wpf.DataGrid.Row row = (Xceed.Wpf.DataGrid.Row)dataGridControl.GetContainerFromItem(dataGridControl.CurrentItem);
Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[dataGridControl.CurrentColumn.Index];
//Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[2];
if (currentCell != null && currentCell.HasContent)
{
Clipboard.SetText(currentCell.Content.ToString());
}
}
}

I´m sure is correct due to i probed it with Xceed.Wpf.DataGrid.Cell currentCell = row.Cells[2] and works fine.
But when i´m trying to use the property CurrentColumn i obtain always a null value.
Why,who knows,someone can tell me how to activate that property correctly ?

Thanks in advance,

Leonardo

Neil Mosafi said...

To be honest I really don't know! I haven't used the Xceed datagrid for over a year now, and there's been lots of new versions since I wrote this article.

Can I suggest you ask the question on the Xceed forums?

Regards
Neil