【问题标题】:Remove DataGridView cells containing duplicate values删除包含重复值的 DataGridView 单元格
【发布时间】:2021-03-28 10:31:51
【问题描述】:

我的 DataGridView 如下所示:

如何清除DataGridView Rows中重复单元格的文字?

我在下面尝试过,但它正在清除 Cells[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;
    }
}

【问题讨论】:

    标签: c# .net winforms


    【解决方案1】:

    实现此目的的一种方法是使用HashSet,如下所示:

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

    或者,如果您更喜欢 LINQ,您可以这样做:

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

    【讨论】:

      【解决方案2】:

      简答:

      如何清除DataGridView Rows中重复单元格的文字?

      显然您认为某些产品是相同的。唉,你忘了说两个产品什么时候相等。产品 [Apple, UK, 1] 是否等于 [Apple, UK, 2]?如果是这样,你想展示哪一个?

      还是要显示总和:[Apple, UK, 3]?

      那么:[Apple, Ireland, 1] 呢?和 [Apple, UK, 1] 一样吗?

      显然,您需要一种方法来说明:该产品等于该产品,但该产品是不同的产品。

      为此,我们必须创建一个相等比较器。

      class Product
      {
          public Name {get; set;}
          public string Country {get; set;}
          public int Quantity {get; set;}
          ...
      }
      
      IEqualityComparer<Product> productComparer = ... // TODO: implement
      

      一旦你有了这个,你就可以摆脱重复:

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

      或者,如果您想组合 [Apple, UK, 1] 和 [Apple, UK, 2] 以显示总和 [Apple, UK, 3],请使用 groupBy 进行分组:

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

      所以解决方案取决于两个产品何时相等,以及如果您找到相等的产品,您想显示什么。

      产品的平等比较器

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

      如果要不区分大小写,请添加属性:

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

      在等式中:

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

      现在,如果您以后决定在比较国家/地区时要区分大小写,或者可能要使用当前文化,您只需在一个位置进行更改。

      比较器的使用使更改更容易,而且您的代码也更容易:您不必检查由比较器处理的空名称和国家/地区。

      GetHashCode:唯一要求:如果 x 等于 y,则返回相同的 GetHashCode。如果不相等,你可以随意返回任何你想要的,但是如果你返回不同的hashcode,效率会更高。

      public override int GetHashCode(Product product)
      {
          if (product == null) return 47855249; // just a number
      
          return NameComparer.GetHashCode(product.Name)
               ^ CountryComparer.GetHashCode(product.Country);
      }
      

      还有改进的余地

      将模型与模型视图交织在一起通常不是一个好主意。如果将来您想更改数据的显示方式,例如您想在 ListBox 或 Graph 中显示它,则必须进行大量更改。

      此外,如果您将模型与其显示方式分开,则对模型进行单元测试会容易得多。要测试您的视图,您不需要原始数据,您可以使用边缘条件进行测试,例如空的 datagridview,

      首先,您需要一个从模型中获取产品的方法:

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

      所以现在您有了一个获取产品的可单元测试方法。好消息是您甚至隐藏了从何处获取此信息:它可以来自数据库、XML 文件,甚至来自互联网:您的表单不知道,也不必知道。完全为改变而设计。

      使用 Visual Studio Designer,您已经定义了列。每一列都准确地显示了一个属性的值。该列显示的属性在属性DataGridViewColumn.DataPropertyName中定义

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

      要显示提取的产品,请使用属性DataGridView.DataSource。如果您分配List&lt;Product&gt;,则操作员所做的更改(添加/删除行、更改单元格)不会反映在列表中。如果您想自动更新操作员所做的更改,请使用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());
      }
      

      不错!如果您不想在 NameCountry 上进行比较,但不同,或者如果您想使用当前文化进行比较,如果您想显示数量的总数,或者即使您想在图表而不是表格中显示它: 你只需要改变一个地方。

      现在,操作员所做的每一项更改:添加/删除/更改都会反映在您的 BindingList 中,即使行已排序。

      例如,如果操作员通过单击按钮指示他完成了编辑:

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

      如果您需要对选定的行执行某些操作,请考虑添加以下内容:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-08-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多