在WPF DataGrid中按Enter键将焦点移动到下一个单元格?

15

我想要一个自定义的DataGrid,它能够:

  1. 在编辑模式下按下Enter键时,移动到下一个单元格。
  2. 当到达当前行的最后一列时,焦点应该移动到下一行的第一个单元格。
  3. 当到达下一个单元格时,如果单元格可编辑,它应该自动变为可编辑状态。
  4. 如果单元格包含一个ComboBox(不是comboboxcolumn),则应该打开DropDown。

请帮助我实现这个功能。我已经尝试了几天创建自定义DataGrid并编写了一些代码。

protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)

但是我失败了。

7个回答

14

一个更简单的实现。思路是在捕获到按键事件时,如果按下的键是“Enter”,那么就移动到下一个选项卡,即网格的下一个单元格。

/// <summary>
/// On Enter Key, it tabs to into next cell.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void DataGrid_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
    var uiElement = e.OriginalSource as UIElement;
    if (e.Key == Key.Enter && uiElement != null)
    {
        e.Handled = true;
        uiElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
    }
}

1
我在处理你的代码时遇到的问题是,我有多个DataGridTemplateColumn,每个列中都有一个文本框...代码将焦点移动到下一个单元格/文本框,但不完全,我的意思是文本框会出现外部黑色斑点/线条。我必须按两次回车键才能完全聚焦到下一个单元格/文本框...有什么解决方法吗? - Aousaf Rashid

6
private void dg_PreviewKeyDown(object sender, KeyEventArgs e)
{
    try
    {
        if (e.Key == Key.Enter)
        {
            e.Handled = true;
            var cell = GetCell(dgIssuance, dgIssuance.Items.Count - 1, 2);
            if (cell != null)
            {
                cell.IsSelected = true;
                cell.Focus();
                dg.BeginEdit();
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox(ex.Message, "Error", MessageType.Error);
    }
}  

public static DataGridCell GetCell(DataGrid dg, int row, int column)
{
    var rowContainer = GetRow(dg, row);

    if (rowContainer != null)
    {
        var presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
        if (presenter != null)
        {
            // try to get the cell but it may possibly be virtualized
            var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
            if (cell == null)
            {
                // now try to bring into view and retreive the cell
                dg.ScrollIntoView(rowContainer, dg.Columns[column]);
                cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
            }
            return cell;
        }
    }
    return null;
}

我在几个项目中做过类似的事情,代码非常相似。我只是扩展了datagrid并封装了你正在做的大部分内容。所以我可以保证这个/你的答案绝对很好用!+1 - denis morozov
19
很高兴能看到缺失代码:GetRow()GetVisualChild<T>()以及什么是dgIssuance - bitbonk
1
为什么在你的GetCell函数中传递硬编码值???问题是关于按Enter键移动到下一个单元格,但你正在尝试在Enter点击时获取第二列中的最后一个单元格。为什么? - AndreyS

6
这个方案怎么样?通过设置 Handled=true 来取消按下 Enter 键的操作,并按下 Tab 键。
public Constructor()
{
  InitializeComponent(); 
  this.SampleDataGrid.PreviewKeyDown += MoveCellOnEnterKey;
}

private void MoveCellOnEnterKey(object sender, KeyEventArgs e)
{
  if(e.Key == Key.Enter)
  {
    // Cancel [Enter] key event.
    e.Handled = true;
    // Press [Tab] key programatically.
    var tabKeyEvent = new KeyEventArgs(
      e.KeyboardDevice, e.InputSource, e.Timestamp, Key.Tab);
    tabKeyEvent.RoutedEvent = Keyboard.KeyDownEvent;
    InputManager.Current.ProcessInput(tabKeyEvent);
  }
}

2
public class DataGrid : System.Windows.Controls.DataGrid
{
    private void PressKey(Key key)
    {
        KeyEventArgs args = new KeyEventArgs(Keyboard.PrimaryDevice, Keyboard.PrimaryDevice.ActiveSource, 0, key);
        args.RoutedEvent = Keyboard.KeyDownEvent;
        InputManager.Current.ProcessInput(args);
    }
    protected override void OnCurrentCellChanged(EventArgs e)
    {
        if (this.CurrentCell.Column != null)                
            if (this.CurrentCell.Column.DisplayIndex == 2)
            {

                if (this.CurrentCell.Item.ToString() == "--End Of List--")
                {
                    this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
                }
            }
            else if (this.CurrentCell.Column != null && this.CurrentCell.Column.DisplayIndex == this.Columns.Count() - 1)
            {
                PressKey(Key.Return);
                DataGridCell cell = DataGridHelper.GetCell(this.CurrentCell);
                int index = DataGridHelper.GetRowIndex(cell);
                DataGridRow dgrow = (DataGridRow)this.ItemContainerGenerator.ContainerFromItem(this.Items[index]);
                dgrow.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
            }
    }
    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            DataGridRow rowContainer = (DataGridRow)this.ItemContainerGenerator.ContainerFromItem(this.CurrentItem);
            if (rowContainer != null)
            {
                int columnIndex = this.Columns.IndexOf(this.CurrentColumn);
                DataGridCellsPresenter presenter = UIHelper.GetVisualChild<DataGridCellsPresenter>(rowContainer);
                if (columnIndex == 0)
                {
                    DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
                    TraversalRequest request = new TraversalRequest(FocusNavigationDirection.Next);
                    request.Wrapped = true;
                    cell.MoveFocus(request);
                    BeginEdit();
                    PressKey(Key.Down);
                }
                else
                {
                    CommitEdit();
                    DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
                    TraversalRequest request = new TraversalRequest(FocusNavigationDirection.Next);
                    request.Wrapped = true;
                    cell.MoveFocus(request);
                }
                this.SelectedItem = this.CurrentItem;
                e.Handled = true;
                this.UpdateLayout();
            }
        }
    }
}

