Reputation: 21855
I currently have a ListBox binded to a collection of items. As the collection is big we want to filter the items being shown based on the text entered on a TextBox.
What I'm asking is if this is possible to implement using only XAML, I don't want to modify the collection of items, I would like to modify the Visibility of each of the items based on the filter.
Hope its clear,
thanks!
Upvotes: 11
Views: 22209
Reputation: 41393
You can use the CollectionViewSource to apply filtering, another example can be found here and here.
Upvotes: 6
Reputation: 1332
Like CodeNaked and devdigital told you CollectionViewSource/CollectionView/ICollectionView are the keys to your goal
It's a MVVM patter but this is a View only related problem so I don't want this code at the ViewModel.
thats not the right way because the View only shows what she get´s but shouldn´t modifi so it should/must be your ViewModel who handel changes
so now some code snips:
public class myVM
{
public CollectionViewSource CollViewSource { get; set; }
public string SearchFilter
{
get;
set
{
if(!string.IsNullOrEmpty(SearchFilter))
AddFilter();
CollViewSource.View.Refresh(); // important to refresh your View
}
}
public myVM(YourCollection)
{
CollViewSource = new CollectionViewSource();//onload of your VM class
CollViewSource.Source = YourCollection;//after ini YourCollection
}
}
Xaml Snip:
<StackPanel>
<TextBox Height="23" HorizontalAlignment="Left" Name="tB" VerticalAlignment="Top"
Width="120" Text="{Binding SearchFilter,UpdateSourceTrigger=PropertyChanged}" />
<DataGrid Name="testgrid" ItemsSource="{Binding CollViewSource.View}"/>
</StackPanel>
Edit i forgot the Filter
private void AddFilter()
{
CollViewSource.Filter -= new FilterEventHandler(Filter);
CollViewSource.Filter += new FilterEventHandler(Filter);
}
private void Filter(object sender, FilterEventArgs e)
{
// see Notes on Filter Methods:
var src = e.Item as YourCollectionItemTyp;
if (src == null)
e.Accepted = false;
else if ( src.FirstName !=null && !src.FirstName.Contains(SearchFilter))// here is FirstName a Property in my YourCollectionItem
e.Accepted = false;
}
Upvotes: 8
Reputation: 184506
The only thing XAML really does is encapsulating logic in a declarative fashion. Using markup extensions you can do quite a lot, here's an example:
<StackPanel>
<StackPanel.Resources>
<CollectionViewSource x:Key="items" Source="{Binding Data}">
<CollectionViewSource.Filter>
<me:Filter>
<me:PropertyFilter PropertyName="Name"
RegexPattern="{Binding Text, Source={x:Reference filterbox}}" />
</me:Filter>
</CollectionViewSource.Filter>
</CollectionViewSource>
</StackPanel.Resources>
<TextBox Name="filterbox" Text="Skeet">
<TextBox.TextChanged>
<me:ExecuteActionsHandler ThrowOnException="false">
<me:CallMethodAction>
<me:CallMethodActionSettings MethodName="Refresh"
TargetObject="{Binding Source={StaticResource items}}" />
</me:CallMethodAction>
</me:ExecuteActionsHandler>
</TextBox.TextChanged>
</TextBox>
<!-- ListView here -->
</StackPanel>
(Note that this works but it will trip every GUI designer, also there is no IntelliSense for the events as they usually are not set via element syntax.)
There are several markup extensions here of which two create handlers and one creates an action:
The extensions look like this:
[ContentProperty("Filters")]
class FilterExtension : MarkupExtension
{
private readonly Collection<IFilter> _filters = new Collection<IFilter>();
public ICollection<IFilter> Filters { get { return _filters; } }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new FilterEventHandler((s, e) =>
{
foreach (var filter in Filters)
{
var res = filter.Filter(e.Item);
if (!res)
{
e.Accepted = false;
return;
}
}
e.Accepted = true;
});
}
}
public interface IFilter
{
bool Filter(object item);
}
Quite straightforward, just loops through filters and applies them. Same goes for the ExecuteActionsHandlerExtension
:
[ContentProperty("Actions")]
public class ExecuteActionsHandlerExtension : MarkupExtension
{
private readonly Collection<Action> _actions = new Collection<Action>();
public Collection<Action> Actions { get { return _actions; } }
public bool ThrowOnException { get; set; }
public ExecuteActionsHandlerExtension()
{
ThrowOnException = true;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new RoutedEventHandler((s, e) =>
{
try
{
foreach (var action in Actions)
{
action.Invoke();
}
}
catch (Exception)
{
if (ThrowOnException) throw;
}
});
}
}
Now the last extension is a bit more complicated as it actually needs to do something concrete:
[ContentProperty("Settings")]
public class CallMethodActionExtension : MarkupExtension
{
//Needed to provide dependency properties as MarkupExtensions cannot have any
public CallMethodActionSettings Settings { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return new Action(() =>
{
bool staticCall = Settings.TargetObject == null;
var argsCast = Settings.MethodArguments.Cast<object>();
var types = argsCast.Select(x => x.GetType()).ToArray();
var args = argsCast.ToArray();
MethodInfo method;
if (staticCall)
{
method = Settings.TargetType.GetMethod(Settings.MethodName, types);
}
else
{
method = Settings.TargetObject.GetType().GetMethod(Settings.MethodName, types);
}
method.Invoke(Settings.TargetObject, args);
});
}
}
public class CallMethodActionSettings : DependencyObject
{
public static readonly DependencyProperty MethodNameProperty =
DependencyProperty.Register("MethodName", typeof(string), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public string MethodName
{
get { return (string)GetValue(MethodNameProperty); }
set { SetValue(MethodNameProperty, value); }
}
public static readonly DependencyProperty TargetObjectProperty =
DependencyProperty.Register("TargetObject", typeof(object), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public object TargetObject
{
get { return (object)GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty TargetTypeProperty =
DependencyProperty.Register("TargetType", typeof(Type), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public Type TargetType
{
get { return (Type)GetValue(TargetTypeProperty); }
set { SetValue(TargetTypeProperty, value); }
}
public static readonly DependencyProperty MethodArgumentsProperty =
DependencyProperty.Register("MethodArguments", typeof(IList), typeof(CallMethodActionSettings), new UIPropertyMetadata(null));
public IList MethodArguments
{
get { return (IList)GetValue(MethodArgumentsProperty); }
set { SetValue(MethodArgumentsProperty, value); }
}
public CallMethodActionSettings()
{
MethodArguments = new List<object>();
}
}
All of these snippets are just quick drafts to demonstrate how one could approach this. (A draft for the property filter implementation can be found in this answer.)
Upvotes: 2
Reputation: 1
Use a data trigger on some property of the item in the collectin and you can do it all in xaml.
Upvotes: 0
Reputation: 1699
There is no way to accomplish this in XAML only. But there are other two ways: 1) using converter
<TextBox x:Name="text"/>
<ListBox Tag="{Binding ElementName=text}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Visibility" Value="{Binding RelativeSource={RelativeSource AncestorType=ListBox},Path=Tag, Converter={StaticResource filterLogicConverter}}"/>
</Style>
</ListBox.ItemContainerStyle>
<LixtBox/>
2) better and more natural way is to use CollectionView.Filter property. It doesn't modify an underlying collection.
var collectionView = CollectionViewSource.GetDefaultView(your_collection);
collectionView.Filter = filter_predicate
Upvotes: 2
Reputation: 34349
You can do this with a CollectionViewSource. You wouldn't want to do this completely in XAML, as it would be much easier to test this if the filtering code is in your view model (assuming an MVVM design pattern).
Upvotes: 2