本文介绍了当单个项目的属性更改时,如何自动更新CollectionViewSource上的过滤器和/或排序顺序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Ok,所以这个问题与Windows Phone 7 / Silverlight(更新的WP7工具,2010年9月)相关,特别是过滤一个底层的 ObservableCollection< T> 。 >

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



正在添加到 ObservableCollection< T> 中的项目实现 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>项目{get;私人集}

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

在MainPage.xaml文件,第一个Pivot的 ItemSource 直接基于 ObservableCollection< T> 列表。在第二个Pivot中,屏幕上的ListBox将其 ItemSource 属性设置为 CollectionViewSource 在上面 LoadData()中的 ObservableCollection< T>

 < phone:PhoneApplicationPage.Resources> 
< CollectionViewSource x:Key =IsSelectedCollectionViewFilter =CollectionViewSource_SelectedListFilter>
< / CollectionViewSource>
< / phone:PhoneApplicationPage.Resources>

<! - LayoutRoot是放置所有页面内容的根网格 - >
< Grid x:Name =LayoutRootBackground =Transparent>
<! - 数据透视控制 - >
< controls:Pivot Title =MY APPLICATION>
<! - 数据透视表项 - >
< controls:PivotItem Header =first>
<! - 带有文本换行的双行列表 - >
< ListBox x:Name =FirstListBoxMargin =0,0,-12,0ItemsSource ={Binding Items}>
< ListBox.ItemTemplate>
< DataTemplate>
< StackPanel Margin =0,0,0,17Width =432>
< TextBlock Text ={Binding LineOne}TextWrapping =WrapStyle ={StaticResource PhoneTextExtraLargeStyle}/>
< TextBlock Text ={Binding LineTwo}TextWrapping =WrapMargin =12,-6,12,0Style ={StaticResource PhoneTextSubtleStyle}/>
< / StackPanel>
< / DataTemplate>
< /ListBox.ItemTemplate>
< / ListBox>
< / controls:PivotItem>

<! - Pivot item two - >
< controls:PivotItem Header =second>
<! - 三行行列表无文本换行 - >
< ListBox x:Name =SecondListBoxMargin =0,0,-12,0ItemsSource ={Binding Source = {StaticResource IsSelectedCollectionView}}>
< ListBox.ItemTemplate>
< DataTemplate>
< StackPanel Margin =0,0,0,17>
< TextBlock Text ={Binding LineOne}TextWrapping =NoWrapMargin =12,0,0,0Style ={StaticResource PhoneTextExtraLargeStyle}/>
< TextBlock Text ={Binding LineThree}TextWrapping =NoWrapMargin =12,-6,0,0Style ={StaticResource PhoneTextSubtleStyle}/>
< / StackPanel>
< / DataTemplate>
< /ListBox.ItemTemplate>
< / ListBox>
< / controls:PivotItem>
< / controls:Pivot>
< / Grid>

<! - 显示ApplicationBar使用情况的示例代码 - >
< phone:PhoneApplicationPage.ApplicationBar>
< shell:ApplicationBar IsVisible =TrueIsMenuEnabled =True>
< shell:ApplicationBarIconButton IconUri =/ Images / appbar_button1.pngText =Button 1Click =ApplicationBarIconButton_Click/>
< shell:ApplicationBarIconButton IconUri =/ Images / appbar_button2.pngText =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,资源 CollectionViewSource 上的 Filter c> section上分配了一个过滤器处理程序,它筛选了将 IsSelected 设置为true的那些项:

  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 列表,以便有基础数据



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






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



这是我的问题的症结 - 当我点击适用的栏图标时,我可以看到列表中的最后一个项目 IsSelected 属性设置为true,而第二个数据透视不显示此更改的项目。我可以看到 NotifyPropertyChanged()处理程序被触发的项目,但是集合没有拾起这个事实,因此数据透视2中的列表框不会更改以反映应该有一个新项目添加到集合的事实。



我很确定我在这里缺少一些相当基本/基本,但失败



我想这个问题也适用于排序以及过滤((在某种意义上,如果 CollectionViewSource 基于排序,那么当排序中使用的项目的属性发生更改时,集合的排序顺序应反映为)

解决方案

我必须处理这个问题,虽然'Refresh()'解决方案很好,执行,因为它刷新整个列表只是为一个项目属性更改事件。不太好。在实时数据每1秒钟进入集合的情况下,我让你想象一下用户体验中的结果,如果你使用这种方法:)



我想出了一个解决方案,其基础是:当将一个项目添加到包装在collectionview中的集合时,则该项目由过滤谓词进行求值,并基于此结果,在视图中显示或不显示。



