Tuesday, February 15, 2011

Selecting the next editable cell in a Datagridview

One aspect of DataGridViews that you can often find being discussed in a healthy and vigorous fashion on the internet is read-only cells, specifically the lack of TabStop = false functionality. It's very common to see dev's trying to force the selected cell to change when a Read-only one is focused and then they post on a board somewhere about a Reentrant Exception from SetCurrentCellAddressCore, the following code will stop the exception happening and find the next available cell (or control):

        //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 i, int o);

        //can this cell be entered?
        //***********************************************
        //The Cell Enter Event Handler - First step in the code flow
        //***********************************************
        private void grid_CellEnter(object sender, DataGridViewCellEventArgs e)
        {
            //typesafe check
            if (sender is DataGridView)
            {
                DataGridView grid = (DataGridView)sender;
                if (grid.Rows[e.RowIndex].Cells[e.ColumnIndex].ReadOnly)
                {
                    //this cell is readonly, find the next tabable cell
                    if (!setNextTabableCell(grid, e.ColumnIndex, e.RowIndex))
                    {
                        //or tab to the next control
                        setNextTabableControl();
                    }
                }
            }
            else
            {
                throw new InvalidOperationException("this method can only be applied to controls of type DataGridView");
            }
        }


        //***********************************************
        //Find the next cell that we want to be selectable
        //***********************************************
        private bool setNextTabableCell(DataGridView grid, int nextColumn, int nextRow)
        {
            //keep selecting each next cell until one is found that isn't either readonly or invisable
            do
            {
                //at the last column, move down a row and go the the first column
                if (nextColumn == grid.Columns.Count - 1)
                {
                    nextColumn = 0;
                    //at the last row and last column exit this method, no cell can be selected
                    if (nextRow == grid.Rows.Count - 1)
                    {
                        return false;
                    }
                    else
                    {
                        nextRow = Math.Min(grid.Rows.Count - 1, nextRow + 1);
                    }
                }
                else
                {
                    nextColumn = Math.Min(grid.Columns.Count - 1, nextColumn + 1);
                }
            }
            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
            SetColumnAndRowOnGrid method = new SetColumnAndRowOnGrid(setGridCell);
            grid.BeginInvoke(method, grid, nextColumn, nextRow);
            //that's all I have to say about that
            return true;
        }

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

        //***********************************************
        // All the cells in the grid have been tried, none could be tabbed
        // to so move onto the next control
        //***********************************************
        private void setNextTabableControl()
        {
            this.SelectNextControl(this, true, true, true, true);
        }

1 comment: