2011-06-24 44 views
35

我正在使用MVVM模式的WPF桌面應用程序。CollectionViewSource上的觸發器過濾器

我想根據TextBox中鍵入的文本過濾出ListView中的一些項目。我希望ListView項目在我更改文本時進行過濾。

我想知道如何在過濾器文本更改時觸發過濾器。

ListView綁定到CollectionViewSource,它綁定到我的ViewModel上的ObservableCollection。過濾器文本的TextBox綁定到ViewModel上的字符串,它應該是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}" /> 

Filter="CollectionViewSource_Filter"鏈接到代碼中的事件處理程序的後面,這只是調用視圖模型的過濾方法。

時FilterText的值更改過濾完成 - 爲FilterText屬性的setter調用FilterList方法,用來在我的視圖模型的ObservableCollection迭代,並且將在每個項目視圖模型一個boolean FilteredOut屬性。

我知道FilteredOut屬性在過濾器文本更改時更新,但List不刷新。只有當我重新加載UserControl時,纔會觸發CollectionViewSource篩選器事件,方法是切換它並返回。

我試過在更新過濾器信息後調用OnPropertyChanged("AllProjects"),但它沒有解決我的問題。 (「AllProjects」是ObservableCollection財產在我的ViewModel到了CollectionViewSource綁定。)

我怎樣才能得到CollectionViewSource來重新過濾本身時的FilterText TextBox的值更改?

非常感謝

+0

此外,有沒有辦法在我的ViewModel上直接調用過濾器方法('bool Include(object o)'),所以我不需要在代碼隱藏中有一個事件處理器? –

回答

62

不要建立在您的視圖CollectionViewSource。相反,在您的視圖模型中創建類型爲ICollectionView的屬性並將ListView.ItemsSource綁定到它。

完成此操作後,您可以將邏輯置於FilterText屬性的設置器中,只要用戶對其進行更改,就會在ICollectionView上調用Refresh()

你會發現,這也簡化排序的問題:你可以建立排序邏輯到視圖模型,然後暴露命令該視圖可以使用。

編輯

這裏的動態排序和使用MVVM集合視圖的過濾的一個非常簡單的演示。這個演示沒有實現FilterText,但一旦你理解它是如何工作,你不應該有實施FilterText財產的任何困難和使用該財產而不是它現在使用硬編碼的過濾器謂詞。 (請注意,這裏的視圖模型類沒有實現屬性更改通知,這只是爲了保持代碼簡單:因爲本演示中沒有任何內容實際上改變屬性值,所以不需要屬性更改通知。 )

首先爲您的項目類:現在

public class ItemViewModel 
{ 
    public string Name { get; set; } 
    public int Age { get; set; } 
} 

,爲應用程序視圖模型。這裏有三件事:首先,它創建和填充它自己的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; 
     } 
    } 
} 

排序種吮吸;您需要實現一個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); 
     } 
    } 
} 

來觸發視圖模型的Execute方法,它使用一個ApplicationCommand類,它是一個簡單的實現ICommand該路由CommandParameter上的按鈕在視圖中的視圖模型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; 
} 

最後,這裏的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> 
+1

到目前爲止給出的答案都很好。我正在標記這一個,因爲它以最大的MVVMish方式解決了問題,同時也幫助我解決了我一直存在的自定義排序問題。謝謝。 –

+0

羅伯特,你以前是否成功實施過?我似乎無法得到它的工作。 –

+0

我的實現都嵌入在太過牽扯到不需要任何操作的應用程序中,因此我爲您提供了一個小型演示應用程序。看我的編輯。 –

1

如果我明白你問清楚什麼:

在你FilterText屬性的設置部位只是叫Refresh()CollectionView

+2

嗨。這是行得通的,但是這在MVVM中是不允許的 - FilterText屬性在ViewModel中,CollectionView在View中,ViewModel不應該有任何關於View的知識。 –

+1

最後回答。我建議,因爲我把我的集合視圖也放在ViewModel中作爲屬性。 – Dummy01

+0

由於某些原因,這不起作用。 –

5

也許你已經在你的問題中簡化了視圖,但是按照書面的說法,你並不需要一個CollectionViewSource--你可以直接綁定到ViewModel中的過濾列表(mItemsToFilter是被過濾的集合,可能在你的榜樣 「AllProjects」):

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

你的看法會那麼僅僅是:

<TextBox Text="{Binding Path=FilterText,UpdateSourceTrigger=PropertyChanged}" /> 
<ListView ItemsSource="{Binding AllFilteredItems}" /> 

一些快速筆記:

  • 這消除落後

  • 代碼的情況下它也消除了「FilterOut」屬性,這是一種人爲的,只有GUI的屬性,從而真正打破MVVM。除非你打算序列化這個,否則我不希望它在我的ViewModel中,當然也不在我的Model中。

  • 在我的例子,我使用了一個「過濾器在」,而不是「篩選出」。這似乎更符合邏輯,我(在大多數情況下),我將濾波器件事情我希望看到的。如果你真的想過濾掉,只需要否定Contains子句(即item =>!Item.Text.Contains(...))。

  • 你可能在你的視圖模型做你集的更集中的方式。要記住的重要一點是,當您更改FilterText時,還需要通知您的AllFilteredItems集合。我在這裏做了內聯,但是當e.PropertyName是FilterText時,你也可以處理PropertyChanged事件並調用PropertyChanged。

請讓我知道你是否需要任何澄清。

1

我剛剛發現了一個更優雅的解決這個問題。在您的視圖模型創建ICollectionView(作爲接受的答案建議)和設置的相反你的更好的方法結合

ItemsSource={Binding Path=YourCollectionViewSourceProperty} 

是建立在你的ViewModel一個CollectionViewSource屬性。然後綁定你ItemsSource如下

ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}  

公告加入.View這樣的ItemsSource結合仍然通知每當有一個變化的CollectionViewSource你永遠不必手動調用Refresh()ICollectionView

注意:我無法確定這是爲什麼。如果直接綁定到CollectionViewSource屬性,則綁定失敗。但是,如果在XAML文件的Resources元素中定義CollectionViewSource,並且您直接綁定到資源鍵,則綁定工作正常。我唯一能猜到的是,當你在XAML中完全執行它時,它知道你真的想綁定到CollectionViewSource.View的值,並將它綁定在幕後(對你有幫助!:/)。

19

如今,您通常不需要明確觸發刷新。 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> 
+6

想要說這是在wpf中加上.net 4.5 – bigworld12

+0

這似乎有點短視。沒有自定義綁定?如果你有一個視圖中的項目集合,你可能會改變父視圖模型的一些值(例如,過濾文本或布爾過濾器標誌)。不僅僅是正在過濾的集合中的項目的屬性。 –

+0

在.NET 4.6.1的WPF中,未實現「ICollectionViewLiveShaping」。 –

3
CollectionViewSource.View.Refresh(); 

CollectionViewSource.Filter將重新評估以這種方式!

相關問題