12

好的,所以这个问题与 Windows Phone 7/Silverlight(更新 WP7 工具,2010 年 9 月)有关,专门过滤底层ObservableCollection<T>.

在使用 WP7 模板 Pivot 控件应用程序时,我遇到了一个问题,即更改 中的基础项目ObservableCollection<T>不会导致屏幕上的 ListBox 被更新。基本上,示例应用程序有两个枢轴,第一个直接绑定到底层ObservableCollection<T>,第二个绑定到 a CollectionViewSource(即,表示底层上的过滤视图ObservableCollection<T>)。

添加到ObservableCollection<T>implement的底层项目INotifyPropertyChanged,如下所示:

public class ItemViewModel : INotifyPropertyChanged
{       
    public string LineOne
    {
        get { return _lineOne; }
        set
        {
            if (value != _lineOne)
            {
                _lineOne = value;
                NotifyPropertyChanged("LineOne");
            }
        }
    } private string _lineOne;

    public string LineTwo
    {
        get { return _lineTwo; }
        set
        {
            if (value != _lineTwo)
            {
                _lineTwo = value;
                NotifyPropertyChanged("LineTwo");
            }
        }
    } private string _lineTwo;

    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (value != _isSelected)
            {
                _isSelected = value;
                NotifyPropertyChanged("IsSelected");
            }
        }
    } private bool _isSelected = false;

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

然后,在主类中,编制了一个数据集合(为简洁起见减少了列表,还请注意,与其他项目不同,三个 LoadData() 条目具有 IsSelected == true):

 public class MainViewModel : INotifyPropertyChanged
 {
  public MainViewModel()
  {
   this.Items = new ObservableCollection<ItemViewModel>();
  }

  public ObservableCollection<ItemViewModel> Items { get; private set; }

  public bool IsDataLoaded
  {
   get;
   private set;
  }

   public void LoadData()
  {
   this.Items.Add(new ItemViewModel() { LineOne = "runtime one", IsSelected = true, LineTwo = "Maecenas praesent accumsan bibendum" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime two", LineTwo = "Dictumst eleifend facilisi faucibus" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime three", IsSelected = true, LineTwo = "Habitant inceptos interdum lobortis" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime four", LineTwo = "Nascetur pharetra placerat pulvinar" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime five", IsSelected = true, LineTwo = "Maecenas praesent accumsan bibendum" });
   this.Items.Add(new ItemViewModel() { LineOne = "runtime six", LineTwo = "Dictumst eleifend facilisi faucibus" });
   this.IsDataLoaded = true;
  }

  public event PropertyChangedEventHandler PropertyChanged;
  public void NotifyPropertyChanged(String propertyName)
  {
   if (null != PropertyChanged)
   {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
  }
 }

在 MainPage.xaml 文件中,第一个 PivotItemSource直接基于ObservableCollection<T>列表。在第二个 Pivot 中,屏幕上的 ListBox 将其ItemSourceProperty 设置为 a CollectionViewSource,其基础源基于上面ObservableCollection<T>填充的内容LoadData()

<phone:PhoneApplicationPage.Resources>
    <CollectionViewSource x:Key="IsSelectedCollectionView" Filter="CollectionViewSource_SelectedListFilter">
    </CollectionViewSource>
</phone:PhoneApplicationPage.Resources>

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <!--Pivot Control-->
    <controls:Pivot Title="MY APPLICATION">
        <!--Pivot item one-->
        <controls:PivotItem Header="first">
            <!--Double line list with text wrapping-->
            <ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                      <StackPanel Margin="0,0,0,17" Width="432">
                          <TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                          <TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                      </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </controls:PivotItem>

        <!--Pivot item two-->
        <controls:PivotItem Header="second"> 
            <!--Triple line list no text wrapping-->
            <ListBox x:Name="SecondListBox" Margin="0,0,-12,0" ItemsSource="{Binding  Source={StaticResource IsSelectedCollectionView}}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Margin="0,0,0,17">
                                <TextBlock Text="{Binding LineOne}" TextWrapping="NoWrap" Margin="12,0,0,0" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                                <TextBlock Text="{Binding LineThree}" TextWrapping="NoWrap" Margin="12,-6,0,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
                            </StackPanel>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
        </controls:PivotItem>
    </controls:Pivot>
</Grid>

<!--Sample code showing usage of ApplicationBar-->
<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1" Click="ApplicationBarIconButton_Click"/>
        <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
            <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

请注意,在 MainPage.xaml.cs 中,为上节中的Filter属性分配了一个过滤器处理程序,该处理程序筛选那些已设置为 true 的项目:CollectionViewSourceResourcesIsSelected

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
        DataContext = App.ViewModel;
        this.Loaded += new RoutedEventHandler(MainPage_Loaded);
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (!App.ViewModel.IsDataLoaded)
        {
            App.ViewModel.LoadData();
            CollectionViewSource isSelectedListView = this.Resources["IsSelectedCollectionView"] as CollectionViewSource;
            if (isSelectedListView != null)
            {
                isSelectedListView .Source = App.ViewModel.Items;
            }
        }
    }

    private void CollectionViewSource_SelectedListFilter(object sender, System.Windows.Data.FilterEventArgs e)
    {
        e.Accepted = ((ItemViewModel)e.Item).IsSelected;
    }

    private void ApplicationBarIconButton_Click(object sender, EventArgs e)
    {
        ItemViewModel item = App.ViewModel.Items[App.ViewModel.Items.Count - 1];
        item.IsSelected = !item.IsSelected;
    }
}

