Is it possible to bind a List to a ListView in WinForms?
Alternatively, you can use DataGridView if you want data binding. Using BindingList and BindingSource will update your DataGrid when new item is added to your list.
var barcodeContract = new BarcodeContract { Barcode = barcodeTxt.Text, Currency = currencyTxt.Text, Price = priceTxt.Text };
list.Add(barcodeContract);
var bindingList = new BindingList<BarcodeContract>(list);
var source = new BindingSource(bindingList, null);
dataGrid.DataSource = source;
And data model class
public class BarcodeContract
{
public string Barcode { get; set; }
public string Price { get; set; }
public string Currency { get; set; }
}
Nice binding implementation for ListView
http://www.interact-sw.co.uk/utilities/bindablelistview/source/
I use the following technique to bind data to a ListView.
It supports proper (not text-based) sorting. In the case above, by string, DateTime and integer.
The ListView above was generated with this code:
var columnMapping = new List<(string ColumnName, Func<Person, object> ValueLookup, Func<Person, string> DisplayStringLookup)>()
{
("Name", person => person.Name, person => person.Name),
("Date of birth", person => person.DateOfBirth, person => $"{person.DateOfBirth:dd MMM yyyy}"),
("Height", person => person.HeightInCentimetres, person => Converter.CentimetresToFeetInchesString(person.HeightInCentimetres))
};
var personListview = new ListViewEx<Person>(columnMapping)
{
FullRowSelect = true,
View = View.Details,
Left = 20,
Top = 20,
Width = 500,
Height = 300,
};
var people = new[]
{
new Person("Cathy Smith", DateTime.Parse("1980-05-15"), 165),
new Person("Bill Wentley", DateTime.Parse("1970-10-30"), 180),
new Person("Alan Bridges", DateTime.Parse("1990-03-22"), 190),
};
personListview.AddRange(people);
Controls.Add(personListview);
In the column mapping, you'll notice that you have to specify how to get the item's value (for sorting) as well as a string (for displaying).
Full source:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace GUI
{
public class ListViewEx<T> : ListView
{
public ListViewEx(IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> columnInfo) : base()
{
ColumnInfo = columnInfo;
DoubleBuffered = true;
//Create the columns
columnInfo
.Select(ci => ci.ColumnName)
.ToList()
.ForEach(columnName =>
{
var col = Columns.Add(columnName);
col.Width = -2;
});
//Add the sorter
lvwColumnSorter = new ListViewColumnSorter<T>(columnInfo);
ListViewItemSorter = lvwColumnSorter;
ColumnClick += ListViewEx_ColumnClick;
}
IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> ColumnInfo { get; }
private readonly ListViewColumnSorter<T> lvwColumnSorter;
public void Add(T item)
{
var lvi = Items.Add("");
lvi.Tag = item;
RefreshContent();
}
public void AddRange(IList<T> items)
{
foreach (var item in items)
{
Add(item);
}
}
public void Remove(T item)
{
if (item == null) return;
var listviewItem = Items
.Cast<ListViewItem>()
.Select(lvi => new
{
ListViewItem = lvi,
Obj = (T)lvi.Tag
})
.FirstOrDefault(lvi => item.Equals(lvi.Obj))
.ListViewItem;
Items.Remove(listviewItem);
RefreshContent();
}
public List<T> GetSelectedItems()
{
var result = SelectedItems
.OfType<ListViewItem>()
.Select(lvi => (T)lvi.Tag)
.ToList();
return result;
}
public void RefreshContent()
{
var columnsChanged = new List<int>();
Items
.Cast<ListViewItem>()
.Select(lvi => new
{
ListViewItem = lvi,
Obj = (T)lvi.Tag
})
.ToList()
.ForEach(lvi =>
{
//Update the contents of this ListViewItem
ColumnInfo
.Select((column, index) => new
{
Column = column,
Index = index
})
.ToList()
.ForEach(col =>
{
var newDisplayValue = col.Column.DisplayStringLookup(lvi.Obj);
if (lvi.ListViewItem.SubItems.Count <= col.Index)
{
lvi.ListViewItem.SubItems.Add("");
}
var subitem = lvi.ListViewItem.SubItems[col.Index];
var oldDisplayValue = subitem.Text ?? "";
if (!oldDisplayValue.Equals(newDisplayValue))
{
subitem.Text = newDisplayValue;
columnsChanged.Add(col.Index);
}
});
});
columnsChanged.ForEach(col => { Columns[col].Width = -2; });
//AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
//AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
}
private void ListViewEx_ColumnClick(object sender, ColumnClickEventArgs e)
{
if (e.Column == lvwColumnSorter.ColumnToSort)
{
if (lvwColumnSorter.SortOrder == SortOrder.Ascending)
{
lvwColumnSorter.SortOrder = SortOrder.Descending;
}
else
{
lvwColumnSorter.SortOrder = SortOrder.Ascending;
}
}
else
{
lvwColumnSorter.ColumnToSort = e.Column;
lvwColumnSorter.SortOrder = SortOrder.Ascending;
}
Sort();
}
}
public class ListViewColumnSorter<T> : IComparer
{
public ListViewColumnSorter(IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> columnInfo)
{
ColumnInfo = columnInfo;
}
public int Compare(object x, object y)
{
if (x == null || y == null) return 0;
int compareResult;
var listviewX = (ListViewItem)x;
var listviewY = (ListViewItem)y;
var objX = (T)listviewX.Tag;
var objY = (T)listviewY.Tag;
if (objX == null || objY == null) return 0;
var valueX = ColumnInfo[ColumnToSort].ValueLookup(objX);
var valueY = ColumnInfo[ColumnToSort].ValueLookup(objY);
compareResult = Comparer.Default.Compare(valueX, valueY);
if (SortOrder == SortOrder.Ascending)
{
return compareResult;
}
else if (SortOrder == SortOrder.Descending)
{
return -compareResult;
}
else
{
return 0;
}
}
public int ColumnToSort { get; set; } = 0;
public SortOrder SortOrder { get; set; } = SortOrder.Ascending;
public IList<(string ColumnName, Func<T, object> ValueLookup, Func<T, string> DisplayStringLookup)> ColumnInfo { get; }
}
}
The ListView class does not support design time binding. An alternative is presented in this project.