This page describes how to customize filtered lists that accept one or multiple user inputs as filter criteria (referred to as 'Dynamic'), alone or combined with predefined criteria(referred to as 'Static'). The following examples are based on the Children.Items collection, however, Properties or Documents can be filtered too, by adjusting the ItemsSource in the <cv:ListCollectionView> or <controls:SfDataSourceExt> in UWP or Xamarin respectively.
Are you sure you need this? UBIK has a simpler out-of-the-box Property-based filtering technique for filtering child objects when one or multiple property value inputs are the only type of criteria required. See Property Based Content Filters |
Four elements are required for Custom Filtering:
- Input field - for user inputs.
- EvalExpression - for converting user inputs into a filter expression.
- Collection Filter - for applying the expression to a collection.
- Items control - for displaying the filtered items.
This system acts dynamically to output the filtered list, without requiring the use of an Evaluation button, and directly outputs all items that match the filter input.
This page will first describe the basic implementation of a single dynamic input, then describe how to allow for multiple inputs, and finally, how to combine multiple dynamic inputs with static, predefined criteria. However, keep in mind that for simply dynamically filtering child objects based on single or multiple property values, this complex customizing is not required. Instead, this technique gives the possibility to create multiple collections based on the same child collection, each with individual filters that do not interfere with each other, or else to create filters based on dynamic inputs for Properties or Documents, which are not handled by the Content-Based Filtering functionality.
Basic Implementation
To build the filter in XAML, you will need to place an EvalExpression and a Collection Filter in the Resources of your container (most likely, a Grid). In the container's content, you will need the Input control, and the items control as an output (most likely a ListView).
The following namespaces are used:
UWP
xmlns:cv="using:UBIK.WinX.UI.CollectionView"
Xamarin
Resources
For UWP, it is possible to place the EvalExpression and Collection Filter into the <Grid.Resources>, as shown below. However, for Xamarin, it is required to use the <ContentView.Resources>. For the Collection Filter, use ListCollectionView for UWP & Content_filtering for Xamarin:
UWP
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT=="") ? "true" : EXP">
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=NameInput, Path=Text}" />
<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=NameInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values["NAME"].ToLower().Contains("{0}".ToLower())==true'}" />
</controls:EvalExpression>
<cv:ListCollectionView
x:Key="FilterView"
Expression="{Binding ElementName=FilterExpression, Path=Result}"
ItemsSource="{Binding Children.Items}" />
</Grid.Resources>
Xamarin
<ResourceDictionary>
<controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT=="") ? "true" : EXP">
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference NameInput}, Path=Text}" />
<controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference NameInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Values["NAME"].ToLower().Contains("{0}".ToLower())==true'}" />
</controls:EvalExpression>
<controls:SfDataSourceExt
x:Key="FilterView"
Expression="{Binding Source={StaticResource FilterExpression}, Path=Result}"
ItemsSource="{Binding Children.Items}" />
</ResourceDictionary>
</ContentView.Resources>
The EvalExpression Expression first checks whether the TextBox input is empty (as this would otherwise incorrectly be considered a filter input, whereby the property should be empty) and if so returns the unfiltered list (expression outcome hardcoded to 'true'), otherwise, applies the filter (expression outcome is taken from string constructed in ConverterParameter of 'EXP').
There are two important details of the EvalExpressionParameter to note here:
- The "ElementName=NameInput" / "x:Reference NameInput" should match exactly to the name of your Input control.
- The Item.Values["NAME"] should be adapted, by changing NAME to the property that should be filtered by this input.
TextBox / Entry
The input field is just a basic <Textbox> / <Entry> we will use to input the filter criteria value. The only important detail for this input control is the name, which must be passed back to the EvalExpressionParameter.
It will look like this:
UWP
Xamarin
Items control
An items control will likely be used for displaying filtered results, such as <controls:SelectionBoundListView> for UWP & SfListViewExt for Xamarin:
UWP
x:Name="FilterQueryList"
ItemsSource="{StaticResource FilterView}" />
Xamarin
x:Name="FilterQueryResultList"
ItemsSource="{Binding DisplayItems, Source={StaticResource FilterView}}" />
Multiple Dynamic (User Inputs) Criteria
It is likely that the customizing is required to process multiple user inputs for the same filter. In this case, the EvalExpression must be adapted to combine the inputs (most likely using an AND relation, for a 'filtering-down' effect), as well as to deliver the full list when nothing is inputted. A <Textbox> / <Entry>, and an EvalExpression, will be required for every additional property, as well as a final EvalExpression to combine them.
The EvalExpressions should be amended as follows:
UWP
<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=NameInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values["NAME"].ToLower().Contains("{0}".ToLower())==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=NameInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="Desc_FilterExpression" Expression="(INPUT==null||INPUT=="") ? "true==true" : EXP " Context="{Binding}">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=DescInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values["DESCR"].ToLower().Contains("{0}".ToLower())==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=DescInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="NAME +"&&"+ DESC">
<controls:EvalExpressionParameter Name="NAME" Value="{Binding ElementName=Name_FilterExpression, Path=Result}" />
<controls:EvalExpressionParameter Name="DESC" Value="{Binding ElementName=Desc_FilterExpression, Path=Result}" />
</controls:EvalExpression>
Xamarin
<controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference NameInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Values["NAME"].ToLower().Contains("{0}".ToLower())==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference NameInput}, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Key="Desc_FilterExpression" Expression="(INPUT==null||INPUT=="") ? "true==true" : EXP " Context="{Binding}">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference DescInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Values["DESCR"].ToLower().Contains("{0}".ToLower())==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference DescInput}, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="NAME +"&&"+ DESC">
<controls:EvalExpressionParameter Name="NAME" Value="{Binding Source={StaticResource Name_FilterExpression}, Path=Result}" />
<controls:EvalExpressionParameter Name="DESC" Value="{Binding Source={StaticResource Desc_FilterExpression}, Path=Result}" />
</controls:EvalExpression>
The above example shows how to check for inputs to either property, NAME and DESCR, then use an additional EvalExpression to create the expression string that combines them. For each additional property you need to:
- Create an EvalExpression that checks whether the text input is null or empty, and if not, returns the desired expression EXP.
- Add each EvalExpression (Name_FilterExpression, Desc_FilterExpression) to the 'main' one (FilterExpression) as Parameters, combined using the escaped '&&' concatenation symbols.
Due to the escaped syntax required in the Expression, +"&&"+ |
Once the additional input controls have been created, processed in their own EvalExpressions, referenced as EvalExpressionParameters and the "FilterExpression" EvalExpression has been adapted as shown, the filter should already function as required; the Collection Filters and Items Controls do not need to be adapted.
Combining Dynamic (User Inputs) and Static (Predefined) Criteria
A common use of the Collection Filter is to filter child lists based on one or multiple properties, such as MetaDefinition.UID or a certain status, to present groups of specific object types. Therefore, it is likely that a customizing may require hardcoded filter criteria, combined with user input, if any is provided.
The following describes how to implement such a scenario in XAML, again using Multiple Dynamic (User Inputs) Criteria as a starting point.
UWP
<controls:EvalExpression x:Name="FilterExpression" Expression="NAME +"&&"+ DESC +"&&"+ STAT" Context="{Binding}">
<controls:EvalExpressionParameter Name="NAME" Value="{Binding ElementName=Name_FilterExpression, Path=Result}" />
<controls:EvalExpressionParameter Name="DESC" Value="{Binding ElementName=Desc_FilterExpression, Path=Result}" />
<controls:EvalExpressionParameter Name="STAT" Value="{StaticResource Static_FilterExpression}" />
</controls:EvalExpression>
Xamarin
<controls:EvalExpression x:Key="FilterExpression" Expression="NAME +"&&"+ DESC +"&&"+ STAT" Context="{Binding}">
<controls:EvalExpressionParameter Name="NAME" Value="{Binding Source={StaticResource Name_FilterExpression}, Path=Result}" />
<controls:EvalExpressionParameter Name="DESC" Value="{Binding Source={StaticResource Desc_FilterExpression}, Path=Result}" />
<controls:EvalExpressionParameter Name="STAT" Value="{StaticResource Static_FilterExpression}" />
</controls:EvalExpression>
Once one or multiple EvalExpressions have been added to process each input (such as Name_FilterExpression, Desc_FilterExpression), the remaining predefined filter criteria, in this example, filtering for a specific MetaClass by UID, can be added as an x:String, or directly in the EvalExpression paramerter, though the first approach is recommended, for better readability.
Other Property Types
Filtering an INT / Double
Though the string comparison works mostly the same for a Int / Dbl, you additionally have to include a .ToString() method to the EXP EvalExpressionParameter of that property's individual EvalExpression for it to evaluate:
Filtering a Boolean (CheckBox)
To process a Boolean filter, the expression needs to be slightly amended, due to the different type of filter match criteria that is required.
Note that the below implementation returns all items by default (ie. when the CheckBox is unchecked), and returns only True matches when the CheckBox is checked. This functionality is designed to align with string input, where no filtering occurs for each string until something is entered into the corresponding text input box.
<controls:EvalExpression x:Name="Bool_FilterExpression" Expression="(INPUT==false) ? "true==true" : EXP" Context="{Binding}">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=CheckBox, Path=IsChecked, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values["MP_BOOL"]=="{0}"'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=CheckBox, Path=IsChecked}" />
</controls:EvalExpression>
- The individual EvalExpression for a Boolean filter match needs to be amended as follows:
- The INPUT can never be null, as the IsChecked property is either True or False. Therefore we can remove the Null or empty test, and instead, check whether the CheckBox is checked or not. IsChecked=False should 'escape' the expression, and return all items (or, not contribute to the overall filtering).
- The EvalExpressionParameter 'EXP' similarly can be simplified, as it does not need to make a string match with .Contains, but simply check whether the property value matches to True or False.
Changes only need to be made to the individual EvalExpression. The "FilterExpression" that joins all individual expressions does not need to be amended, other than the regular additional EvalExpressionParameter, and updating the expression accordingly.
Filtering a GUID Property
The expression string seen in the "EXP" command parameters shown above compare a user input with the value stored in Values[MP_PROPERTY], of the requested property. However, in certain cases, it may not be adequate to search in Values[MP_PROPERTY], such as when the property is a GUID. This is because Values[LK_GUID_PROPERTY] returns the UID value of the linked object, whereas Properties.VisibleItems[LK_GUID_PROPERTY].DisplayValue returns the human readable label associated with this linked object.
The solution is to rewrite the expression string to target the correct binding:
UWP
Xamarin
A good way to inspect and test the target binding is to output it on the items UI, therefore on the ItemTemplate of your ListView, as well as to output the expressions that are being dynamically generated in your EvalExpressions, by binding the Result to the Text property of a <TextBlock> (UWP) or <Label> (Xamarin).
Adjustments
And / Or Operations
All examples of multiple inputs in this article use an 'and' filter condition, where, if any inputs are entered, only items that match all inputs (NAME and DESC) are kept in the collection, to produce a 'filtering down' effect in the user interface.
If a less strict filter condition is required, the FilterExpression can be altered to have an 'or' condition, where all results that match NAME or DESC, but not necessarily both, are shown. To do so, simply change the concatenation syntax to return 'or' symbols (two pipes or '||'):
Known Issues
Text Input in ListView Header Template
This implementation will not work when parts of it, such as the Input fields, are hosted in a ListView header or footer template, as this content is not readable by the rest of the page and will return a "Can not find the object referenced" parsing error.
To workaround this, you need to move the Input control out of the header template.
Troubleshooting
- Everything looks correct but filtering returns no results:
- Ensure that the string you are trying to filter by matches what is returned as the DisplayValue.
- If the property is a INT / DBL (Integer or Double) type: The string comparison doesn't work directly with non-string types. Check the Other Property Types section.