Pie*_*ler 44 wpf xaml filter mvvm collectionviewsource
我正在使用MVVM模式处理WPF桌面应用程序.
我试图ListView
根据输入的文本过滤一些项目TextBox
.我希望在ListView
更改文本时过滤项目.
我想知道在过滤器文本更改时如何触发过滤器.
在ListView
绑定到CollectionViewSource
,它绑定到ObservableCollection
我的视图模型.该TextBox
用于过滤文字结合在视图模型的字符串,UpdateSourceTrigger=PropertyChanged
,因为它应该是.
<CollectionViewSource x:Key="ProjectsCollection"
Source="{Binding Path=AllProjects}"
Filter="CollectionViewSource_Filter" />
<TextBox Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<ListView DataContext="{StaticResource ProjectsCollection}"
ItemsSource="{Binding}" />
Run Code Online (Sandbox Code Playgroud)
Filter="CollectionViewSource_Filter"
指向后面代码中的事件处理程序的链接,它只是在ViewModel上调用过滤器方法.
当FilterText的值发生更改时进行过滤 - FilterText属性的setter调用FilterList方法,该方法迭代ObservableCollection
我的ViewModel并boolean
在每个项目ViewModel上设置FilteredOut属性.
我知道过滤器文本更改时FilteredOut属性会更新,但List不会刷新.该CollectionViewSource
当我切换远离它,并再次重新加载用户控件过滤事件仅触发.
我OnPropertyChanged("AllProjects")
在更新过滤器信息后尝试过调用,但它没有解决我的问题.("AllProjects"是ObservableCollection
我绑定到的ViewModel上的属性CollectionViewSource
.)
CollectionViewSource
当FilterText的值TextBox
发生变化时,如何让自己重新过滤?
非常感谢
Rob*_*ney 70
不要CollectionViewSource
在视图中创建.而是ICollectionView
在视图模型中创建一个类型的属性并绑定ListView.ItemsSource
到它.
一旦你做到了这一点,你可以把逻辑在FilterText
属性的setter方法调用Refresh()
上ICollectionView
,每当用户改变它.
您会发现这也简化了排序问题:您可以将排序逻辑构建到视图模型中,然后公开视图可以使用的命令.
编辑
这是使用MVVM动态排序和过滤集合视图的非常简单的演示.这个演示没有实现FilterText
,但是一旦你理解它是如何工作的,你应该没有任何困难实现FilterText
使用该属性的属性和谓词而不是它现在使用的硬编码过滤器.
(另请注意,此处的视图模型类不实现属性更改通知.这只是为了保持代码简单:因为此演示中的任何内容实际上都不会更改属性值,所以它不需要属性更改通知.)
首先是您的项目的课程:
public class ItemViewModel
{
public string Name { get; set; }
public int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
现在,该应用程序的视图模型.这里有三件事:首先,它创造和填充自己的东西ICollectionView
; 第二,它公开了一个ApplicationCommand
(见下文)视图将用于执行排序和过滤命令,最后,它实现了一个Execute
排序或过滤视图的方法:
public class ApplicationViewModel
{
public ApplicationViewModel()
{
Items.Add(new ItemViewModel { Name = "John", Age = 18} );
Items.Add(new ItemViewModel { Name = "Mary", Age = 30} );
Items.Add(new ItemViewModel { Name = "Richard", Age = 28 } );
Items.Add(new ItemViewModel { Name = "Elizabeth", Age = 45 });
Items.Add(new ItemViewModel { Name = "Patrick", Age = 6 });
Items.Add(new ItemViewModel { Name = "Philip", Age = 11 });
ItemsView = CollectionViewSource.GetDefaultView(Items);
}
public ApplicationCommand ApplicationCommand
{
get { return new ApplicationCommand(this); }
}
private ObservableCollection<ItemViewModel> Items =
new ObservableCollection<ItemViewModel>();
public ICollectionView ItemsView { get; set; }
public void ExecuteCommand(string command)
{
ListCollectionView list = (ListCollectionView) ItemsView;
switch (command)
{
case "SortByName":
list.CustomSort = new ItemSorter("Name") ;
return;
case "SortByAge":
list.CustomSort = new ItemSorter("Age");
return;
case "ApplyFilter":
list.Filter = new Predicate<object>(x =>
((ItemViewModel)x).Age > 21);
return;
case "RemoveFilter":
list.Filter = null;
return;
default:
return;
}
}
}
Run Code Online (Sandbox Code Playgroud)
排序很糟糕; 你需要实现一个IComparer
:
public class ItemSorter : IComparer
{
private string PropertyName { get; set; }
public ItemSorter(string propertyName)
{
PropertyName = propertyName;
}
public int Compare(object x, object y)
{
ItemViewModel ix = (ItemViewModel) x;
ItemViewModel iy = (ItemViewModel) y;
switch(PropertyName)
{
case "Name":
return string.Compare(ix.Name, iy.Name);
case "Age":
if (ix.Age > iy.Age) return 1;
if (iy.Age > ix.Age) return -1;
return 0;
default:
throw new InvalidOperationException("Cannot sort by " +
PropertyName);
}
}
}
Run Code Online (Sandbox Code Playgroud)
要Execute
在视图模型中触发该方法,这将使用一个ApplicationCommand
类,该类是一个简单的实现,ICommand
它将CommandParameter
视图中的on按钮路由到视图模型的Execute
方法.我这样实现它是因为我不想RelayCommand
在应用程序视图模型中创建一堆属性,我想在一个方法中保留所有排序/过滤,以便很容易看到它是如何完成的.
public class ApplicationCommand : ICommand
{
private ApplicationViewModel _ApplicationViewModel;
public ApplicationCommand(ApplicationViewModel avm)
{
_ApplicationViewModel = avm;
}
public void Execute(object parameter)
{
_ApplicationViewModel.ExecuteCommand(parameter.ToString());
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Run Code Online (Sandbox Code Playgroud)
最后,这MainWindow
是应用程序:
<Window x:Class="CollectionViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CollectionViewDemo="clr-namespace:CollectionViewDemo"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<CollectionViewDemo:ApplicationViewModel />
</Window.DataContext>
<DockPanel>
<ListView ItemsSource="{Binding ItemsView}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}"
Header="Name" />
<GridViewColumn DisplayMemberBinding="{Binding Age}"
Header="Age"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel DockPanel.Dock="Right">
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByName">Sort by name</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="SortByAge">Sort by age</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="ApplyFilter">Apply filter</Button>
<Button Command="{Binding ApplicationCommand}"
CommandParameter="RemoveFilter">Remove filter</Button>
</StackPanel>
</DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)
Dre*_*kes 24
如今,您通常不需要明确地触发刷新.CollectionViewSource
实现ICollectionViewLiveShaping
自动更新,如果IsLiveFilteringRequested
是真实的,根据其在田间LiveFilteringProperties
采集.
XAML中的一个示例:
<CollectionViewSource
Source="{Binding Items}"
Filter="FilterPredicateFunction"
IsLiveFilteringRequested="True">
<CollectionViewSource.LiveFilteringProperties>
<system:String>FilteredProperty1</system:String>
<system:String>FilteredProperty2</system:String>
</CollectionViewSource.LiveFilteringProperties>
</CollectionViewSource>
Run Code Online (Sandbox Code Playgroud)
也许你已经在你的问题中简化了你的View,但是如你所写的,你并不真正需要一个CollectionViewSource - 你可以直接在ViewModel中绑定到一个过滤的列表(mItemsToFilter是被过滤的集合,可能是"AllProjects"in你的例子):
public ReadOnlyObservableCollection<ItemsToFilter> AllFilteredItems
{
get
{
if (String.IsNullOrEmpty(mFilterText))
return new ReadOnlyObservableCollection<ItemsToFilter>(mItemsToFilter);
var filtered = mItemsToFilter.Where(item => item.Text.Contains(mFilterText));
return new ReadOnlyObservableCollection<ItemsToFilter>(
new ObservableCollection<ItemsToFilter>(filtered));
}
}
public string FilterText
{
get { return mFilterText; }
set
{
mFilterText = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FilterText"));
PropertyChanged(this, new PropertyChangedEventArgs("AllFilteredItems"));
}
}
}
Run Code Online (Sandbox Code Playgroud)
您的视图将只是:
<TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" />
<ListView ItemsSource="{Binding AllFilteredItems}" />
Run Code Online (Sandbox Code Playgroud)
一些快速说明:
这消除了后面代码中的事件
它还消除了"FilterOut"属性,这是一个人为的,仅限GUI的属性,因此真正打破了MVVM.除非你打算序列化这个,否则我不希望它在我的ViewModel中,当然也不在我的模型中.
在我的例子中,我使用"Filter In"而不是"Filter Out".这似乎更符合逻辑,我(在大多数情况下),我将滤波器件事情我不希望看到的.如果你真的想要过滤掉东西,只需取消Contains子句(即item =>!Item.Text.Contains(...)).
您可以在ViewModel中使用更集中的方式来执行集合.要记住的重要一点是,当您更改FilterText时,还需要通知AllFilteredItems集合.我在这里内联它,但你也可以处理PropertyChanged事件并在e.PropertyName是FilterText时调用PropertyChanged.
如果您需要任何说明,请告诉我.
CollectionViewSource.View.Refresh();
Run Code Online (Sandbox Code Playgroud)
以这种方式重新评估CollectionViewSource.Filter!
归档时间: |
|
查看次数: |
43074 次 |
最近记录: |