c# - Eliminar celdas de DataGridView que contienen valores duplicados

CorePress2024-01-25  10

Mi DataGridView tiene este aspecto:

¿Cómo borrar el texto de celdas duplicadas en las filas de DataGridView?

Lo intenté a continuación pero se borran todos los valores de Celdas[0].

string duplicateValue = dataGridView1.Rows[0].Cells[0].Value.ToString();

for (int i = 1; i < dataGridView1.Rows.Count; i++)
{
    if (dataGridView1.Rows[i].Cells[0].Value.ToString() == duplicateValue)
    {
        dataGridView1.Rows[i].Cells[0].Value = string.Empty;
    }
    else
    {
        dataGridView1.Rows[i].Cells[0].Value = duplicateValue;
    }
}


------------------------------------

Una forma de lograr esto sería utilizar un HashSet de la siguiente manera:

var valuesFound = new HashSet<string>();

for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
    string cellText = dataGridView1.Rows[i].Cells[0].Value.ToString();
    // Attempt to add the value to the HashSet. If it fails, then it's a duplicate.
    if (!valuesFound.Add(cellText))
    {
        dataGridView1.Rows[i].Cells[0].Value = string.Empty;
    }
}

O si prefieres LINQ, puedes hacer algo como esto:

var duplicateCells = dataGridView1.Rows.OfType<DataGridViewRow>()
    .Select(r => r.Cells[0])
    .GroupBy(c => c.Value.ToString())
    .SelectMany(g => g.Skip(1))
    .ToList();

duplicateCells.ForEach(c => c.Value = string.Empty);



------------------------------------

Respuesta corta:

Cómo borrar el texto¿T de celdas duplicadas en las filas de DataGridView?

Aparentemente consideras que algunos productos son iguales. Lamentablemente, olvidaste decir cuándo dos productos son iguales. ¿Es el Producto [Apple, Reino Unido, 1] igual a [Apple, Reino Unido, 2]? Y si es así, ¿cuál quieres mostrar?

¿O quieres mostrar la suma: [Apple, Reino Unido, 3]?

¿Y qué pasa con: [Apple, Irlanda, 1]? ¿Es lo mismo que [Apple, Reino Unido, 1]?

Claramente necesitas un método que diga: este producto es igual a ese producto, pero ese es un producto diferente.

Para ello tendremos que crear un comparador de igualdad.

class Product
{
    public Name {get; set;}
    public string Country {get; set;}
    public int Quantity {get; set;}
    ...
}

IEqualityComparer<Product> productComparer = ... // TODO: implement

Una vez que tengas esto, podrás deshacerte de los duplicados:

IEnumerable<Product> productsWithDuplicates = ...
IEnumerable<Product> noDuplicates = productsWithDuplicates.Distinct(productComparer);

O si deseas combinar [Apple, UK, 1] y [Apple, UK, 2] para mostrar la suma [Apple, UK, 3], usa groupBy para crear grupos:

IEnumerable<Product> productsToDisplay = productsWithDuplicates
    .GroupBy(product => new {product.Name, product.Country}

    (key, productsWithThisKey) => new Product
    {
        Name = key.Name,
        Country = key.Country,
        Quantity = productWithThisKey.Select(product => product.Quantity).Sum(),
    },

    productComparer);

Entonces la solución dependeds sobre cuándo dos productos son iguales y qué desea mostrar si encuentra productos iguales.

Comparador de igualdad de productos
class ProductComparer : EqualityComparer<Product>()
{
    public static IEqualityComparer<Product> NameCountry {get;} = new ProductComparer();