目前为止,我已经写了这个并且它对我有效。


当我扩展DataGrid类时,出现错误“错误137:未找到类型'my:DataGridComboBoxColumn'。请验证您是否没有丢失程序集引用,并且所有引用的程序集都已构建。”,这是怎么回事? - Mussammil

0
    Public Sub SendKey(ByVal key As Key)
     Dim args As New KeyEventArgs(Keyboard.PrimaryDevice, Keyboard.PrimaryDevice.ActiveSource, 0, key)
        args.RoutedEvent = Keyboard.KeyDownEvent
        InputManager.Current.ProcessInput(args)
    End Sub
Private Sub dataGrid_PreviewKeyDown(sender As Object, e As KeyEventArgs) Handles dataGrid.PreviewKeyDown
        Dim i As UIElement = e.OriginalSource
        Dim DG As DataGrid = sender
        If (e.Key = Key.Enter Or e.Key = Key.Return) AndAlso i IsNot Nothing Then
            MyBase.OnKeyDown(e)
            DG.CommitEdit()
            SendKey(Key.Tab)
            e.Handled = True
        End If
    End Sub

0
关键方法是 'dataGrid.SetKeyboardFocusToCell'。所以我们可以附加KeyDown事件:
    private void dataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        DataGridTemplateColumn col = (DataGridTemplateColumn)dataGrid.CurrentColumn;
        if (col != null)
        {
            switch (col.SortMemberPath)
            {
                case "From":
                    if (e.Key == Key.Enter && Keyboard.Modifiers == ModifierKeys.None) // Pure Enter
                    {
                        e.Handled = true;
                        int columnIndex = dataGrid.GetColumnIndex(colTo);
                        DataGridRow currentRow = dataGrid.GetRow(dataGrid.CurrentItem);

                        dataGrid.SetKeyboardFocusToCell(dataGrid.CurrentItem, columnIndex);
                        Dispatcher.Invoke(() =>
                        {
                            GridTimeSpanBox timeSpanBox = VisualTree.FindChild<GridTimeSpanBox>(currentRow, tsb => tsb.Name == "tsbTo", true);
                            timeSpanBox.SelectAll();
                        }, System.Windows.Threading.DispatcherPriority.ContextIdle);
                    }
                    break;
            }
        } // col != null
    }

    /// <summary>
    /// Get the row container that holds the 'item'
    /// </summary>
    public DataGridRow GetRow(object item)
    {
        return (DataGridRow)ItemContainerGenerator.ContainerFromItem(item);
    }

    /// <summary>
    /// Gets the index of a 'DataGridColum' or 'DataGridTemplateColumn' in the 'Columns' list. This doesn't change if the user
    /// reorders the columns.
    /// </summary>
    public int GetColumnIndex(DataGridColumn column)
    {
        return this.Columns.IndexOf(column);
    }

在这个例子中,下面的框中的文本也被选中了。对于大部分时间内容被新输入替换的领域来说,这可能很有用。
需要注意的是,通常在 'dataGrid.SetKeyboardFocusToCell()' 之后的操作必须通过调度程序发送,以允许UI完成更新。否则会发生奇怪的事情。
使用这种方案,你甚至可以在当前行后面插入一行。

0
private void dg_ExpenseItem_PreviewKeyDown(object sender, KeyEventArgs e)           
    {
        try
        {
            DataGrid grid = (DataGrid)sender;
            if (e.Key == Key.Enter)
            {
                e.Handled = true;
                var cell = GetCell(grid, grid.SelectedIndex, grid.CurrentCell.Column.DisplayIndex+1);
                if (cell != null)
                {
                    cell.IsSelected = true;
                    cell.Focus();
                    grid.BeginEdit();
                }
            }
        }
        catch (Exception ex)
        {
            
        }
    }
public static DataGridCell GetCell(DataGrid grid, int row, int column)
    {
        var rowContainer =  GetRow(grid, row);

        if (rowContainer != null)
        {
            var presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer);
            if (presenter != null)
            {
                // try to get the cell but it may possibly be virtualized
                var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                if (cell == null)
                {
                    // now try to bring into view and retreive the cell
                    grid.ScrollIntoView(rowContainer, grid.Columns[column]);
                    cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);
                }
                return cell;
            }
        }
        return null;
    }
static public DataGridRow GetRow(DataGrid dg, int index)
    {
        DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
        if (row == null)
        {
            // may be virtualized, bring into view and try again
            dg.ScrollIntoView(dg.Items[index]);
            row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
        }
        return row;
    }
    static T GetVisualChild<T>(Visual parent) where T : Visual
    {
        T child = default(T);
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接