CollectionViewSourceのFilter条件を変更するAction
WPFのListBoxなどは、直接ObservableCollectionなどへバインドするのではなく、XAML上でCollectionViewSourceをResourceとして定義して、それを経由してバインドすることができます。
こうしておくと、CollectionViewSourceのソート条件やフィルタ条件を変更することで、表示する対象やその並び順を変更することができます。
このフィルタ条件の変更を、LivetのInteractionMessageActionとして作成した例です。
まず、Action本体。
フィルタ条件変更後の表示件数をMessageのResponseとして返します。
using System.Linq; using System.Windows; using System.Windows.Data; using Livet.Behaviors.Messaging; using Livet.EventListeners.WeakEvents; using Livet.Messaging; namespace Bells.Behaviors { /// <summary> /// CollectionViewSourceのFilterの内容を変更し、同時にその表示内容に更新するAction /// </summary> public class SetCollectionViewSourceFilterAction : InteractionMessageAction<Window> { private LivetWeakEventListener<FilterEventHandler, FilterEventArgs> _listener; protected override void InvokeAction(InteractionMessage m) { var msg = m as FilterMessage; if (msg == null) return; var cvs = (CollectionViewSource)(AssociatedObject.FindResource(msg.CollectionViewSourceKey)); _listener = new LivetWeakEventListener<FilterEventHandler, FilterEventArgs> ( h => new FilterEventHandler(h), h => cvs.Filter += h, h => cvs.Filter -= h, (sender,e)=> msg.FilterEventHandler(sender,e) ); cvs.IsLiveFilteringRequested = true; cvs.IsLiveFilteringRequested = false; msg.Response = cvs.View.Cast<object>().Count(); } } }
上のActionに渡すMessageがこちら。
using System.Windows; using System.Windows.Data; using Livet.Messaging; namespace Bells.Behaviors { /// <summary> /// Filter変更用のメッセージ。Responseはint型でフィルター適用後の件数を受け取る。 /// </summary> public class FilterMessage : ResponsiveInteractionMessage<int> { public FilterMessage() { } public FilterMessage(string messageKey): base(messageKey) { } public FilterMessage(string collectionViewSourceKey, FilterEventHandler filterEventHandler, string messageKey) : this(messageKey) { CollectionViewSourceKey = collectionViewSourceKey; FilterEventHandler = filterEventHandler; _msgKey = messageKey; } private readonly string _msgKey; public string CollectionViewSourceKey { get; set; } public FilterEventHandler FilterEventHandler { get; set; } protected override Freezable CreateInstanceCore() { return new FilterMessage(CollectionViewSourceKey, FilterEventHandler, _msgKey); } } }
これを利用するViewのXAMLでは、まず、
<Window.Resources> <CollectionViewSource Source="{Binding BellCollection}" x:Key="BellSource1"/> </Window.Resources>
などとResourceの中にCollectionViewSourceを定義し、そちらからViewModelの本来のバインドすべき大元のCollectionなどへバインドしておき、さらに、
<ListBox ItemsSource="{Binding Source={StaticResource BellSource1}}"/>
と、表示するListBoxなどからはこのCollectionViewSourceをItemsSourceに指定しておきます。
また、ViewModelからMessageが投げられたときに肝心のActionを動作させるために
<i:Interaction.Triggers> <l:InteractionMessageTrigger Messenger="{Binding Messenger}" MessageKey="SetFilter"> <behaviors:SetCollectionViewSourceFilterAction/> </l:InteractionMessageTrigger> </i:Interaction.Triggers>
の記述も必要です。View側は以上。
続いて、実際にこのActionをViewModelから呼ぶためには、まずFilterイベントハンドラを作っておく必要があります。
private void Filter(object sender, FilterEventArgs e) { //e.Item にobject型でCollectionViewsSourceの各行のレコードが入ってくるので(レコード全件と同じ回数呼ばれます) //元の型にキャストして、そのプロパティなどを元に判断し、表示するかしないか決定します。 //e.Accepted をtrue/falseに設定するかでその行の表示/非表示を決定してreturn。 }
で、実際に、何かボタンを押した時にフィルタ条件を変更する場合には、
public void Search() { var msg = new FilterMessage("BellSource1", Filter, "SetFilter"); Messenger.Raise(msg); if (msg.Response == 0) Messenger.Raise(new InformationMessage("該当レコードなし", "該当レコードなし", MessageBoxImage.Information, "Info")); }
こんな感じになりますね。フィルタ条件変更後、1件以上何か表示されるならそれで終了。条件に合うレコードが0件で何も表示されない時だけメッセージボックスも表示しています。