How to bind Custom Object to ListBox in WPF

pblyt picture pblyt · Mar 5, 2013 · Viewed 10.9k times · Source

I am new to WPF so please accept my apologies if my question is stupid.

I am creating a food ordering system which consists of 2 main sections, the "Food Menu" and the "Order List". When user chooses an item from the food menu, it will be added to the a listbox control which represents the order list.

I have created a few custom objects: Order, OrderLine, Item, as well as a Collection class, OrderLineCollection, for "OrderLine". They look like the following:

public class Order
{
    public string ID { get; set; }
    public DateTime DateTime { get; set; }
    public double TotalAmt { get; set; }
    public OrderLineCollection LineItems { get; set; }
}

public class OrderLine
{
    public Item Item { get; set; }
    public double UnitPrice { get; set; }
    public int Quantity { get; set; }
    public double SubTotal { get { return unitPrice * quantity; } }
}

[Serializable]
public class OrderLineCollection : CollectionBase
{
    public OrderLine this[int index]
    {
        get { return (OrderLine)List[index]; }
        set { List[index] = value; }
    }
}

public class Item
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public double UnitPrice { get; set; }
    public byte[] Image { get; set; }
}

My ListBox control has a DataTemplate so that more details are shown for each item. The XAML as below:

<Page x:Class="FoodOrdering.OrderList"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Order List">
    <ListBox Name="lbxOrder" HorizontalContentAlignment="Stretch">            
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Grid Margin="5">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="30"/>
                        <ColumnDefinition Width="80"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Text="{Binding item.name}"/>
                    <TextBlock Grid.Row="0" Grid.Column="1" Text="x "/>
                    <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding quantity}" Margin="10,0,0,0"/>
                    <TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding subTotal, Converter={StaticResource priceConverter}}" HorizontalAlignment="Right"/>
                    <Button Grid.Row="1" Grid.Column="2" Margin="0,5,0,0" Click="btnDelete_Click">Delete</Button>
                </Grid>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Page>

so when items are added, the ListBox will look like the below image: https://dl.dropbox.com/u/6322828/orderList.png

In the code-behind, I have created a static variable currentOrder for storing the current order and it will be used by other classes/methods.

And every time its value is changed, the following stupid method LoadCurrentOrder() is called to refresh the ListBox view.

public partial class OrderList : Page
{
    public static Order currentOrder = new Order();
    public OrderList()
    {
        InitializeComponent();
        LoadCurrentOrder();
    }

    public void LoadCurrentOrder()
    {
        lbxOrder.Items.Clear();
        foreach (OrderLine ol in currentOrder.LineItems)
            lbxOrder.Items.Add(ol);
    }
}

My problem is how can I bind the data in an elegant way (such as using Resources ItemsSource and so on) instead of using the above method, so that the ListBox will update automatically every time the value of the variable is changed?

I tried

lbxOrder.ItemsSource = currentOrder; 

but it does not work as currentOrder is not a System.Collections.IEnumerable object.

Answer

Federico Berasategui picture Federico Berasategui · Mar 5, 2013

1 - Don't create your own collection types, the .Net framework Base Class Library already has pretty much everything you need.

Remove the OrderLineCollection and use an ObservableCollection<OrderLine>.

2 - Try to stick to MVVM conventions. This means you should not manipulate UI elements in code.

public OrderList()
{
    InitializeComponent();
    LoadCurrentOrder();
    DataContext = this;
}

then in XAML:

<ListBox ItemsSource="{Binding LineItems}" HorizontalContentAlignment="Stretch">

3 - Do not name UI elements in XAML unless you need to reference them in XAML. This will help you reconsider your approach every time you find yourself wanting to manipulate UI elements in procedural code.

4 - Get used to the C# naming convention. Property names should be "proper cased" (I.E LineItems instead of lineItems)