在 WPF DataGrid 中实现自定义复制和粘贴,当其中没有行时有效

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29630671/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:04:52  来源:igfitidea点击:

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

c#wpfwpfdatagridcommandbindingrouted-commands

提问by too

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.

我需要为要在 WPF 应用程序中的网格之间复制的数据(不是文本或 CSV)实现自定义复制 + 剪切 + 粘贴。使用标准的 ApplicationCommands 和定义 CommandBinding 非常有效,但前提是 DataGrid 包含至少 1 行数据并且当它被选中时。当没有行或焦点不在其中任何行上时,所有命令都将被禁用。

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.

为了解决这个问题,我尝试调用 CommandManager.InvalidateRequerySuggested() 并在 DataGrid 上设置 Focusable=true 和/或 FocusManager.IsFocusScope=true 但在内部看来 DataGrid 作为一个整体对处理复制/粘贴操作“不感兴趣”,只有它行是重新查询命令 CanExecute 状态并相应地调用 Execute。它还忽略 KeyBindings。

How to make DataGrid handle requerying ApplicationCommands?

如何让 DataGrid 处理重新查询 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:

Ayyappan Subramanian 的纯代码解决方案与将要使用的应用程序最接近。最终因为我已经继承了网格,因为它有自定义的复制+粘贴格式可以处理,我添加了一些代码以确保焦点位于3种情况下的网格:

  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.
  1. 显示上下文菜单
  2. 当子视觉对象没有焦点时,用户在网格(空)区域中单击
  3. (我们的应用程序特定案例)用户单击网格导航 TreeView,然后将焦点移至网格,因此快捷方式将立即生效。

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

采纳答案by Ayyappan Subramanian

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

当 ContextMenu 打开时,您可以将焦点设置到网格上,这将启用所有菜单项。这里给出了很好的解释http://www.wpftutorial.net/RoutedCommandsInContextMenu.html

Also for implementing the paste refer the SO post WPF datagrid pasting

同样为了实现粘贴,请参考 SO post WPF datagrid pasteing

WPF:

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:

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

}

回答by Thomas Bailey

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

这是我的 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