    public override bool Equals(Product x, Product y)
    {
        if (x == null) return y == null; // true if both null, false if x null, but y not
        if (y == null) return false;     // because x not null
        if (object.ReferenceEquals(x, y) return true;

        // define equality, for instance:
        return x.Name == y.Name && x.Country == y.Country;
    }

Si no desea distinguir entre mayúsculas y minúsculas, agregue una propiedad:

private static IEqualityComparer<string> NameComparer {get; } = StringComparer.InvariantIgnoreCase;
private static IEqualityComparer<string> CountryComparer {get;} = ...

Y en Iguales:

return NameComparer.Equals(x.Name, y.Name)
    && CountryComparer.Equals(x.Country, y.Country);

Ahora, si luego decide que desea distinguir entre mayúsculas y minúsculas al comparar países, o tal vez desea utilizar la cultura actual, solo tendrá que cambiar esto en una ubicación.

El uso de los comparadores facilita el cambio, pero también su código: no tiene que buscar nombres y países nulos, eso lo manejan los comparadores.

GetHashCode: único requisito: si x es igual a y, devuelve el mismo GetHashCode. si no son iguales, eres libre de devolver lo que quieras, pero es más eficiente si devuelves un código hash diferente.

public override int GetHashCode(Product product)
{
    if (product == null) return 47855249; // just a number

    return NameComparer.GetHashCode(product.Name)
         ^ CountryComparer.GetHashCode(product.Country);
}
Hay margen de mejoraNuevo Testamento

Normalmente no es una buena idea entrelazar su modelo con la vista de su modelo. Si en el futuro quieres cambiar la forma en que se muestran tus datos, por ejemplo si quieres mostrarlos en un ListBox o en un Gráfico, tendrás que cambiar mucho.

Además, si ha separado su modelo de la forma en que se muestra, será mucho más fácil realizar una prueba unitaria de su modelo. Para probar su vista, no necesitará sus datos originales; puede probar con condiciones de borde, como una vista de cuadrícula de datos vacía,

En primer lugar, necesitas un método para recuperar los productos de tu modelo:

private IEnumerable<Productm> FetchProducts(...) {...}

Así que ahora tienes un método unitario comprobable que recupera los productos. Lo bueno es que incluso ocultaste de dónde obtienes esta información: puede ser de una base de datos, de un archivo XML o incluso de Internet: tu Fo.rm no lo sabe y no tiene por qué saberlo. Totalmente diseñado para el cambio.

Con Visual Studio Designer tienes columnas definidas. Cada columna muestra exactamente el valor de una propiedad. La propiedad que muestra la columna se define en la propiedad DataGridViewColumn.DataPropertyName

columnName.DataPropertyName = nameof(Product.Name);
columnCountry.DatapropertyName =  nameof(Product.Country);
...

Para mostrar los productos obtenidos, utilice la propiedad DataGridView.DataSource. Si asigna una Lista<Producto>, los cambios que realiza el operador (agregar/eliminar filas, cambiar celdas) no se reflejan en la Lista. Si desea actualizar automáticamente los cambios que realizó el operador, utilice BindingList

public BindingList<Product> DisplayedProducts
{
    get => (BindingList<Product>)this.datagridView1.DataSource;
    set => this.datagridView1.DataSource = value;
}

private IEqualityComparer<Product> ProductComparer {get;} = ProductComparer.NameCountry;

public void InitProductDisplay()
{
    IEnumerable<Product> productsToDisplay = this.FetchProducts()
        .Distinct(productComparer);

        // or if you want to show the totals: use the GroupBy described above

    this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}

¡Qué bien! Si no desea comparar en NameCountry, sino de manera diferente, o si desea comparar usando la cultura actual, si desea mostrar los totales de la cantidad, o incluso si desea mostrar yot en un gráfico en lugar de una tabla: solo hay un lugar que necesitas cambiar.

Ahora cada cambio que realiza el operador: agregar/eliminar/cambiar se refleja en su BindingList, incluso si las filas están ordenadas.

Por ejemplo, si el operador indica que terminó de editar haciendo clic en un botón:

private void OnButtonOk_Clicked(object sender, ...)
{
    var displayedProducts = this.DisplayedProducts;
    // find out which products are added / removed / changed
    this.ProcessEditedProducts(displayedProducts);
}

Si necesita hacer algo con las filas seleccionadas, considere agregar lo siguiente:

private Product CurrentProduct => (Product)(this.datagridView1.CurrentRow?.DataBoundItem);

private IEnumerable<Product> SelectedProducts = this.datagridView1.SelectedRows
    .Cast<DataGridViewrow>()
    .Select(row => row.DataBoundItem)
    .Cast<Product>();

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare