'How can I change each duplicate set of values to different colour in DataGridView c#

I have a DataGridView with a Contact column full of numbers. Each number can reoccur multiple times, or just once. I am trying to find a solution to re-colour each set of rows that contain matching numbers, but not recolour the ones with no duplicates. Example of the outcome I am trying to achieve:

<p style="color:green;">2</p>
<p style="color:green;">2</p>
<p style="color:red;">3</p>
<p style="color:red;">3</p>
123 <br><br>
321 <br>
<p style="color:yellow;">4</p>
<p style="color:yellow;">4</p>
<p style="color:red;">3</p>
<p style="color:yellow;">4</p>

Here is my code, I think the problem is where I am incrementing the "duplicateCount" integer for the switch statement as I need it to increment only after every row containing the same value is coloured, but could not figure out a way to do that. Hoping I am clear enough and someone can guide me in the right direction. Thanks in advance

    public void HighlightDuplicates(DataGridView grv)
    {
        List<String> alreadydone = new List<String>();
        int duplicateCount = 1;
        for (int currentRow = 0; currentRow < grv.Rows.Count - 1; currentRow++)
        {
            DataGridViewRow rowToCompare = grv.Rows[currentRow];
            for (int otherRow = 0; otherRow < grv.Rows.Count-1; otherRow++)
            {
                DataGridViewRow row = grv.Rows[otherRow];
                bool duplicateRow = true;
                string rowToCompareString0 = rowToCompare.Cells[2].Value.ToString();
                string rowString0 = row.Cells[2].Value.ToString();
                if (rowToCompare.Cells[2].Value.ToString() != row.Cells[2].Value.ToString())

                {

                    duplicateRow = false;
                    continue;
                    
                    
                }
                if (duplicateRow && currentRow != otherRow && !alreadydone.Contains(rowToCompare.Cells[2].Value.ToString() + rowToCompare))
                {
                    switch (duplicateCount)
                    {
                        case 1:
                            rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Red;
                            rowToCompare.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
                            row.Cells[2].Style.BackColor = System.Drawing.Color.Red;
                            row.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
                            alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
                            break;
                        case 2:
                            rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Yellow;
                            rowToCompare.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
                            row.Cells[2].Style.BackColor = System.Drawing.Color.Yellow;
                            row.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
                            alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
                            break;
                        case 3:
                            rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Green;
                            rowToCompare.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
                            row.Cells[2].Style.BackColor = System.Drawing.Color.Green;
                            row.Cells[2].Style.ForeColor = System.Drawing.Color.Black;
                            alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
                            break;
                        default:
                            rowToCompare.Cells[2].Style.BackColor = System.Drawing.Color.Purple;
                            row.Cells[2].Style.BackColor = System.Drawing.Color.Purple;
                            alreadydone.Add(rowToCompare.Cells[2].Value.ToString() + rowToCompare);
                            break;
                    }
                    
                }
            }
            duplicateCount++;
        }
    }


Solution 1:[1]

It appears you may be over complicating things. It is unclear why your current code is using two for loops for this. One for loop will work and it will be less complicated.

The approach below simply loops through the rows in the grid and checks adjacent rows for equality. The only caveat here is that we need to color the first row FIRST. We know the first row will be colored since there is no “previous” row to compare it with.

Once this first row is colored, then we will loop through each row in the grid… HOWEVER, the for loop will not iterate to the LAST row. The loop will iterate to the second to the last row. With each iteration of the loop, we will check the “second” row to see if it equals the “previous” row and if so, we can color the second rows cell with the same color. If the cell values are not equal, then we will simply change the color of that cell to the next color.

It is unknown how many “different” colors you may want to use and, in the example below I used four (4) different colors and if the number of “different” cell values is greater than four (4), then we will simply start over with the first color used. In other words, the first and fifth groups of rows with “different” values will be colored the same and will repeat for every group of five different cell values.

To help, we will create a simple method called SetCellColor that will take an int curColor and a DataGridViewCell cell. In this method we will simply color the cell based on the given curColor. This method may look something like…

private void SetCellColor(int curColor, DataGridViewCell cell) {
  switch(curColor) {
    case 1:
      cell.Style.ForeColor = Color.Red;
      cell.Style.BackColor = Color.Black;
      break;
    case 2:
      cell.Style.ForeColor = Color.Black;
      cell.Style.BackColor = Color.Yellow;
      break;
    case 3:
      cell.Style.ForeColor = Color.Black;
      cell.Style.BackColor = Color.Green;
      break;
    case 4:
      cell.Style.ForeColor = Color.Wheat;
      cell.Style.BackColor = Color.Purple;
      break;
  }
}

This method above will make it easier to color the cell when we compare the two row’s cell values. We will use the above method in the method below which loops through the grid rows as described above. Keep in mind we need to set the loop to go from row zero (0), to the “SECOND” to last row. NOTE: this assumes the grids AllowUsersToAddRows property is set to false. If it is set to true then you will need to set the ending condition to Rows.Count – 2 to avoid a guaranteed null exception.

private void ColorDuplicateCells() {
  int curColorIndex = 1;
  SetCellColor(curColorIndex, dataGridView1.Rows[0].Cells[2]);
  for (int i = 0; i < dataGridView1.Rows.Count - 1; i++) {
    if (dataGridView1.Rows[i].Cells[2].Value.ToString() == dataGridView1.Rows[i + 1].Cells[2].Value.ToString()) {
      SetCellColor(curColorIndex, dataGridView1.Rows[i+1].Cells[2]);
    }
    else {
      curColorIndex++;
      if (curColorIndex > 4) {
        curColorIndex = 1;
      }
      SetCellColor(curColorIndex, dataGridView1.Rows[i + 1].Cells[2]);
    }
  }
}

Walking through the method, we color the first cell before we enter the for loop. Once in the for loop we compare the current row cells value with the next rows cell value. If the values are equal, then we will use the same color. If the values are different, then we will change to the next color by incrementing curColorIndex and then call the SetCellColor for the second row. Then continue on to the next row. In the else portion of the if statement, we need to check and make sure curColorIndex stays in bounds of the four (4) different colors. If the curColorIndex gets greater than four (4), then we will simply set it back to 1 and repeat the same color scheme for the rest of the rows.

I hope this makes sense.

Edit update per OP comment...

There are a couple of things that appear to be left out of you question. One question would be what do you plan on doing if the number of “duplicate” rows exceeds the number of “different” colors you use. In your example, you can clearly see the data and surmise that only four different (4) colors are needed. However, this may not always be the case and in my first answer I simply “repeat” the colors by repeating the four colors. This would mean that two or more rows of “different” values may have the same color.

Since I was under the impression the “different” values would be grouped together, then this strategy of repeating colors technically would work and still remove some ambiguity by the different values with the same color. Unfortunately, if the values are NOT “grouped” together, then ambiguity IS going to be an issue. In this case, with repeated colors, you could end up with two or more consecutive cells where ALL the values are different but the colors are the same…? …

The only way to avoid this ambiguity is to ensure that we have enough colors such that no color is used more than once. This is going to be dictated strictly by the data. This clearly points to looping through the data twice.

The first loop would be to figure out how many “different” colors are needed to avoid any ambiguity. How you do this is your call and, in the example below, the code sticks with the same strategy and simply repeats the colors if there are more “different” repeated rows than there are available colors… therefore ambiguity is guaranteed if there are not enough colors.

Next, it is unclear “how” the grid is getting filled with data. Typically, setting the grids DataSource to a List<SomeClass> or a DataTable is the preferred method to fill the grid with data. I will assume your code is “manually” adding the data directly to the grid cells and the grid does not use a data source. If this is true, you should re-think that approach to use a data source…

Casting UI DataGridViewRows/Cell to an enumerable type so you can select/filter/group the data is only extra work for you. If you use a data source for the grid, then selecting/filtering/grouping the DataSource as opposed to UI grid cells… becomes much easier and you can usually avoid the unnecessary casting of the UI cells. Bottom line, you need to do this grouping and counting using the DATA SOURCE NOT the grid cells. This is just a recommendation, pick your own poison. The example below uses a DataTable as a DataSource to the grid.

Given these issues, one approach is to loop through the data source DataTable to get a list of “different” duplicate rows. The approach you attempted sounds like a good approach… is what we want to get is a list of the row cell values that ARE duplicates. If a cell value is only used once, then, we do not want that value in the list. We can use this list to then loop through the grid rows and check each cells value to see if it is in the list. If it IS in the list, then that is a duplicate value and the cell needs to be colored. If the cells value is NOT in the list, then it is not a duplicated value and does not need to be colored.

The only problem here, is that we know the row is a duplicate… but what color should it be?... When we get the list of duplicate values, we need to assign a color to each of the distinct duplicate values. We could obviously create something to calculate this and in the example below, I simply used the index of the value in the list plus 1. Example, if the duplicate list has 5 items, then the first items (duplicate value) would be 0 + 1, the second item would be 1 + 1 etc.… this will enable us to use the same SetCellColor method created in the original answer and we do not have to create any extra variables.