所以我不是调用refresh(),而是模拟一个对象的插入,它的属性更新。通过模拟对象的插入,它将被过滤器谓词自动计算,而无需刷新整个列表。



这里是代码命令:



派生的observable集合:

 命名空间dotnetexplorer.blog.com 
{
使用System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

///< summary>
///派生类用于在集合项属性更改时管理过滤器应用
/// whithout需要刷新
///< / summary>
internal sealed class CustomObservableCollection:ObservableCollection< object>
{
///< summary>
///初始化< see cref =CustomObservableCollection/>的新实例。类。
///< / summary>
public CustomObservableCollection()
{
}

///< summary>
///初始化< see cref =CustomObservableCollection/>的新实例。类。
///< / summary>
///< param name =source>
///源。
///< / param>
public CustomObservableCollection(IEnumerable< object> source)
:base(source)
{
}

///< summary>
///自定义Raise集合已更改
///< / summary>
///< param name =e>
///通知操作
///< / param>
public void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
OnCollectionChanged(e);
}
}
}
  private void ItemPropertyChanged(object sender,PropertyChangedEventArgs e)
{

//为了避免对属性更改进行刷新,这将在非常有用的用户体验中结束
//我们模拟对集合的替换,因为过滤器会自动应用在这种情况下
int index = _substituteSource.IndexOf(sender);

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

}
}

将帮助!


Ok, so this question is related to Windows Phone 7/Silverlight (updated WP7 Tools, Sept 2010), specifically filtering an underlying ObservableCollection<T>.

In mucking about with the WP7 template Pivot control application, I've run into an issue whereby changing an underlying item in an ObservableCollection<T>, does not result in the on-screen ListBox being updated. Basically, the sample app has two pivots, the first directly bound to the underlying ObservableCollection<T>, and the second bound to a CollectionViewSource (i.e., representing a filtered view on the underlying ObservableCollection<T>).

The underlying items that are being added to the ObservableCollection<T> implement INotifyPropertyChanged, like so:

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

Then, in the main class, a data collection is concocted (list reduced for brevity, also note that unlike other items, three of the LoadData() entries have 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));
   }
  }
 }

In the MainPage.xaml file, the first Pivot has its ItemSource based directly on the ObservableCollection<T> list. Within the second Pivot, the on-screen ListBox has its ItemSource Property set to a CollectionViewSource, whose underlying source is based on the ObservableCollection<T> populated in LoadData() above.

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

Note that in the MainPage.xaml.cs, the Filter attribute on the CollectionViewSource in the Resources section above is assigned a filter handler, which sifts through those items that have IsSelected set to true:

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

Also note that immediately after loading up the data, I obtain the CollectionViewSource and set its data source as the ObservableCollection<T> list, in order that there is base data upon which the filtering can take place.

When application loads, the data is displayed as expected, with those items in the ObservableCollection<T> which have IsSelected true, being displayed in the second Pivot:

You'll notice that I've uncommented the Application Bar Icons, the first of which toggles the IsSelected property of the last item in the ObservableCollection<T> when clicked (see the last function in MainPage.xaml.cs).

Here is the crux of my question - when I click the applicable bar icon, I can see when the last item in the list has its IsSelected property set to true, howoever the second Pivot does not display this changed item. I can see that the NotifyPropertyChanged() handler is being fired on the item, however the collection is not picking up this fact, and hence the list box in Pivot 2 does not change to reflect the fact that there should be a new item added to the collection.

I'm pretty certain that I'm missing something quite fundamental/basic here, but failing that, does anyone know the best way to get the collection and it's underlying items to play happily together?

I suppose this problem also applies to sorting as well as filtering ((in the sense that if a CollectionViewSource is based on sorting, then when a property of an item that is used in the sort changes, the sort order of the collection should reflect this as well))

解决方案

I had to handle this problem and although the 'Refresh()' solution works well, it is quite long to execute because its refreshes the whole list just for one item property changed event. Not very good. And in a scenario of real time data entering the collection every 1 seconds, I let you imagine the result in user experience if you use this approach :)

I came up with a solution which base is : when adding an item to collection wrapped in a collectionview, then the item is evaluated by the filter predicate and, based on this result, displayed or not in the view.

So instead of calling refresh(), I came up simulating an insert of the object that got its property updated. By simulating the insert of the object, it is going to be automatically evaluated by the filter predicate without need to refresh the whole list with a refresh.

Here is the code in order to do that :

The derived observable collection :

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

And there is the code to use when receiveing item property changed event where substitute source is a 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);
            }

        }
    }

Hope this will help !

这篇关于当单个项目的属性更改时,如何自动更新CollectionViewSource上的过滤器和/或排序顺序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-19 13:23