Reputation: 21
I'll start by giving an example cause it will be easier.
Lets say I have a ListBox that's named "lbDestinations" and I have a class for that listbox called Destination.
Destination.cs
public class Destination
{
public string Name { get; set; }
public int Distance { get; set; }
public int Price { get; set; }
public Destination(string name, int distance, int price)
{
Name = name;
Distance = distance;
Price = price;
}
public override string ToString()
{
return string.Format("{0} {1} km {2} $", Name, Distance, Price);
}
}
Now I want to add destinations with a name, the distance to that location and the price for that trip. Lets have a couple destinations added for examples and stuff..
London 100 km 200 $
Berlin 400 km 800 $
Paris 200 km 400 $
Madrid 150 km 300 $
Now if I have all those in that order in the ListBox and I do lbDestinations.Sorted it will Sort them alphabetically, but I don't want that..
I want to sort them by the distance or the price.. The output has to be "Name Distance Price"
I tried a couple things but none of them worked
Upvotes: 0
Views: 129
Reputation: 8776
The accepted answer is excellent, but I'd be remiss if I didn't mention how DataGridView
might save you a lot of legwork by automatically configuring itself based on your Destination
class.
I have a class [...] called Destination. The output has to be "Name Distance Price".
public class Destination
{
[ReadOnly(true)]
public string Name { get; set; } = string.Empty;
[ReadOnly(true)]
public int Distance { get; set; }
[ReadOnly(false)]
public decimal Price { get; set; }
public override string ToString() =>
string.Format($"{Name} {Distance} km {Price} $");
}
I want to sort them by the distance or the price.
Implementation where consecutive clicks on same header alternates low-to-high or high-to-low.
private void sortByHeader(object? sender, DataGridViewCellMouseEventArgs e)
{
Destination[] tmp;
if (!e.ColumnIndex.Equals(-1))
{
var column = dataGridView.Columns[e.ColumnIndex].Name;
if (column.Equals(_prevColumn))
{
_highToLow = !_highToLow;
}
else
{
_highToLow = false;
_prevColumn = column;
}
switch (column)
{
case nameof(Destination.Name):
tmp = _highToLow ?
Destinations.OrderByDescending(_ => _.Name).ToArray() :
Destinations.OrderBy(_ => _.Name).ToArray();
break;
case nameof(Destination.Distance):
tmp = _highToLow ?
Destinations.OrderByDescending(_ => _.Distance).ToArray() :
Destinations.OrderBy(_ => _.Distance).ToArray();
break;
case nameof(Destination.Price):
tmp = _highToLow ?
Destinations.OrderByDescending(_ => _.Price).ToArray() :
Destinations.OrderBy(_ => _.Price).ToArray();
break;
default:
return;
}
Destinations.Clear();
foreach (var destination in tmp)
{
Destinations.Add(destination);
}
}
}
string? _prevColumn = null;
bool _highToLow = false;
Auto-Generate Columns
The method that loads the main form configures the DataGridView
using the Destination
class as a template and attaches the dataGridView.ColumnHeaderMouseClick
event to sort the data. The DataSource
of the data grid is set to Destinations
which is a BindingList<Destination>
.
public partial class MainForm : Form
{
public MainForm() =>InitializeComponent();
private readonly BindingList<Destination> Destinations= new BindingList<Destination>();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
dataGridView.RowTemplate.Height = 60;
// Add destinations interactively?
dataGridView.AllowUserToAddRows= false;
dataGridView.DataSource= Destinations;
#region F O R M A T C O L U M N S
Destinations.Add(new Destination()); // <= Auto-generate columns
dataGridView.Columns[nameof(Destination.Name)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView.Columns[nameof(Destination.Distance)].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
dataGridView.Columns[nameof(Destination.Price)].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
dataGridView.Columns[nameof(Destination.Price)].DefaultCellStyle.Format = "F2";
Destinations.Clear();
#endregion F O R M A T C O L U M N S
dataGridView.ColumnHeaderMouseClick += sortByHeader;
addDestinationsToExample();
}
.
.
.
}
Now [...] add destinations with a name, the distance to that location and the price...
private void addDestinationsToExample()
{
Destinations.Add(new Destination
{
// * Had to make the Price sort different from Distance sort!
Name = "London - VIP",
Distance= 100,
Price= 1200,
});
Destinations.Add(new Destination
{
Name = "Berlin",
Distance= 400,
Price= 800,
});
Destinations.Add(new Destination
{
Name = "Paris",
Distance= 200,
Price= 400,
});
Destinations.Add(new Destination
{
Name = "Madrid",
Distance= 150,
Price= 300,
});
}
I hope this adds to your toolbox of possible ways to achieve your objective.
Upvotes: 0
Reputation: 112259
Instead of adding items to the listbox one by one, you can assign a collection to the DataSource
of the listbox. This enables you to apply a custom sort to the collection.
E.g.
var destinations = new List<Destination>();
destinations.Add(new Desination { Name = "London", Distance = 100, Price = 200 });
...
destinations.Sort( (a, b) => a.Price.CompareTo(b.Price) );
lbDestinations.DataSource = null; // Ensures the listbox refreshes.
lbDestinations.DataSource = destinations;
You can also use LINQ to sort by more than one column:
var destinations = source
.OrderBy(d => d.Price)
.ThenBy(d => d.Name)
.ToList();
Upvotes: 2