WP7 - привязка ListBox к вложенной коллекции ObservableCollection - PullRequest
3 голосов
/ 07 января 2011

У меня есть ObservableCollection объектов следующим образом:

public class UserDataViewModel
{
  private ObservableCollection<CategoryItem> _data = 
                                       new ObservableCollection<CategoryItem>();

  public ObservableCollection<CategoryItem> Data
  {
    get { return _data; }
    private set { }
  }
  // Other methods to set Data
}

Класс CategoryItem определяется как:

public class CategoryItem : INotifyPropertyChanged
{
  private string _name = null;
  private ObservableCollection<EntryItem> _entries = 
                                 new ObservableCollection<EntryItem>();

  public string Name
  {
    get { return _name; }
    set {
      if( value != _name ) {
        _name = value;
        NotifyPropertyChanged( "Name" );
      }
    }
  }

  public ObservableCollection<EntryItem> Entries
  {
    get { return _entries; }
    set {
      if( value != _entries ) {
        _entries = value;
        NotifyPropertyChanged( "Entries" );
      }
    }
  }
  // INotifyPropertyChanged code follows
}

Класс EntryItem определяется как:

public class EntryItem : INotifyPropertyChanged
{
  private string _name = null;

  public string Name
  {
    get { return _name; }
    set {
      if( value != _name ) {
        _name = value;
        NotifyPropertyChanged( "Name" );
      }
    }
  }
  // INotifyPropertyChanged code follows
}

Я пытаюсь связать это с ListBox.Каждый ListBoxItem состоит из 2 TextBlock с.Я хочу, чтобы первый TextBlock отображал свойство EntryItem.Name, а второй - свойство CategoryItem.Name.Вот что я попробовал в XAML (безуспешно):

<ListBox x:Name="MyListBox"
         Margin="0,0,-12,0"
         ItemsSource="{Binding Data}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Margin="0,0,0,17">
        <!--This should display EntryItem.Name-->
        <TextBlock Text="{Binding Entries.Name}"
                   TextWrapping="Wrap"
                   Margin="12,0,0,0"
                   Style="{StaticResource PhoneTextExtraLargeStyle}" />

        <!--This should display CategoryItem.Name-->
        <TextBlock Text="{Binding Name}"
                   TextWrapping="Wrap"
                   Margin="12,-6,0,0"
                   Style="{StaticResource PhoneTextSubtleStyle}" />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

В коде этой страницы я задаю:

DataContext = App.ViewModel; // ViewModel is of type UserDataViewModel

Я получаю ошибку привязки:

System.Windows.Data Error: BindingExpression path error: 'Name' property not found on 'System.Collections.ObjectModel.ObservableCollection`1[NestedCollection.ViewModels.EntryItem]' 'System.Collections.ObjectModel.ObservableCollection`1[NestedCollection.ViewModels.EntryItem]' (HashCode=123081170). BindingExpression: Path='Entries.Name' DataItem='NestedCollection.ViewModels.CategoryItem' (HashCode=121425257); target element is 'System.Windows.Controls.TextBlock' (Name=''); target property is 'Text' (type 'System.String')..

NestedCollection - название этого проекта, и все вышеперечисленные классы находятся в пространстве имен NestedCollection.ViewModels.

Отображается только содержимое второго TextBlock.Как мне это исправить?

Спасибо за вашу помощь, это сводит меня с ума уже несколько часов!

РЕДАКТИРОВАТЬ:

