WP7 - ListBox绑定到嵌套的ObservableCollection

时间:2011-01-07 06:03:25

标签: silverlight data-binding windows-phone-7

我有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属性。说第一个{{ 1}}有CategoryItem个对象“Visa”,“Mastercard”和“American Express”,第二个EntryItem对象有CategoryItem个对象“GMail”和“Hotmail”,然后我希望EntryItem显示:

ListBox

我意识到Visa Credit Cards Mastercard Credit Cards American Express Credit Cards GMail Email Accounts Hotmail Email Accounts 的{​​{1}}属性没有Entries属性,其中的每个条目都有。无论如何要在XAML绑定中索引Data吗?

4 个答案:

答案 0 :(得分:3)

您正在尝试将ObservableCollection<T>绑定到TextBox。想一想。

ObservableCollection<EntryItem>没有名为Name的媒体资源。 EntryItem上课。

我建议您改为使用ItemsControl或使用ConverterEntryItem名称转换为逗号分隔的字符串。

查看编辑后:

请尝试以下代码:

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

- 编辑 -

看起来它是Silverlight 3中的一个已知问题。

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

要解决此问题,请在名为CategoryItem的{​​{1}}或类似内容中添加EntryItem的引用以访问它。

Parent

或者如上面的链接中所述,将public class EntryItem : INotifyPropertyChanged { public CategoryItem Parent { get; set; } .... } 放入DataTemplate以使其正常工作。

答案 1 :(得分:2)

在我看来,您试图在单个控件中显示来自单个(嵌套)数据源的分组数据,在这种情况下,您应该考虑使用Silverlight Toolkit for WP7中的LongListSelector控件。 WindowsPhoneGeek有一个good blog post关于如何在类似情况下使用它。

或者,您需要使用嵌套项控件。如果您不需要选择的概念,那么只需将ListBox的项模板设置为ItemsControl,其ItemsSource =“{Binding Entries}”。对于ItemsControl,DataContext将是一个单独的CategoryItem,因此您可以根据需要添加绑定到Name属性的TextBlock标头。这基本上就是LongListSelector正在做的事情,但提供了更大的灵活性。

如果您需要选择条目的概念,那么我怀疑您在CategoryItem级别不需要它,因此将root和ItemsConmp和ItemTemplate设为ListBox。这种方式你需要小心滚动,ListBox为自己提供,所以你最终可能会有一个令人困惑的用户体验,因此我最初建议尝试LongListSelector。

答案 2 :(得分:2)

假设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上可能是真的(可能在其他地方)。在显示视图期间,不修改集合的内容,也不修改项目的名称。因此,这些对象的Observable性质(虽然可能在其他地方可能有用)对于视图无效是不必要的。

如果这些假设为真,则可以使用值转换器和附加类以更可接受的方式显示项目: -

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>  

答案 3 :(得分:1)

您的问题没有意义 - 条目列表中的哪个项目您想要名称?第一项?整个集合没有名称,仅在该集合中的每个元素上。

如果你想要第一个项目你可以将你的TextBox绑定到Entries [0] .Name - 我认为这适用于Windows Phone上的Silverlight(我不记得是否支持索引器)。

如果不支持索引器,那么您需要编写一个可以从ObservableCollection<EntityItem>转换为字符串的IValueConverter,并将其用作绑定中的转换器。