52

MVVMパターンを使用してWPFデスクトップアプリケーションに取り組んでいます。

ListViewに入力したテキストに基づいて、からいくつかのアイテムをフィルタリングしようとしていますTextBoxListViewテキストを変更するときにアイテムをフィルタリングしたい。

フィルタテキストが変更されたときにフィルタをトリガーする方法を知りたいです。

ListViewバインドします。CollectionViewSourceこれはObservableCollection、ViewModelのにバインドします。フィルタテキストのTextBoxforは、必要に応じて、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"リンク。これは、ViewModelのフィルターメソッドを呼び出すだけです。

FilterTextの値が変更されると、フィルタリングが実行されます。FilterTextプロパティのセッターは、ViewModel内で反復するFilterListメソッドを呼び出し、各アイテムのViewModelにFilteredOutプロパティをObservableCollection設定します。boolean

フィルタテキストが変更されるとFilteredOutプロパティが更新されることは知っていますが、リストは更新されません。フィルタイベントは、CollectionViewSourceUserControlから切り替えて再度切り替えて、UserControlをリロードした場合にのみ発生します。

フィルタ情報を更新してから電話をかけてみましOnPropertyChanged("AllProjects")たが、問題は解決しませんでした。(「AllProjects」は、ObservableCollectionバインド先のViewModelのプロパティCollectionViewSourceです。)

CollectionViewSourceFilterTextの値が変更されたときに、を再フィルタリングするにはどうすればよいTextBoxですか?

どうもありがとう

4

6 に答える 6

78

ビューにを作成しないCollectionViewSourceでください。代わりに、ICollectionViewビューモデルにtypeのプロパティを作成し、それにバインドListView.ItemsSourceします。

これを行うと、ユーザーが変更するたびに呼び出すロジックをプロパティのセッターに配置できますFilterTextRefresh()ICollectionView

これにより、並べ替えの問題も単純化されることがわかります。並べ替えロジックをビューモデルに組み込み、ビューで使用できるコマンドを公開できます。

編集

これは、MVVMを使用したコレクションビューの動的な並べ替えとフィルタリングの非常に簡単なデモです。このデモは実装されていませんFilterTextが、すべてがどのように機能するかを理解すれば、FilterText現在使用しているハードコードされたフィルターの代わりに、プロパティとそのプロパティを使用する述語を実装するのに問題はありません。

(ここのビューモデルクラスはプロパティ変更通知を実装していないことにも注意してください。これはコードを単純にするためです。このデモでは実際にプロパティ値を変更するものはないため、プロパティ変更通知は必要ありません。)

あなたのアイテムのための最初のクラス:

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

ここで、アプリケーションのビューモデル。ここで行われていることは3つあります。1つは、独自の機能を作成して設定すること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ルーティングする単純な実装です。アプリケーションビューモデルに多数のプロパティを作成したくなかったため、この方法で実装しました。また、すべての並べ替え/フィルタリングを1つのメソッドに保持して、実行方法を簡単に確認できるようにしたいと考えました。CommandParameterExecuteRelayCommand

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>
于 2011-06-24T15:42:08.567 に答える
26

最近では、更新を明示的にトリガーする必要がないことがよくあります。コレクション内のフィールドに基づいて、trueの場合に自動的に更新するCollectionViewSource実装。ICollectionViewLiveShapingIsLiveFilteringRequestedLiveFilteringProperties

XAMLの例:

  <CollectionViewSource
         Source="{Binding Items}"
         Filter="FilterPredicateFunction"
         IsLiveFilteringRequested="True">
    <CollectionViewSource.LiveFilteringProperties>
      <system:String>FilteredProperty1</system:String>
      <system:String>FilteredProperty2</system:String>
    </CollectionViewSource.LiveFilteringProperties>
  </CollectionViewSource>
于 2015-06-09T17:03:01.643 に答える
11
CollectionViewSource.View.Refresh();

CollectionViewSource.Filterは、この方法で再評価されます。

于 2015-06-10T12:22:46.280 に答える
6

おそらくあなたは質問でビューを単純化しましたが、書かれているように、実際には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}" />

いくつかの簡単なメモ:

  • これにより、コードビハインドのイベントが排除されます

  • また、人工的なGUIのみのプロパティである「FilterOut」プロパティを削除するため、MVVMが実際に機能しなくなります。あなたがこれをシリアル化することを計画していない限り、私はそれを私のViewModelに、そして確かに私のモデルには望まないでしょう。

  • 私の例では、「フィルターアウト」ではなく「フィルターイン」を使用しています。私が適用しているフィルターが私が見たいものであるということは、私には(ほとんどの場合)より論理的に思えます。本当にフィルターをかけたい場合は、Contains句を無効にします(つまり、item =>!Item.Text.Contains(...))。

  • ViewModelでセットを実行するためのより集中化された方法があるかもしれません。覚えておくべき重要なことは、FilterTextを変更するときは、AllFilteredItemsコレクションにも通知する必要があるということです。ここではインラインで実行しましたが、e.PropertyNameがFilterTextの場合は、PropertyChangedイベントを処理してPropertyChangedを呼び出すこともできます。

説明が必要な場合はお知らせください。

于 2011-06-24T13:34:03.687 に答える
2

あなたが何を求めているのかよく理解できたら:

FilterTextプロパティの設定部分で、を呼び出すだけRefresh()ですCollectionView

于 2011-06-24T12:26:27.417 に答える
2

私はこの問題に対するはるかに洗練された解決策を発見しました。(受け入れられた答えが示唆するように)ViewModelでを作成し、バインディングをに設定する代わりにICollectionView

ItemsSource={Binding Path=YourCollectionViewSourceProperty}

より良い方法はCollectionViewSource、ViewModelにプロパティを作成することです。ItemsSource次に、次のようにバインドします

ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}    

.Viewが追加されていることに注意してください。この方法ItemsSourceでは、に変更が加えられるたびにバインディングが通知され、手動で呼び出す必要はCollectionViewSourceありません。Refresh()ICollectionView

注:これが当てはまる理由を特定できません。CollectionViewSourceプロパティに直接バインドすると、バインドは失敗します。ただし、XAMLファイルの要素でを定義CollectionViewSourceResources、リソースキーに直接バインドする場合、バインドは正常に機能します。私が推測できる唯一のことは、XAMLで完全に実行すると、実際にCollectionViewSource.View値にバインドする必要があることを認識し、舞台裏でそれをバインドすることです(非常に役立ちます!:/)。

于 2014-10-10T16:56:12.480 に答える