【问题标题】:How can I get navigation (tab/arrow) through cells on a DataGridView to skip over the read-only cells?如何通过 DataGridView 上的单元格导航(选项卡/箭头)以跳过只读单元格?
【发布时间】:2011-02-08 18:43:51
【问题描述】:

我在 C# (.NET 2.0) 中有一个 DataGridView,其中包含一些只读单元格(25 个中的 3 个)。我正在使用最佳答案here 中概述的方法在 Form_Load 处理程序中的那些特定单元格上设置 ReadOnly。链接的问题指出

某些单元格必须是只读的,并且当用户在单元格之间使用 TAB 或 ENTER 导航时,应该绕过只读单元格

所以我假设设置标志会导致单元格被跳过。

长话短说,事实并非如此。即使无法编辑只读单元格,它们也会被标记并选中。我正在梳理 DataGridView 的属性,寻找某种 TabMode 或 TabSkipsReadOnlyCells 属性,但到目前为止还没有。是否有为此行为设置的属性,还是我必须编写某种选项卡事件处理代码?

这似乎应该是默认行为,所以我有点恼火,甚至不得不为它找到一个属性,更不用说编写代码来做到这一点了......

编辑:我应该澄清一下,我对仅使用 Tab 键处理导航不感兴趣。我想用箭头键和鼠标来实现合理的导航。这意味着如果我必须编写代码,我需要直接控制当我将选择从只读单元格中弹出时的移动位置,可能通过在 DataGridView 上设置 CurrentCell。这样一来,如果用户向上箭头进入只读单元格,我可以重定向到上面的单元格,而不是总是重定向到右侧的单元格。

编辑 2:这是我的最终解决方案,基于 Sean Griffiths' code 处理箭头键导航和选项卡导航(也在接受的答案中链接):

private void GridForm_Load(object sender, EventArgs e)
{
    dataGridView1.CellEnter += dataGridView1_CellEnter;
}

//a delegate is needed to avoid a circular loop when selecting a cell when in a cell selection event
private delegate void SetColumnAndRowOnGrid(DataGridView grid, int columnIndex, int rowIndex);
static SetColumnAndRowOnGrid setCellMethod = new SetColumnAndRowOnGrid(setGridCell);

// Method pointed to by the delegate
private static void setGridCell(DataGridView grid, int columnIndex, int rowIndex)
{
    grid.CurrentCell = grid.Rows[rowIndex].Cells[columnIndex];
    grid.BeginEdit(true);
}

// Track the cell we leave so we can determine direction of "travel"
int _lastRow = 0, _lastCol = 0;
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e)
{
    _lastRow = e.RowIndex;
    _lastCol = e.ColumnIndex;
}

enum Direction { Up, Down, Left, Right }

// When we enter a read only cell, determine direction 
// of "travel" and keep going that way
private void dataGridView1_CellEnter(object sender, DataGridViewCellEventArgs e)
{
    int currRow = e.RowIndex;
    int currCol = e.ColumnIndex;
    if (dataGridView1.Rows[currRow].Cells[currCol].ReadOnly)
    {
        Direction direction = Direction.Right;
        if ((currRow != _lastRow) && (currCol == _lastCol))
        {
            // moving vertically
            if (currRow < _lastRow) direction = Direction.Up;
            else direction = Direction.Down;
        }
        else
        {
            // moving horizontally
            if (currCol == 0 &&
                _lastCol == dataGridView1.Columns.Count - 1 &&
                currRow == _lastRow + 1)
            {
                // Special case - probably just tabbed from end of row
                direction = Direction.Right;
            }
            else if (currCol == dataGridView1.Columns.Count - 1 &&
                _lastCol == 0 &&
                currRow == _lastRow - 1)
            {
                // Special case - probably just shift-tabbed from start of row
                direction = Direction.Left;
            }
            else if (currCol < _lastCol) { direction = Direction.Left; }
        }
        //this cell is readonly, find the next tabable cell
        if (!SetNextTabableCell(dataGridView1, currCol, currRow, direction))
        {
            // All the cells in the grid have been tried, none could be tabbed
            // to so move onto the next control
            bool tabForward = direction == Direction.Right || direction == Direction.Down;
            SelectNextControl(this, tabForward, true, true, true);
        }
    }
}

