watkipet
watkipet

Reputation: 1059

How do I implement a clipboard copy on a WPF DataGrid using MVVM?

I have a DataGrid backed by an ObservableCollection of objects in my ViewModel. I also have a context menu with a Copy entry which uses the default Copy command. I'd like to be able to copy data from the DataGrid, but when I click on the Copy menu item, WPF throws this exception:

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>

I've seen other descriptions of this exception. However:


More Information - 10/17/2019

I did some more digging after Abin Mathew posted his answer. While his answer is an excellent one, and it works--it doesn't use WPF Commanding, but rather uses RelayCommand. That's fine. This question doesn't specify that WPF Commanding must be used.

However, I still wanted to know why WPF Commanding Copy wasn't working for a DataGrid. In fact, it does work--it just depends on timing. If you run the exact code I posted above, but put breakpoints at System.Windows.Clipbard.Flush and System.Windows.Controls.DataGrid.OnExecutedCopy, and then click the run button each time the breakpoints are hit, the copy will succeed:

Successful copy using breakpoints

So in conclusion:

  1. RelayCommand works for performing a copy.
  2. WPF Commanding also "works".
  3. WPF Commanding manages getting the data destined for the clipboard from the selected DataGrid rows itself.
  4. There's some sort of race condition going on that is causing WPF Commanding to fail to get access to Flush the clipboard when copying from a DataGrid.
  5. Either I need more code to prevent this race condition, or Microsoft has introduced a bug breaking WPF Commanding Copy from a DataGrid.

Upvotes: 0

Views: 4576

Answers (1)

Abin
Abin

Reputation: 2956

ApplicationCommands like Cut & Copy only act on Selection.if you are not able to select text like in TextBox then it will throw exception.

I am afraid that WPF introduced a new Bug

for example like below

    <TextBox>
        <TextBox.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Paste" Command="ApplicationCommands.Paste" />                    
                <MenuItem Header="Copy" Command="ApplicationCommands.Copy" />
            </ContextMenu>
        </TextBox.ContextMenu>
    </TextBox>

You can use copy on DataGrid without selection by adding ICommand and Binding the Command Like below

<MenuItem Header="Copy" Command="{Binding CopyCommand}" CommandParameter="{Binding}"

And the ViewModel will be

        public ICommand CopyCommand => new RelayCommand<object>(Copy);

        private static void Copy(object obj)
        {
            Clipboard.SetDataObject(((ViewModel)obj).People);
        }

This will copy the collection of People to the Clipboard. If that is what you are trying to do.

Hope this helps.

Upvotes: 1

Related Questions