另请注意,在加载数据后,我立即获取CollectionViewSource并将其数据源设置为ObservableCollection<T>列表,以便有可以进行过滤的基础数据。

当应用程序加载时,数据按预期显示,其中那些为 true 的项目ObservableCollection<T>显示IsSelected在第二个 Pivot 中:

替代文字 替代文字

您会注意到我取消了应用程序栏图标的注释,其中第一个在单击时切换了IsSelected最后一项的属性ObservableCollection<T>(请参阅 MainPage.xaml.cs 中的最后一个函数)。

这是我的问题的症结所在- 当我单击适用的栏图标时,我可以看到列表中的最后一项的IsSelected属性设置为 true,但是第二个 Pivot 不显示此更改的项。我可以看到NotifyPropertyChanged()处理程序正在对该项目进行触发,但是该集合并没有发现这一事实,因此 Pivot 2 中的列表框不会更改以反映应该有一个新项目添加到集合中的事实。

我很确定我在这里遗漏了一些非常基本/基本的东西,但是如果做不到这一点,有没有人知道获得该系列的最佳方式,并且它是一起愉快玩耍的基础项目?

我想这个问题也适用于排序和过滤((从某种意义上说,如果 aCollectionViewSource基于排序,那么当排序中使用的项目的属性发生变化时,集合的排序顺序应该反映为出色地))

4

2 回答 2

4

我必须处理这个问题,虽然“Refresh()”解决方案运行良好,但执行时间很长,因为它仅针对一个项目属性更改事件刷新整个列表。不是很好。并且在每 1 秒实时数据进入集合的场景中,我让你想象一下如果你使用这种方法,用户体验的结果:)

我想出了一个解决方案,其基础是:将项目添加到包含在集合视图中的集合时,然后由过滤器谓词评估该项目,并根据此结果在视图中显示或不显示。

因此,我没有调用 refresh(),而是模拟了一个对象的插入,该对象更新了它的属性。通过模拟对象的插入,它将由过滤谓词自动评估,而无需通过刷新来刷新整个列表。

这是执行此操作的代码:

派生的可观察集合:

namespace dotnetexplorer.blog.com
{
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

/// <summary>
/// Derived class used to be able to manage filter application when a collection item property changed
///   whithout having to do a refresh
/// </summary>
internal sealed class CustomObservableCollection : ObservableCollection<object>
{
    /// <summary>
    ///   Initializes a new instance of the <see cref = "CustomObservableCollection " /> class.
    /// </summary>
    public CustomObservableCollection ()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="CustomObservableCollection "/> class.
    /// </summary>
    /// <param name="source">
    /// The source.
    /// </param>
    public CustomObservableCollection (IEnumerable<object> source)
        : base(source)
    {
    }

    /// <summary>
    /// Custom Raise collection changed
    /// </summary>
    /// <param name="e">
    /// The notification action
    /// </param>
    public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        OnCollectionChanged(e);
    }
}
}

当接收到替代源是 CustomObservableCollection 的项目属性更改事件时,可以使用代码:

        private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {

                // To avoid doing a refresh on a property change which would end in a very hawful user experience
                // we simulate a replace to the collection because the filter is automatically applied in this case
                int index = _substituteSource.IndexOf(sender);

                var argsReplace = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                                                                       new List<object> { sender },
                                                                       new List<object> { sender }, index);
                _substituteSource.RaiseCollectionChanged(argsReplace);
            }

        }
    }

希望这会有所帮助!

于 2011-04-12T11:48:17.040 回答
3

当这种情况发生时,您不要只是讨厌它,自从我发布问题后不到 5 分钟,我已经弄清楚了问题所在 - 这非常基本的事情。在CollectionViewSource对象上,有一个View属性,它有一个Refresh()功能。在更改中包含的基础项目的属性之后调用此函数ObservableCollection<T>似乎已经完成。

基本上,我所要做的就是将CollectionViewSource对象更改为成员变量,然后在LoadData()调用时保存它:

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    if (!App.ViewModel.IsDataLoaded)
    {
        App.ViewModel.LoadData();
        m_isSelectedListView = this.Resources["IsSelectedCollectionView"] as CollectionViewSource;
        if (m_isSelectedListView != null)
        {
            m_isSelectedListView.Source = App.ViewModel.Items;
        }
    }
}

然后,在基础更改Refresh()中的任何项目之后调用视图。ObservableCollection<T>所以在 MainPage.xaml.cs 中,就在更改最后一项之后,添加对刷新的调用:

private void ApplicationBarIconButton_Click(object sender, EventArgs e)
{
    ItemViewModel item = App.ViewModel.Items[App.ViewModel.Items.Count - 1];
    item.IsSelected = !item.IsSelected;
    m_isSelectedListView.View.Refresh();
}

...并且第二个 Pivot 的 ListBox 会立即更新。这么短的一行代码,天壤之别!

在我写下那个问题的时间里,我可以做一百件事:-(嗯,迟到总比没有好-我想在这里发布答案,如果只是为了拯救别人撕毁他们的头发和我一样。

于 2010-09-22T16:55:38.310 回答