Как привязать пользовательский объект к ListBox в WPF

Я новичок в WPF, поэтому примите мои извинения, если мой вопрос глуп.

Я создаю систему заказа еды, которая состоит из 2 основных разделов: «Меню еды» и «Список заказов». Когда пользователь выбирает элемент из меню еды, он будет добавлен в элемент управления списка, который представляет список заказов.

Я создал несколько пользовательских объектов: Order, OrderLine, Item, а также класс Collection OrderLineCollection для "OrderLine". Они выглядят следующим образом:

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; }
}

Мой элемент управления ListBox имеет элемент DataTemplate, так что для каждого элемента отображается более подробная информация. XAML, как показано ниже:

<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>

поэтому при добавлении элементов ListBox будет выглядеть так, как показано ниже: https://dl.dropbox.com/u/6322828/orderList.png

В коде программной части я создал статическую переменную currentOrder для хранения текущего заказа, и она будет использоваться другими классами/методами.

И каждый раз, когда его значение изменяется, вызывается следующий глупый метод LoadCurrentOrder() для обновления представления ListBox.

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);
    }
}

Моя проблема заключается в том, как я могу связать данные элегантным способом (например, используя Resources ItemsSource и т. д.) вместо использования вышеуказанного метода, чтобы ListBox автоматически обновлялся каждый раз, когда значение переменной изменяется?

Я попытался

lbxOrder.ItemsSource = currentOrder; 

но это не работает, так как currentOrder не является объектом System.Collections.IEnumerable.


person pblyt    schedule 05.03.2013    source источник


Ответы (2)


1. Не создавайте свои собственные типы коллекций, в библиотеке базовых классов .Net framework уже есть почти все, что вам нужно.

Удалите OrderLineCollection и используйте ObservableCollection<OrderLine>.

2 - Старайтесь придерживаться соглашений MVVM. Это означает, что вы не должны манипулировать элементами пользовательского интерфейса в коде.

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

затем в XAML:

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

3. Не называйте элементы пользовательского интерфейса в XAML, если вам не нужно ссылаться на них в XAML. Это поможет вам пересматривать свой подход каждый раз, когда вы обнаружите, что хотите манипулировать элементами пользовательского интерфейса в процедурном коде.

4. Привыкайте к соглашению об именах C#. Имена свойств должны быть "в правильном регистре" (т.е. LineItems вместо lineItems)

person Federico Berasategui    schedule 05.03.2013
comment
Спасибо за советы по соглашению, особенно по именованию, думаю, я давно испортил концепцию... Я отредактировал имена свойств на случай, если другие могут запутаться. ObservableCollection отлично работает в моем проекте WPF. Но что, если я создам классы в проекте библиотеки классов? Я пробовал, и похоже, что ObservableCollection поддерживает только использование XAML? - person pblyt; 06.03.2013
comment
@pblyt определен класс System.Collections.ObjectModel.ObservableCollection<T> в сборке System.dll, поэтому он является основной частью .Net BCL, он никоим образом не зависит от каких-либо сборок или классов WPF. - person Federico Berasategui; 06.03.2013

Все, что вам нужно сделать, это преобразовать свойство lineItems в свойство ObservableCollection<yourType>. Затем, когда эта коллекция изменяется (элементы добавляются, удаляются), список автоматически обновляется.

Если ваша проблема в том, что сам порядок меняется, и вам нужно обновить список. Затем все, что вам нужно сделать, это реализовать INotifyPropertyChanged и, когда порядок изменится, вызвать уведомление о том, что свойство изменилось, и пользовательский интерфейс обновится через привязку.

Что касается привязки более элегантным способом. Вы можете привязать к currentOrder.lineItems:

lbxOrder.ItemsSource = currentOrder.lineItems;//this should be an observable collection if you intend to have items change
person dutzu    schedule 05.03.2013
comment
Спасибо @dutzu, ObservableCollection отлично работает в моем проекте WPF. Но что, если я создам классы в проекте библиотеки классов и добавлю их в качестве ссылки в проект WPF? Я пробовал, и похоже, что ObservableCollection поддерживает только использование XAML. - person pblyt; 06.03.2013
comment
Вы всегда можете обернуть список объектов, которые вы получаете из библиотеки классов, в качестве ObservableCollection для удовлетворения ваших потребностей пользовательского интерфейса. - person dutzu; 06.03.2013