DataGridView "CellPainting" event not painting the correct cell

Viewed 270

I have a column shifting function for my DataGridView control where I can shift columns to the left and right of the table, and I also have an outline painting/drawing event that draws a red outline over cells which are "invalid". Unfortunately, when I shift a column with these outlines assigned to it, the outlines fail to draw to the correct cell in a pretty strange manner, where the outline stays to the left or right of the cell it should be drawn to based on where the outline was originally.

I've created a project with these two features to demonstrate this strange behaviour: gif of demo project

Here is the code relevant to creating the outline(s):

        //Dictionary<colIndex,List<RowIndex>>
        private Dictionary<int,List<int>> invalidCellIndexes = new Dictionary<int,List<int>>();

        public void AddInvalidCellIndexes(int colIndex, int rowIndex)
        {
            Console.WriteLine("Adding outline to Column: " + colIndex.ToString() + ", at Row: " + rowIndex.ToString());
            if (!invalidCellIndexes.ContainsKey(colIndex))
            {
                invalidCellIndexes[colIndex] = new List<int>();
            }
            if (!invalidCellIndexes[colIndex].Contains(rowIndex))
            {
                invalidCellIndexes[colIndex].Add(rowIndex);
            }

            

        }
        public void RemoveInvalidCellIndexes(int colIndex, int rowIndex)
        {
            if (invalidCellIndexes.ContainsKey(colIndex))
            {
                invalidCellIndexes[colIndex].Remove(rowIndex);
                if (invalidCellIndexes[colIndex].Count == 0)
                {
                    invalidCellIndexes.Remove(colIndex);
                }
            }

            


        }


        //takes old ColumnOrder and new ColumnOrder and translates the invalidCellIndexes accordingly
        public void ShiftColumnsOfInvalidCellIndexes(List<string> oldColumnOrder, List<string> newColumnOrder)
        {



            Dictionary<int, List<int>> oldInvalidCellIndexes = invalidCellIndexes;
            Dictionary<int, List<int>> newInvalidCellIndexes = new Dictionary<int, List<int>>();

            for (int i = 0; i < oldColumnOrder.Count; i++)
            {
                string colName = oldColumnOrder[i];
                int oldIndex = i;
                int newIndex = newColumnOrder.IndexOf(colName);
                if (oldInvalidCellIndexes.ContainsKey(oldIndex))
                {
                    Console.WriteLine("shifting outline at index " + oldIndex.ToString() + " TO " + newIndex.ToString());

                    newInvalidCellIndexes[newIndex] = oldInvalidCellIndexes[oldIndex];



                }

            }


            invalidCellIndexes = newInvalidCellIndexes;



            


        }

        //code linked to CellPainting Event
        private void DataGridView1_CellPainting(object sender, System.Windows.Forms.DataGridViewCellPaintingEventArgs e)
        {
            


            //DataGridView senderDGV = (DataGridView)sender;

            if (e.RowIndex >= 0 && e.ColumnIndex >= 0)
            {
                if (invalidCellIndexes.ContainsKey(e.ColumnIndex) && invalidCellIndexes[e.ColumnIndex].Contains(e.RowIndex))
                {
                    //DataGridViewCell cell = senderDGV.Rows[e.RowIndex].Cells[e.ColumnIndex];

                    Console.WriteLine("painting: Row " + e.RowIndex.ToString() + ", column " + e.ColumnIndex.ToString());
                    e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);
                    using (Pen p = new Pen(Color.Red, 1))
                    {
                        Rectangle rect = e.CellBounds;
                        rect.Width -= 2;
                        rect.Height -= 2;
                        e.Graphics.DrawRectangle(p, rect);
                    }
                    e.Handled = true;
                }
            }




            
        }

