开发者

How can I get navigation (tab/arrow) through cells on a DataGridView to skip over the read-only cells?

I have a DataGridV开发者_运维问答iew in C# (.NET 2.0) with a few read-only cells (3 out of 25). I'm using the method outlined in the top answer here to set ReadOnly on those specific cells in the Form_Load handler. The linked question states that

some cells has to be ReadOnly and also when the user navigates with TAB or ENTER between cells, the ReadOnly cells should be bypassed

so I assumed setting the flag would cause the cells to be skipped.

Long story short, it's not. The read-only cells get tabbed to and selected even though they cannot be edited. I'm combing over the properties of the DataGridView looking for some kind of TabMode or TabSkipsReadOnlyCells property, but so far nothing. Is there a property to set for this behavior, or do I have to write some tab event handling code of some kind?

This seems like it should be the default behavior, so I'm kind of annoyed to even have to find a property for it, much less have to write code to do it...

EDIT: I should clarify, I'm not interested in only handling navigation with the Tab key. I want to implement reasonable navigation with the arrow keys and possibly the mouse as well. That means if I have to write code I need direct control over where the selection moves when I bounce it out of a read-only cell, probably by setting CurrentCell on the DataGridView. This is so that if the user up-arrows into a read-only cell I can redirect to the cell above, not always to the cell to the right.

EDIT 2: Here's my final solution, handling arrow key navigation as well as tab navigation, based on Sean Griffiths' code (also linked in the accepted answer):

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

If anyone uses this and finds cases where it behaves badly, I'd like to hear about it so I can hopefully update this with a fix.

EDIT 3: Added a safety counter after the code managed to get itself in an infinite-loop state today. All cells in column zero were set to read-only, and the first click into the grid control was in column zero, so it tried to move down, then up, then down....


You will have put in some code for that One way of doing it is

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


I had a similar problem using a datagridview - there is a write up on my solution here http://codemumbler.blogspot.com/2011/02/one-aspect-of-datagridviews-that-you.html

It uses the CellEnter eventhandler and hunts for the next available usable sell in the grid and uses a delegate to avoid a Reentrant exception.

Hope this helps - Sean

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