// Find the next cell that we want to be selectable
private static bool SetNextTabableCell(DataGridView grid, int nextColumn, int nextRow, Direction direction)
{
    //keep selecting each next cell until one is found that isn't either readonly or invisible
    int maxMoves = grid.ColumnCount * grid.RowCount;
    int moves = 0;
    do
    {
        if (!GetNextCell(grid, ref nextColumn, ref nextRow, ref direction)) return false;
        // Prevent infinite loop - I managed to get in one when this function
        // wound up in a readonly column with a direction of Down (if we've moved
        // to another cell more times than there are cells in the grid, just give up)
        if (++moves > maxMoves) return false;
    }
    while (grid.Rows[nextRow].Cells[nextColumn].ReadOnly == true ||
                grid.Rows[nextRow].Cells[nextColumn].Visible == false);

    //a cell has been found that can be entered, use the delegate to select it
    grid.BeginInvoke(setCellMethod, grid, nextColumn, nextRow);
    return true;
}

// Get the next cell in the indicated direction
// Wrap around if going left-right
// Bounce at the edge if going up/down
private static bool GetNextCell(DataGridView grid, ref int nextColumn, ref int nextRow, ref Direction direction)
{
    switch (direction)
    {
        case Direction.Right:
            if (nextColumn < grid.Columns.Count - 1)
            {
                // Nominal case - move right one cell
                nextColumn = nextColumn + 1;
            }
            else // at the last column
            {
                // go the the first column
                nextColumn = 0;
                if (nextRow < grid.Rows.Count - 1)
                {
                    // Nominal case - move down one row
                    nextRow = nextRow + 1;
                }
                // at the last row and last column exit this method, no cell can be selected
                else { return false; }
            }
            break;
        case Direction.Left:
            if (nextColumn > 0)
            {
                // Nominal case - move left one cell
                nextColumn = nextColumn - 1;
            }
            else // at the first column
            {
                // go the the last column
                nextColumn = grid.Columns.Count - 1;
                if (nextRow > 0)
                {
                    // Nominal case - move up one row
                    nextRow = nextRow - 1;
                }
                // at the first row and first column exit this method, no cell can be selected
                else { return false; }
            }
            break;
        case Direction.Down:
            if (nextRow < grid.Rows.Count - 1)
            {
                // Nominal case - move down one cell
                nextRow = nextRow + 1;
            }
            else // at the last row
            {
                // turn around
                nextRow = nextRow - 1;
                direction = Direction.Up;
            }
            break;
        case Direction.Up:
            if (nextRow > 0)
            {
                // Nominal case - move up one cell
                nextRow = nextRow - 1;
            }
            else // at the first row
            {
                // turn around
                nextRow = nextRow + 1;
                direction = Direction.Down;
            }
            break;
        default: return false;
    }
    return true;
}

如果有人使用它并发现它表现不佳的情况,我很想听听它,以便我希望通过修复来更新它。

编辑 3:在今天的代码设法使自己处于无限循环状态后,添加了一个安全计数器。零列中的所有单元格都设置为只读,并且第一次单击网格控件是在零列中,因此它尝试向下移动,然后向上,然后向下移动....

【问题讨论】:

  • 哦,当然,在我发布相关列表后弹出一个可能的重复项:stackoverflow.com/questions/751981/…
  • 将单元格设置为只读时设置Focusable = false;?或TabStop = false;
  • Intellisense 没有在 DataGridViewCell 或其 Style 属性上向我显示 Focusable 或 TabStop 属性。这是 .NET 3 还是更好的属性?
  • 我应该写一篇关于在没有asp.net内置控件的情况下显示动态数据的文章。我认为这会让您大吃一惊。

标签: c# datagridview readonly


【解决方案1】:

您将为此输入一些代码 一种方法是

void grd_CellEnter(object sender, DataGridViewCellEventArgs e)
{            
   if(grd[e.ColumnIndex,e.RowIndex].ReadOnly)
       SendKeys.Send("{TAB}");
}

【讨论】:

  • 我喜欢它的紧凑度,尽管使用 SendKeys 让我感觉很笨拙。我要试验一下……
  • 我发现我无法通过 CellEnter 事件处理程序在我的 DataGridView 上设置 CurrentCell,所以这对我不起作用(请参阅对上述问题的编辑)。我尝试了其他一些事件处理程序,如 SelectionChanged,但根据导航方法,当调用处理程序时 CurrentCell 可能仍指向前一个单元格(SelectionChanged 对箭头键导航工作正常,但对 Tab 导航无效)。
【解决方案2】:

我在使用 datagridview 时遇到了类似的问题 - 这里有一篇关于我的解决方案的文章 http://codemumbler.blogspot.com/2011/02/one-aspect-of-datagridviews-that-you.html

它使用 CellEnter 事件处理程序并在网格中寻找下一个可用的销售,并使用委托来避免可重入异常。

希望这会有所帮助 - 肖恩

【讨论】:

  • 这就像一个冠军,虽然我不得不将 this.dataGridView1.CellEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellEnter); 从设计器移动到 Form_Load 以避免在加载时出现 BeginInvoke 异常。
猜你喜欢
  • 2014-03-20
  • 2010-10-30
  • 2014-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多