Предположимколлекция Data имеет 2 записи: «Кредитные карты» и «Учетные записи электронной почты» (это свойства Name каждого объекта CategoryItem в коллекции. Скажем, первый CategoryItem имеет объекты EntryItem «Visa», "Mastercard" и "American Express", а второй CategoryItem объект имеет EntryItem объекты "GMail" и "Hotmail", тогда я хочу, чтобы ListBox отображал:

Visa
Credit Cards

Mastercard
Credit Cards

American Express
Credit Cards

GMail
Email Accounts

Hotmail
Email Accounts

Я понимаю, что свойство Entries Data не имеет свойства Name, есть у каждой записи в нем. Есть ли способ индексировать Entries в привязке XAML?

Ответы [ 4 ]

3 голосов
/ 07 января 2011

Вы пытаетесь связать ObservableCollection<T> с TextBox.Подумайте об этом.

ObservableCollection<EntryItem> не имеет свойства с именем Name.EntryItem class делает.

Я предлагаю вам использовать ItemsControl вместо или Converter для преобразования EntryItem имен в строку через запятую.

После просмотра ваших правок:

Попробуйте следующий код:

<ListBox x:Name="MyListBox"
            Margin="0,0,-12,0"
            ItemsSource="{Binding Data}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid Name="RootGrid">
                <ItemsControl ItemsSource="{Binding Entries}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Margin="0,0,0,17">
                                <!--This should display EntryItem.Name-->
                                <TextBlock Text="{Binding Name}"
                                            TextWrapping="Wrap"
                                            Margin="12,0,0,0"
                                            Style="{StaticResource PhoneTextExtraLargeStyle}" />
                                <!--This should display CategoryItem.Name-->
                                <TextBlock Text="{Binding ElementName=RootGrid, Path=DataContext.Name}"
                                            TextWrapping="Wrap"
                                            Margin="12,-6,0,0"
                                            Style="{StaticResource PhoneTextSubtleStyle}" />
                            </StackPanel>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

- EDIT -

Похоже, это известная проблема в Silverlight 3.

http://forums.silverlight.net/forums/p/108804/280986.aspx

Чтобы обойти это, добавьте ссылку CategoryItem в EntryItem с именем Parent или что-то подобное для доступа к ней.

public class EntryItem : INotifyPropertyChanged
{
    public CategoryItem Parent { get; set; }
    ....
}

Или, как описано в ссылке выше, поместите ваш DataTemplate в UserControl, чтобы он заработал.

2 голосов
/ 07 января 2011

Предположение 1: UserDataViewModel действительно является ViewModel

Термин «ViewModel» в конце имени класса означает, что целью этого класса является поддержка определенного просмотр.Вы не ожидаете, что такая модель представления затруднит выполнение представлением, к которому она присоединена, своей работы.

Поэтому я бы предположил, что ваша "ViewModel" испорчена и нуждается в переработке.Начните с: -

public class EntryItem
{
    public string Name {get; set;}
    public CategoryItem Category {get; set;}
}

Ваш CategoryItem не нуждается в свойстве коллекции entires, а ваш UserDataView возвращает плоскую коллекцию всех EntryItem объектов.Привязка проста.

   <TextBlock Text="{Binding Name}"
                   TextWrapping="Wrap"
                   Margin="12,0,0,0"
                   Style="{StaticResource PhoneTextExtraLargeStyle}" />
   <TextBlock Text="{Binding Category.Name}"
                   TextWrapping="Wrap"
                   Margin="12,-6,0,0"
                   Style="{StaticResource PhoneTextSubtleStyle}" />   

Предположение 2: UserDataViewModel на самом деле не ViewModel

Возможно, что то, что вы называете моделью представления, на самом деле простомодель данных, упорядоченная в соответствии с ее хранилищем или общим использованием.Это объясняет, почему оно не соответствует требованиям фактического представления.

Я хотел бы представить еще одно предположение, которое в WP7, вероятно, будет верным (вероятно, в другом месте).При отображении представления содержимое коллекций не изменяется, равно как и названия элементов.Следовательно, наблюдаемая природа этих объектов (хотя, возможно, и полезна в других местах) не обязательна для работы представления.

Если эти предположения верны, тогда для представления элементов в представлении можно использовать преобразователь значений и дополнительный класс.более приемлемый способ: -

public class EntryHolder
{
    public EntryItem Entry {get; set;}
    public CategoryItem Category {get; set; }
}

public class CategoryToEntryItemExConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
         UserDataViewModel model = value as UserDataViewModel;
         if (model != null)
         {
              return model.Data.SelectMany(c => c.Entries
                 .Select(e => new EntryHolder() { Category = c, Entry = e})
              );
         }
         else
         {
             return null;
         }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Теперь вы можете настроить свой Xaml: -

<Grid x:Name="LayoutRoot">
    <Grid.Resources>
   <local:CategoryToEntryItemExConverter x:Key="ItemsConv" />
</Grid.Resources>
</Grid>

...

<ListBox x:Name="MyListBox"
         Margin="0,0,-12,0"
         ItemsSource="{Binding Converter={StaticResource ItemsConv}}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Margin="0,0,0,17">
        <!--This should display EntryItem.Name-->
        <TextBlock Text="{Binding Entry.Name}"
                   TextWrapping="Wrap"
                   Margin="12,0,0,0"
                   Style="{StaticResource PhoneTextExtraLargeStyle}" />

        <!--This should display CategoryItem.Name-->
        <TextBlock Text="{Binding Category.Name}"
                   TextWrapping="Wrap"
                   Margin="12,-6,0,0"
                   Style="{StaticResource PhoneTextSubtleStyle}" />
      </StackPanel>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>  
2 голосов
/ 07 января 2011

Мне кажется, что вы пытаетесь отобразить сгруппированные данные в одном элементе управления из одного (вложенного) источника данных, и в этом случае вам следует рассмотреть возможность использования элемента управления LongListSelector из Silverlight Toolkit для WP7 .У WindowsPhoneGeek есть хорошее сообщение в блоге о том, как использовать его в ситуации, аналогичной вашей.

В качестве альтернативы вам необходимо использовать элементы управления вложенными элементами.Если вам не нужна концепция выделения, просто установите шаблон элемента для ListBox, чтобы он был ItemsControl с ItemsSource = "{Binding Entries}".Для ItemsControl DataContext будет отдельным CategoryItem, так что вы можете добавить заголовок TextBlock, который связывается со свойством Name, если это необходимо.Это в основном то, что делает LongListSelector, но предлагает большую гибкость.

Если вам нужна концепция выбора для записей, то я подозреваю, что она вам не нужна на уровне CategoryItem, поэтому сделайте root иItemsControl и ItemTemplate объекта ListBox.В этом случае вам нужно быть осторожным с прокруткой, которую обеспечивает ListBox, так что вы можете столкнуться с запутанным пользовательским интерфейсом, отсюда мое первоначальное предложение попробовать LongListSelector.

1 голос
/ 07 января 2011

Ваш вопрос на самом деле не имеет смысла - какой элемент в списке записей вы хотите назвать? Первый предмет? Нет имени всей коллекции, только для каждого элемента в этой коллекции.

Если вам нужен первый элемент, вы можете привязать TextBox к Entries [0]. Name - я думаю, что это работает для Silverlight на Windows Phone (я не помню, поддерживаются ли индексаторы).

Если индексаторы не поддерживаются, вам нужно написать IValueConverter, который может конвертировать из ObservableCollection<EntityItem> в строку, и использовать его в качестве конвертера в привязке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...