MVVMパターンを使用して、WPFデスクトップアプリケーションで作業しています。
ListView
に入力されたテキストに基づいて、TextBox
からいくつかのアイテムを除外しようとしています。テキストを変更するときにListView
アイテムをフィルター処理したい。
フィルターテキストが変更されたときにフィルターをトリガーする方法を知りたい
ListView
は、CollectionViewSource
にバインドします。これは、ViewModelのObservableCollection
にバインドします。フィルターテキストのTextBox
は、必要に応じて_UpdateSourceTrigger=PropertyChanged
_を使用して、ViewModelの文字列にバインドします。
_<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のObservableCollection
を反復処理し、各アイテムViewModelにboolean
FilteredOutプロパティを設定するFilterListメソッドを呼び出します。
フィルターテキストが変更されるとFilteredOutプロパティが更新されますが、リストは更新されません。 CollectionViewSource
フィルターイベントは、ユーザーコントロールを切り替えて再度切り替えることにより、ユーザーコントロールを再読み込みしたときにのみ発生します。
フィルター情報を更新した後にOnPropertyChanged("AllProjects")
を呼び出してみましたが、問題は解決しませんでした。 (「AllProjects」は、ObservableCollection
がバインドされるViewModelのCollectionViewSource
プロパティです。)
FilterText CollectionViewSource
の値が変更されたときに、TextBox
を取得して自分自身を再フィルタリングするにはどうすればよいですか?
どうもありがとう
ビューにCollectionViewSource
を作成しないでください。代わりに、ビューモデルにICollectionView
型のプロパティを作成し、それに_ListView.ItemsSource
_をバインドします。
これを実行したら、ユーザーが変更するたびにFilterText
でRefresh()
を呼び出すICollectionView
プロパティのセッターにロジックを配置できます。
これにより、並べ替えの問題も簡素化されることがわかります。並べ替えロジックをビューモデルに組み込み、ビューで使用できるコマンドを公開できます。
[〜#〜] edit [〜#〜]
以下は、MVVMを使用したコレクションビューの動的な並べ替えとフィルタリングの非常に簡単なデモです。このデモはFilterText
を実装していませんが、すべてがどのように機能するかを理解すれば、FilterText
プロパティと、ハードプロパティの代わりにそのプロパティを使用する述語を実装することに問題はないはずです。現在使用しているコード化されたフィルター。
(ここでのビューモデルクラスはプロパティ変更通知を実装しないことにも注意してください。これはコードを単純にするためです。このデモではプロパティ値を実際に変更しないため、プロパティ変更通知は必要ありません。)
最初にアイテムのクラス:
_public class ItemViewModel
{
public string Name { get; set; }
public int Age { get; set; }
}
_
次に、アプリケーションのビューモデル。ここでは3つのことが行われています。最初に、独自の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
プロパティの束を作成したくなかったので、この方法で実装しました。そして、すべてのソート/フィルタリングを1つのメソッドに保持して、完了しました。
_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>
_
現在、多くの場合、明示的に更新をトリガーする必要はありません。 CollectionViewSource
は、ICollectionViewLiveShaping
コレクションのフィールドに基づいて、IsLiveFilteringRequested
がtrueの場合に自動的に更新される 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>
おそらくあなたはあなたの質問でビューを単純化しましたが、書かれているように、CollectionViewSourceは本当に必要ありません-ViewModelでフィルタリングされたリストに直接バインドできます(mItemsToFilterはフィルタリングされているコレクション、おそらくあなたの例):
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には必要ありませんし、Modelには必要ありません。
この例では、「フィルターアウト」ではなく「フィルターイン」を使用します。 (ほとんどの場合)私が適用しているフィルターは、私がdoを見たいものであるということは、私にとってより論理的なようです。本当にフィルターで除外したい場合は、Contains句を無効にします(つまり、item =>!Item.Text.Contains(...))。
ViewModelでセットをより集中的に行う方法があるかもしれません。覚えておくべき重要なことは、FilterTextを変更するときは、AllFilteredItemsコレクションにも通知する必要があるということです。ここではインラインで行いましたが、e.PropertyNameがFilterTextの場合、PropertyChangedイベントを処理してPropertyChangedを呼び出すこともできます。
明確化が必要な場合はお知らせください。
CollectionViewSource.View.Refresh();
CollectionViewSource.Filterはこの方法で再評価されます!
この問題に対するより洗練された解決策を発見しました。 代わりにViewModelにICollectionView
を作成し(受け入れられた答えが示唆するように)、バインディングを
_ItemsSource={Binding Path=YourCollectionViewSourceProperty}
_
より良い方法は、ViewModelでCollectionViewSource
プロパティを作成することです。次に、次のようにItemsSource
をバインドします
_ItemsSource={Binding Path=YourCollectionViewSourceProperty.View}
_
。Viewが追加されていることに注意してください。このようにすると、ItemsSource
が変更されるたびにCollectionViewSource
バインディングに通知され、手動でRefresh()
ICollectionView
で
注:なぜそうなのか判断できません。 CollectionViewSource
プロパティに直接バインドすると、バインドは失敗します。ただし、XAMLファイルのCollectionViewSource
要素でResources
を定義し、リソースキーに直接バインドする場合、バインディングは正常に機能します。私が推測できる唯一のことは、XAMLで完全に実行すると、本当にCollectionViewSource.View値にバインドすることを認識し、それをバックグラウンドでバインドすることを知っているということです(とても便利です!:/)。
あなたが尋ねていることをよく理解している場合:
FilterText
プロパティの設定部分で、CollectionView
に対してRefresh()
を呼び出すだけです。