简体   繁体   English

WP7 - ListBox绑定到嵌套的ObservableCollection

[英]WP7 - ListBox Binding to nested ObservableCollection

I have an ObservableCollection of objects as follows: 我有一个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
}

The CategoryItem class is defined as: 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
}

The EntryItem class is defined as: 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
}

I'm trying to bind this to a ListBox . 我正在尝试将其绑定到ListBox Each ListBoxItem consists of 2 TextBlock s. 每个ListBoxItem由2个TextBlock组成。 I want the first TextBlock to display the EntryItem.Name property and the second to display the CategoryItem.Name property. 我希望第一个TextBlock显示EntryItem.Name属性,第二个显示CategoryItem.Name属性。 Here's what I tried in XAML (without success): 这是我在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>

In the code-behind for this page I'm setting: 在这个页面的代码隐藏我设置:

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

I keep getting the binding error: 我一直收到绑定错误:

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 is the name of this project and all of the above classes are in the NestedCollection.ViewModels namespace. NestedCollection是此项目的名称,所有上述类都在NestedCollection.ViewModels命名空间中。

Only the contents of the second TextBlock are being displayed. 仅显示第二个TextBlock的内容。 How do I fix this? 我该如何解决?

Thanks for your help, this has been driving me nuts for a few hours now! 谢谢你的帮助,这让我疯了几个小时吧!

EDIT: 编辑:

