#c# #.net #winforms
#c# #.net #winforms
Вопрос:
Мой DataGridView выглядит следующим образом:
Как очистить текст от повторяющихся ячеек в строках DataGridView?
Я пробовал ниже, но он очищает все значения 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;
}
}
Ответ №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?
Очевидно, вы считаете, что некоторые продукты одинаковы. Увы, вы забыли сказать, когда два произведения равны. Является ли продукт [Apple, UK, 1] равным [Apple, UK, 2]? И если да, то какую из них вы хотите показать?
Или вы хотите показать сумму: [Apple, UK, 3]?
А как насчет: [Apple, Ирландия, 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 amp;amp; 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)
amp;amp; CountryComparer.Equals(x.Country, y.Country);
Теперь, если позже вы решите, что хотите учитывать регистр при сравнении стран или, возможно, захотите использовать текущую культуру, вам нужно будет изменить это только в одном месте.
Использование средств сравнения упрощает изменение, а также ваш код: вам не нужно проверять наличие пустых имен и стран, которые обрабатываются средствами сравнения.
GetHashCode: единственное требование: если x равно y, верните тот же GetHashCode. если не равно, вы можете вернуть все, что хотите, но будет более эффективно, если вы вернете другой хэш-код.
public override int GetHashCode(Product product)
{
if (product == null) return 47855249; // just a number
return NameComparer.GetHashCode(product.Name)
^ CountryComparer.GetHashCode(product.Country);
}
Есть возможности для улучшения
Обычно не рекомендуется переплетать вашу модель с представлением вашей модели. Если в будущем вы захотите изменить способ отображения своих данных, например, вы захотите отобразить их в виде списка или графика, вам придется многое изменить.
Кроме того, если вы отделили свою модель от способа ее отображения, модульное тестирование вашей модели будет намного проще. Чтобы протестировать ваше представление, вам не понадобятся исходные данные, вы можете протестировать с граничными условиями, например, с пустым datagridview,
Прежде всего, вам нужен метод для извлечения продуктов из вашей модели:
private IEnumerable<Productm> FetchProducts(...) {...}
Итак, теперь у вас есть модульный тестируемый метод, который извлекает продукты. Приятно то, что вы даже скрыли, откуда вы получаете эту информацию: это может быть из базы данных, или XML-файла, или даже из Интернета: ваша форма не знает и не должна знать. Полностью разработан для изменений.
С помощью Visual Studio Designer вы определили столбцы. В каждом столбце отображается точное значение одного свойства. Какое свойство отображается в столбце, определено в свойстве DataGridViewColumn .DataPropertyName
columnName.DataPropertyName = nameof(Product.Name);
columnCountry.DatapropertyName = nameof(Product.Country);
...
Чтобы отобразить выбранные продукты, используйте свойство DataGridView.DataSource . Если вы назначаете a List<Product>
, то изменения, которые вносит оператор (добавление / удаление строк, изменение ячеек), не отражаются в списке. Если вы хотите автоматически обновлять изменения, внесенные оператором, используйте список привязок
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, а по-другому, или если вы хотите сравнить, используя текущую культуру, если вы хотите показать итоговые значения количества, или даже если вы хотите показать его на графике вместо таблицы: есть только одно место, которое вам нужно изменить.
Теперь каждое изменение, которое вносит оператор: добавить / удалить / изменить, отражается в вашем списке привязок, даже если строки отсортированы.
Например, если оператор указывает, что он закончил редактирование, нажав кнопку:
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>();