简答:
如何清除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<Product>,则操作员所做的更改(添加/删除行、更改单元格)不会反映在列表中。如果您想自动更新操作员所做的更改,请使用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>();