Suppose the Data collection has 2 entries, "Credit Cards" and "Email Accounts" (these are Name property of each CategoryItem object in the collection. Say the first CategoryItem has the EntryItem objects "Visa", "Mastercard" and "American Express", and the second CategoryItem object has the EntryItem objects "GMail" and "Hotmail", then I want the ListBox to display: 假设Data集合有2个条目,“信用卡”和“电子邮件帐户”(这些是集合中每个CategoryItem对象的Name属性。假设第一个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

I realize that the Entries property of Data does not have a Name property, each entry within it does. 我意识到DataEntries属性没有Name属性,其中的每个条目都有。 Is there anyway to index into the Entries in the XAML binding? 是否有索引到XAML绑定中的Entries

You are trying to bind a ObservableCollection<T> to a TextBox . 您正在尝试将ObservableCollection<T>绑定到TextBox Think about it. 想一想。

ObservableCollection<EntryItem> does not have a property named Name . ObservableCollection<EntryItem>没有名为Name的属性。 EntryItem class does. EntryItem类可以。

I suggest you use an ItemsControl instead or use a Converter to convert EntryItem names into a comma separated string. 我建议您使用ItemsControl或使用ConverterEntryItem名称转换为逗号分隔的字符串。

After looking at your edit: 看完你的编辑后:

Try following code: 请尝试以下代码:

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

Looks like it's a known problem in Silverlight 3 . 看起来这是Silverlight 3的一个已知问题。

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

To workaround that, either put a reference of CategoryItem in EntryItem named Parent or something similar to access it. 要解决这个问题,要么将CategoryItem的引用EntryItem名为Parent EntryItem中,要么类似于访问它。

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

Or as discussed in the link above, put your DataTemplate into a UserControl for it to work. 或者如上面的链接中所讨论的,将DataTemplate放入UserControl以使其工作。

It looks to me like your trying to display grouped data in a single control from a single (nested) data source, in which case you should consider using the LongListSelector control from the Silverlight Toolkit for WP7 . 在我看来,您试图从单个(嵌套)数据源在单个控件中显示分组数据,在这种情况下,您应该考虑使用Silverlight Toolkit for WP7中的LongListSelector控件。 WindowsPhoneGeek has a good blog post about how to use it in a similar situation to yours. WindowsPhoneGeek有一篇关于如何在类似情况下使用它的博客文章

Alternatively, you would need to use nested items controls. 或者,您需要使用嵌套项控件。 If you don't need the concept of selection, then just set the item template for the ListBox to be an ItemsControl with ItemsSource="{Binding Entries}". 如果您不需要选择的概念,那么只需将ListBox的项模板设置为ItemsControl,其ItemsSource =“{Binding Entries}”。 For the ItemsControl, the DataContext will be an individual CategoryItem, so you can add a TextBlock header that binds to the Name property if necessary. 对于ItemsControl,DataContext将是一个单独的CategoryItem,因此您可以根据需要添加绑定到Name属性的TextBlock标头。 This is basically what the LongListSelector is doing, but offers greater flexibility. 这基本上就是LongListSelector正在做的事情,但提供了更大的灵活性。

If you need the concept of selection for the entries, then I suspect you don't need it at the CategoryItem level, so make the root and ItemsControl and the ItemTemplate a ListBox. 如果您需要选择条目的概念,那么我怀疑您在CategoryItem级别不需要它,因此将root和ItemsControl以及ItemTemplate设为ListBox。 This way round you'll need to be careful with scrolling, which the ListBox provides for itself, so you may end up with a confusing user experience, hence my original suggestion of trying the LongListSelector. 这种方式你需要小心滚动,ListBox为自己提供,所以你最终可能会有一个令人困惑的用户体验,因此我最初的建议是尝试LongListSelector。

Assumption 1: UserDataViewModel truely is a ViewModel 假设1:UserDataViewModel确实是一个ViewModel

The term "ViewModel" on the end of class name it implies that the purpose of that class is to support a specific view. 类名末尾的术语“ViewModel”暗示该类的目的是支持特定的视图。 You would not expect such a view model to make it difficult for the view to which it is attached to do its job. 您不会期望这样的视图模型使其附加的视图难以完成其工作。

I would therefore suggest your "ViewModel" is messed up and needs redevelopment. 因此,我建议你的“ViewModel”搞砸了,需要重新开发。 Start with:- 从...开始:-

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

Your CategoryItem doesn't need an entires collection property and your UserDataView returns a flat collection of all EntryItem objects. 您的CategoryItem不需要entires集合属性,您的UserDataView返回所有EntryItem对象的扁平集合。 Binding is easy. 绑定很容易。

   <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}" />   

Assumption 2: UserDataViewModel isn't actually a ViewModel 假设2:UserDataViewModel实际上不是ViewModel

Its possible that what you've called a view model is in fact just a model of data arranged in manner matching its storage or general usage. 您所谓的视图模型实际上可能只是一种与其存储或一般用法相匹配的数据模型。 This would account for why it doesn't match the actual view's requirements. 这将解释为什么它与实际视图的要求不匹配。

I would introduce another assumption which on WP7 is likely be true (probably would be elsewhere). 我将介绍另一个假设,在WP7上可能是真的(可能在其他地方)。 During display of the view the contents of the collections are not modified nor are the names of the items. 在显示视图期间,不修改集合的内容,也不修改项目的名称。 Hence the Observable nature of these objects (whilst possibly being useful elsewhere) are not necessary for the view to work. 因此,这些对象的Observable性质(尽管可能在其他地方有用)对于视图的工作不是必需的。

If these assumptions are true then a value converter and an additional class can be used to present the items in more acceptable way:- 如果这些假设为真,则可以使用值转换器和附加类以更可接受的方式呈现项目: -

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

Now you would adjust your Xaml:- 现在你要调整你的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>  

Your question doesn't really make sense - which item in the Entries list do you want the name of? 您的问题没有意义 - 条目列表中的哪个项目您想要名称? The first item? 第一项? There is no name of the entire collection, only on each element within that collection. 整个集合没有名称,仅在该集合中的每个元素上。

If you want the first item you could have your TextBox bind to Entries[0].Name - I think that works for Silverlight on Windows Phone (I can't remember if indexers are supported). 如果你想要第一个项目你可以让你的TextBox绑定到Entries [0] .Name - 我认为这适用于Windows Phone上的Silverlight(我不记得是否支持索引器)。

If indexers aren't supported, then you'd need to write an IValueConverter that can convert from ObservableCollection<EntityItem> to string and use that as the Converter in the Binding. 如果不支持索引器,那么您需要编写一个可以从ObservableCollection<EntityItem>转换为字符串的IValueConverter,并将其用作Binding中的Converter。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM