当单个项目的属性更改时,如何自动更新 CollectionViewSource 上的过滤器和/或排序顺序?
好的,所以这个问题与 Windows Phone 7/Silverlight(更新的 WP7 工具,2010 年 9 月)相关,特别是过滤底层 ObservableCollection
。
在研究 WP7 模板 Pivot 控件应用程序时,我遇到了一个问题,即更改 ObservableCollection
中的基础项目不会导致屏幕上的 ListBox 被更新。基本上,示例应用程序有两个枢轴,第一个直接绑定到底层 ObservableCollection
,第二个绑定到 CollectionViewSource
(即表示底层的 ObservableCollection
添加到 ObservableCollection
的底层项目实现 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 文件中,第一个数据透视表的 ItemSource
直接基于 ObservableCollection< ;T>
列表。在第二个透视图中,屏幕上的 ListBox 将其 ItemSource
属性设置为 CollectionViewSource
,其底层源基于 ObservableCollection
> 在上面的 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 中,上面 Resources
部分中 CollectionViewSource
上的 Filter
属性被分配了一个筛选处理程序,该处理程序会筛选通过那些将 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
T>
列表,以便有可以进行过滤的基础数据。
应用程序加载时,数据按预期显示,ObservableCollection
中 IsSelected
true 的项目显示在第二个数据透视中:
您会注意到我已取消注释应用程序栏图标,其中第一个图标会切换 IsSelected单击时
属性(请参阅 MainPage.xaml.cs 中的最后一个函数)。ObservableCollection
中最后一项的
这是我的问题的关键 - 当我单击适用的栏图标时,我可以看到列表中最后一项的 IsSelected
属性何时设置为 true,但是第二个数据透视表不显示此更改的项目。我可以看到 NotifyPropertyChanged()
处理程序正在该项目上被触发,但是集合没有接收到这一事实,因此 Pivot 2 中的列表框不会更改以反映以下事实:应该是添加到集合中的新项目。
我很确定我在这里错过了一些非常基本的/基本的东西,但如果失败了,有人知道获得该系列的最佳方法以及它的基础项目可以一起愉快地玩耍吗?
我想这个问题也适用于排序和过滤((从某种意义上说,如果CollectionViewSource
基于排序,那么当排序中使用的项目的属性发生变化时,排序集合的顺序也应该反映这一点))
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我必须处理这个问题,尽管“Refresh()”解决方案运行良好,但执行时间相当长,因为它仅针对一项属性更改事件刷新整个列表。不太好。在实时数据每 1 秒进入集合的情况下,我让您想象一下如果使用这种方法,用户体验的结果:)
我想出了一个解决方案,其基础是:当将项目添加到包装在集合中时CollectionView,然后通过过滤谓词评估该项目,并根据此结果在视图中显示或不显示。
因此,我没有调用refresh(),而是模拟插入更新其属性的对象。通过模拟对象的插入,过滤谓词将自动评估该对象,而无需刷新整个列表。
下面是执行此操作的代码:
派生的可观察集合:
并且有在接收项目属性更改事件时使用的代码,其中替代源是 CustomObservableCollection:
希望这会有所帮助!
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 :
And there is the code to use when receiveing item property changed event where substitute source is a CustomObservableCollection :
Hope this will help !
当这种情况发生时,你难道不讨厌它吗?自从我发布问题以来不到 5 分钟,我就已经找出了问题所在 - 而且它是一些非常基本的东西。在
CollectionViewSource
对象上,有一个View
属性,它有一个Refresh()
函数。在ObservableCollection
中包含的基础项的属性更改后调用此函数,似乎已经完成了。基本上,我所要做的就是将
CollectionViewSource
对象更改为成员变量,然后在调用LoadData()
时保存它:然后,调用
Refresh( )
在视图上,在底层ObservableCollection
中的任何项目发生更改之后。因此,在 MainPage.xaml.cs 中,在更改最后一项后,添加对刷新的调用:...,第二个 Pivot 的 ListBox 会立即更新。这么短的一行代码,却带来了天壤之别!
在我写下这个问题的时间里,我可以做一百件事:-(啊好吧,我想迟到总比不到好——想在这里发布答案,哪怕只是为了避免其他人撕扯他们的头发就像我一样。
Don't you just hate it when that happens, not 5 minutes gone since I posted the question, and I've figured out what the problem is - and it was something quite basic. On the
CollectionViewSource
object, there is aView
property, which has aRefresh()
function. Calling this function after a property on an underlying item contained in theObservableCollection<T>
changes, seems to have done it.Basically, all I had to do was change the
CollectionViewSource
object into a member variable, and then save it whenLoadData()
is called:Then, call
Refresh()
on the view, after any of the items in the underlyingObservableCollection<T>
changes. So in MainPage.xaml.cs, just after changing the last item, add the call to refresh:... and the second Pivot's ListBox is updated instantly. Such a short line of code, a whole world of difference!
In the time it took me to write up that question, there are a hundred things I could've done :-( Ah well, better late than never I guess - thought to post the answer here, if only to save someone else tearing out their hair like I did.