and here is the code relevant to adding the columns, adding the outline, and column shifting:

        //orderList tracks the column display order
        List<string> orderList = new List<string>();
        private void button2_Click(object sender, EventArgs e)
        {
            dataGridView1.Columns.Add("B", "B");
            dataGridView1.Columns.Add("C", "C");
            dataGridView1.Columns.Add("D", "D");
            dataGridView1.Columns.Add("A", "A");

            dataGridView1.Rows.Add();
            dataGridView1.Rows.Add();
            dataGridView1.Rows.Add();

            //add outline to second row of column "A"
            int AColIndex = dataGridView1.Columns.IndexOf(dataGridView1.Columns["A"]);
            int ARowIndex = 1;
            AddInvalidCellIndexes(AColIndex, ARowIndex);

            //add columns to orderlist
            foreach (DataGridViewColumn col in dataGridView1.Columns)
            {
                orderList.Add(col.Name);
            }


            button2.Enabled = false;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            int AColIndex = dataGridView1.Columns.IndexOf(dataGridView1.Columns["A"]);
            //shift "A" column to the left
            ShiftColumn("A", true);
        }
        private void button3_Click(object sender, EventArgs e)
        {
            int AColIndex = dataGridView1.Columns.IndexOf(dataGridView1.Columns["A"]);
            //shift "A" column to the right
            ShiftColumn("A", false);
        }

        internal void ShiftColumn(string colName, bool isLeft)
        {
            
            


            int newIndex = orderList.IndexOf(colName);
            if (isLeft)
            {
                newIndex -= 1;
            }
            else
            {
                newIndex += 1;
            }

            if (newIndex > -1 && newIndex < orderList.Count)
            {
                //duplicate orderList
                List<string> oldOrderList = new List<string>(orderList);


                orderList.Remove(colName);
                orderList.Insert(newIndex, colName);




                //for all columns which have shifted position due to this change

                //if shifted right, a single column to the right has been shifted before the new index
                int colI = newIndex - 1;
                if (isLeft)
                {
                    //if shifted to the left, only the new index and indexes after it have been shifted
                    colI = newIndex;
                }

                //shift column DisplayIndexes
                while (colI < orderList.Count)
                {
                    string thisColName = orderList[colI];
                    int thisOldIndex = oldOrderList.IndexOf(thisColName);
                    int thisNewIndex = colI;
                    dataGridView1.Columns[thisColName].DisplayIndex = thisNewIndex;


                    colI++;
                }

                //shift cell outlines as well
                ShiftColumnsOfInvalidCellIndexes(oldOrderList, orderList);

                
                




            }

        }
1 Answers

I didn't debug your code to tell what's the problem in your code, but you just need to set the DisplayIndex and you don't need to shift the data and I can quickly show in a very simple example how you can do that in the right way.

In the example, I use a data table to hold data (it could be any other data structure which supports data binding). Also to have valid or invalid rows, I assume you can tell the validity based on the cell values; for example here "O" is invalid. There's also a button to shift the active column to left and a button to shift to right:

enter image description here

private void Form1_Load(object sender, EventArgs e)
{
    var dt = new DataTable();
    dt.Columns.Add("A");
    dt.Columns.Add("B");
    dt.Columns.Add("C");
    dt.Columns.Add("D");
    dt.Rows.Add(new[] { "O", "O", "O", "O" });
    dt.Rows.Add(new[] { "X", "O", "O", "O" });
    dt.Rows.Add(new[] { "X", "X", "O", "O" });
    dt.Rows.Add(new[] { "X", "X", "X", "O" });
    dgv1.DataSource = dt;
}
private void dgv1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (e.FormattedValue as string == "O")
    {
        //Invalid
        e.Graphics.FillRectangle(Brushes.Pink, e.CellBounds);
        e.Paint(e.CellBounds, DataGridViewPaintParts.All &
            ~DataGridViewPaintParts.Background);
        e.Handled = true;
    }
}
private void ShiftLeftButton_Click(object sender, EventArgs e)
{
    if (dgv1.CurrentCell != null &&
        dgv1.CurrentCell.OwningColumn.DisplayIndex > 0)
        dgv1.CurrentCell.OwningColumn.DisplayIndex -= 1;
}
private void ShiftRightButton_Click(object sender, EventArgs e)
{
    if (dgv1.CurrentCell != null &&
        dgv1.CurrentCell.OwningColumn.DisplayIndex < dgv1.Columns.Count - 1)
        dgv1.CurrentCell.OwningColumn.DisplayIndex += 1;
}
Related