The only change made to the method is I added a “default” value for the cells that are not duplicated. In that case we set the forecolor to black and the background to white. This will allow the code to color the non-duplicated cells to the proper colors. This updated method may look something like…

private void SetCellColor(int curColor, DataGridViewCell cell) {
  switch(curColor) {
    case 1:
      cell.Style.ForeColor = Color.Red;
      cell.Style.BackColor = Color.Black;
      break;
    case 2:
      cell.Style.ForeColor = Color.Black;
      cell.Style.BackColor = Color.Yellow;
      break;
    case 3:
      cell.Style.ForeColor = Color.Black;
      cell.Style.BackColor = Color.Green;
      break;
    case 4:
      cell.Style.ForeColor = Color.Wheat;
      cell.Style.BackColor = Color.Purple;
      break;
    case 5:
      cell.Style.ForeColor = Color.Black;
      cell.Style.BackColor = Color.Aqua;
      break;
        // more colors ?
    default:
      cell.Style.ForeColor = Color.Black;
      cell.Style.BackColor = Color.White;
      break;
  }
}

And finally, the code that puts all this together may look something like below. duplicateItems is a list of only the rows such that the values are duplicated… it gets its items using the LINQ statement with the DATATABLE… not the grid rows. This is the first loop through the data.

Then we start a second loop though the grids rows. dupRow will contain the item from the duplicate list that matches that rows value. If dupRow returns null then that value is not in the duplicateItems list and we set the color index to zero (0). If dupRow is NOT null, then that row is a duplicate and we can set the color index to the index of the item in the duplicateaList plus 1. Once the colorIndex is set we can call the SetColorCell method. This code may look something like below…

Note: GridDT is a DataTable that is used as a DataSource to the grid. The values column [2] is named “DupCol”.

private void ColorCells() {
  var duplicateItems = GridDT.AsEnumerable().GroupBy(x => x.ItemArray[2]).Where(y => y.Count() > 1).ToList();
  int colorIndex;
  foreach (DataGridViewRow row in dataGridView1.Rows) {
    var dupRow = duplicateItems.FirstOrDefault(x => x.Key.Equals(row.Cells["DupCol"].Value.ToString()));
    if (dupRow != null) {
      colorIndex = duplicateItems.IndexOf(dupRow) + 1;
    }
    else {
      colorIndex = 0;
    }
    SetCellColor(colorIndex, row.Cells["DupCol"]);
  }
}

Solution 2:[2]

You are trying to do too much in one go. Things get much easier when you split them up. First get the distinct values (using linq and from memory):

var distinctValues = grv.Rows.Select(r=>r.Cells[2].Value.ToString()).Distinct;

Then write a method to assign the colours:

Dictionary<string, System.Drawing.Color> AssignColours(IEnumerable<string> values)
{
    // Add as many colours as you need here
    System.Drawing.Color[] colours = new System.Drawing.Color[] {System.Drawing.Color.Red, System.Drawing.Color.Yellow, System.Drawing.Color.Green, System.Drawing.Color.Purple};
    var result = new Dictionary<string, System.Drawing.Color>();
    int currentColour = 0;
    foreach (var s in values)
    {
        result[s] = colours[currentColour++];
    }
    return result;
}

And finally call the method and set the colour of the cells:

var colourDict = AssignColours(distinctValues);
for (int row = 0; row < grv.Rows.Count - 1; row++)
{
    var cell = grv.Rows[row].Cells[2];
    var value = cell.Value.ToString();
    var backColour = colourDict[value];
    cell.Style.BackColor = backColour;
    cell.Style.ForeColor = System.Drawing.Color.Black;
}

EDIT
To not colour the unique values you need to change the expression to this:

    var values = grv.Rows.Select(r=>r.Cells[2].Value.ToString();
    var distinctValues = values.GroupBy(g=>g).Where(h=>h.Count()>1).Select(f=>f.Key).Distinct();

There might be a way to shorten that, but it works. Then in the for-loop you need to take into account that the value might not be in the list:

var colourDict = AssignColours(distinctValues);
for (int row = 0; row < grv.Rows.Count - 1; row++)
{
    var cell = grv.Rows[row].Cells[2];
    var value = cell.Value.ToString();
    if (colourDict.ContainsKey(value))
    {
        var backColour = colourDict[value];
        cell.Style.BackColor = backColour;
        cell.Style.ForeColor = System.Drawing.Color.Black;
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2