Klopeh
Klopeh

Reputation: 21

Custom Sort a ListBox in C# Visual Studio

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

Answers (2)

IV.
IV.

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.

screenshot


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

Olivier Jacot-Descombes
Olivier Jacot-Descombes

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

Related Questions