Jump to: navigation, search

Difference between revisions of "HowTo:Implement Custom Filtering"


m (Comparison: added indents to Xamarin code)
(Comparison: Restructed Open and Closed range sections into one)
Line 478: Line 478:
  
  
'''Open Range'''
+
'''Range'''
 
<br>
 
<br>
Open Range denotes a single comparison, however, we are not filtering in items of a specific date, but rather all items with their date property after or before the user input (depending on whether a  'Starting From' or 'Ending By' comparison is implemented.  
+
It is also possible to filter not for a specific date, but rather for all items whose property falls after or before the user input (depending on whether a  'Starting From' or 'Ending By' comparison is implemented. An 'open' range filter is created when only one value is inputted by the user, whereas a 'closed' range has a start and end date, and must fall on or in between these two.
  
For range comparison, we use the  [https://learn.microsoft.com/en-us/dotnet/api/system.string.compareto?view=net-9.0 Microsoft .CompareTo method]. As documented there, this method outputs a -1/0/1 depending on the compared string's location in the range. Our expression can target one output for an 'exclusive' filter (in which the inputted date is not considered for the range), however, a more likely scenario is that the user wants the inputted date included in the filter. This is why the expression in the StringFormatConverter should be defined as either !=-1 or !=1; the instance is either not less than a minimum (where true would be an output of 0 or 1), or not greater than a maximum (true = -1 or 0), respectively.
+
For range comparison, we use the  [https://learn.microsoft.com/en-us/dotnet/api/system.string.compareto?view=net-9.0 Microsoft .CompareTo method]. As documented there, this method outputs a -1/0/1 depending on the compared string's location in the range. Our expression can target one output for an 'exclusive' filter (in which the inputted date is not considered for the range), however, a more likely scenario is that the user wants the inputted date included in the filter. This is why the expression in the StringFormatConverter should be defined for start date as !=-1, and for end date as !=1; the instance should be not less than than the start date (where true would be an output of 0 or 1), and not greater than the end date (true = -1 or 0), respectively.
  
The following example shows a 'Starting From' comparison (0 or 1), where the user property value should be equal to or later than the user's inputted value.
+
Similar to the earlier section on [[HowTo:Implement_Custom_Filtering#Multiple_Dynamic_.28User_Inputs.29_Criteria|multiple inputs]], separate EvalExpressions are used for each half of the range, meaning that the filtering also works (as an 'open Range' filter) when only one value is inputted by the user. If an open range filter is all that is required by the usecase, the second input and EvalExpression can simply be eliminated.
  
 
<tabs>
 
<tabs>
<tab name="UWP -  Starting From">
+
<tab name="UWP">
 
<source lang = "xml">
 
<source lang = "xml">
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
+
<controls:EvalExpression x:Name="Start_FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
     <controls:EvalExpressionParameter Name="EXP" Value="{Binding Path=Tag, ElementName=DateInput, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;-1&quot;'}" />
+
     <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=StartInput, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;-1&quot;'}" />
     <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Path=Tag, ElementName=DateInput}" />
+
     <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=StartInput, Path=Text}" />
 
</controls:EvalExpression>
 
</controls:EvalExpression>
 +
<controls:EvalExpression x:Name="End_FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
 +
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=EndInput, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;1&quot;'}" />
 +
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=EndInput, Path=Text}" />
 +
</controls:EvalExpression>
 +
 +
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="START +&quot;&amp;&amp;&quot;+ END">
 +
    <controls:EvalExpressionParameter Name="START" Value="{Binding ElementName=Start_FilterExpression, Path=Result}" />
 +
    <controls:EvalExpressionParameter Name="END" Value="{Binding ElementName=End_FilterExpression, Path=Result}" />
 +
</controls:EvalExpression>
 
</source>
 
</source>
</tab>
 
  
<tab name="UWP -  Ending By">
 
<source lang = "xml">
 
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
 
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding Path=Tag, ElementName=DateInput, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;1&quot;'}" />
 
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Path=Tag, ElementName=DateInput}" />
 
</controls:EvalExpression>
 
</source>
 
 
</tab>
 
</tab>
 
+
<tab name="Xamarin">
<tab name="Xamarin -  Starting From">
+
 
<source lang = "xml">
 
<source lang = "xml">
<controls:EvalExpression x:Key="FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
+
<controls:EvalExpression x:Key="Start_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
     <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference DateString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;-1&quot;'}" />
+
     <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference StartString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;-1&quot;'}" />
     <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference DateString}, Path=Text}" />
+
     <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference StartString}, Path=Text}" />
 +
</controls:EvalExpression>
 +
<controls:EvalExpression x:Key="End_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
 +
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference EndString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;1&quot;'}" />
 +
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference EndString}, Path=Text}" />
 
</controls:EvalExpression>
 
</controls:EvalExpression>
</source>
 
</tab>
 
  
<tab name="Xamarin -  Ending By">
+
<controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="START +&quot;&amp;&amp;&quot;+ END">
<source lang = "xml">
+
     <controls:EvalExpressionParameter Name="START" Value="{Binding Source={StaticResource Start_FilterExpression}, Path=Result}" />
<controls:EvalExpression x:Key="FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
+
     <controls:EvalExpressionParameter Name="END" Value="{Binding Source={StaticResource End_FilterExpression}, Path=Result}" />
     <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference DateString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;1&quot;'}" />
+
     <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference DateString}, Path=Text}" />
+
 
</controls:EvalExpression>
 
</controls:EvalExpression>
 
</source>
 
</source>
 
</tab>
 
</tab>
  
{{Hint|The difference between seeking a 'Staring From' (0, 1) or 'Ending By' comparison (-1, 0) is simply defined in the EXP ConverterParameter expression string, which ends with <noWiki>!=-1</noWiki>, or <noWiki>!=1</noWiki>, respectively.}}
+
{{Hint|Note the .CompareTo method used in each half of the ranged filter expression; the start date comparison is <noWiki>!=-1</noWiki>, whereas the end date comparison <noWiki>!=1</noWiki>.}}
 
</tabs>
 
</tabs>
 
 
'''Closed Range'''
 
For range comparison, we use the [https://learn.microsoft.com/en-us/dotnet/api/system.string.compareto?view=net-9.0 Microsoft .CompareTo method]. As documented there, this method outputs a -1/0/1 depending on the compared string's location in the range. Our expression can target one output for an 'exclusive' filter (in which the inputted date is not considered for the range), however, a more likely scenario is that the user wants the inputted date included in the filter. This is why the expression in the StringFormatConverter should be defined for start date as !=-1, and for end date as !=1; the instance should be not less than than the start date (where true would be an output of 0 or 1), and not greater than the end date (true = -1 or 0), respectively.
 
 
 
{{Attention|Note the .CompareTo method used in each half of the ranged filter expression! The start date comparison is !=-1, whereas the end date comparison !=1. }}
 
  
 
<br>
 
<br>

Revision as of 12:40, 29 July 2025

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.

IC Attention.pngAre 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:controls="using:UBIK.WinX.Controls"
xmlns:cv="using:UBIK.WinX.UI.CollectionView"

Xamarin

xmlns:controls="clr-namespace:UBIK.CPL.Controls;assembly=UBIK.CPL"

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

<Grid.Resources>
        <controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true&quot; : 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[&quot;NAME&quot;].ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        </controls:EvalExpression>
        <cv:ListCollectionView
                x:Key="FilterView"
                Expression="{Binding ElementName=FilterExpression, Path=Result}"
                ItemsSource="{Binding Children.Items}" />
</Grid.Resources>
IC Attention.pngNote that the EvalExpression in UWP requires an x:Name, which in turn requires the 'ElementName' syntax seen in the <cv:ListCollectionView>.
IC Attention.pngNote that the Converter seen in the 'EXP' EvalExpressionParameter in UWP is 'StringFormatConverter', which is different than in Xamarin.

Xamarin

<ContentView.Resources>
    <ResourceDictionary>
                <controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true&quot; : 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[&quot;NAME&quot;].ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
                </controls:EvalExpression>
                <controls:SfDataSourceExt
                        x:Key="FilterView"
                        Expression="{Binding Source={StaticResource FilterExpression}, Path=Result}"
                        ItemsSource="{Binding Children.Items}"
                        Unloaded="{Binding SkipFiltering}"/>
    </ResourceDictionary>
</ContentView.Resources>
IC Attention.pngNote that Unloaded attribute improves performance by preventing refiltering while navigating away from the page. This attribute should be added to all instances of controls:SfDataSourceExt in your customizing.
IC Hint square.pngNote that the EvalExpression in Xamarin requires an x:Key, which in turn requires the 'StaticResource ' syntax seen in the <controls:SfDataSourceExt>.
IC Hint square.pngNote that the Converter seen in the 'EXP' EvalExpressionParameter in Xamarin is 'StringFormat', which is different than in UWP.

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

<TextBox x:Name="NameInput" />

Xamarin

<Entry x:Name="NameInput"/>

Items control

An items control will likely be used for displaying filtered results, such as <controls:SelectionBoundListView> for UWP & SfListViewExt for Xamarin:

UWP

<controls:SelectionBoundListView
      x:Name="FilterQueryList"    
      ItemsSource="{StaticResource FilterView}" />

Xamarin

<controls:SfListViewExt
      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:EvalExpression x:Name="Name_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=NameInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values[&quot;NAME&quot;].ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=NameInput, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Name="Desc_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=DescInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values[&quot;DESCR&quot;].ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=DescInput, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="NAME +&quot;&amp;&amp;&quot;+ 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:EvalExpression x:Key="Name_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference NameInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Values[&quot;NAME&quot;].ToLower().Contains(&quot;{0}&quot;.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==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference DescInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Values[&quot;DESCR&quot;].ToLower().Contains(&quot;{0}&quot;.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 +&quot;&amp;&amp;&quot;+ 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.
IC Attention.pngDue to the escaped syntax required in the Expression,
+&quot;&amp;&amp;&quot;+
is the correct syntax required to concatenate filter expressions together.

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.

One Input To Filter Multiple Properties

Since we have control over how the input is matched against the properties, is possible to use the same input to filter against multiple properties, for example, both Title and SubTitle. This keeps the UI lean with less elements to clutter the layout, and the user is not forced to think about which input relates to which field. The downside, of course, is that the search will be slightly less refined. This is achieved easily as shown:

UWP

<controls:EvalExpression x:Name="Title_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=FilterInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Title.ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=FilterInput, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Name="SubTitle_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=FilterInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.SubTitle.ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=FilterInput, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="TIT +&quot;||&quot;+ SUB">
        <controls:EvalExpressionParameter Name="TIT " Value="{Binding ElementName=Title_FilterExpression, Path=Result}" />
        <controls:EvalExpressionParameter Name="SUB" Value="{Binding ElementName=SubTitle_FilterExpression, Path=Result}" />
</controls:EvalExpression>

Xamarin

<controls:EvalExpression x:Key="Title_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference FilterInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Title.ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference FilterInput}, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Key="SubTitle_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
        <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference FilterInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.SubTitle.ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />
        <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference FilterInput}, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="TIT +&quot;||&quot;+ SUB">
        <controls:EvalExpressionParameter Name="TIT" Value="{Binding Source={StaticResource Title_FilterExpression}, Path=Result}" />
        <controls:EvalExpressionParameter Name="SUB" Value="{Binding Source={StaticResource SubTitle_FilterExpression}, Path=Result}" />
</controls:EvalExpression>
IC Attention.pngNote the different operator required in this case. Unless your object has the same Title and SubTitle, there will be no match found unless the FilterExpression is amended to an Or relationship.


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

<x:String x:Key="Static_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower()==&quot;6f73cde9-ed38-4cbd-8ca1-4597cc2ae621&quot;</x:String>

<controls:EvalExpression x:Name="FilterExpression" Expression="NAME +&quot;&amp;&amp;&quot;+ DESC +&quot;&amp;&amp;&quot;+ 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

<x:String x:Key="Static_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower()==&quot;6f73cde9-ed38-4cbd-8ca1-4597cc2ae621&quot;</x:String>

<controls:EvalExpression x:Key="FilterExpression" Expression="NAME +&quot;&amp;&amp;&quot;+ DESC +&quot;&amp;&amp;&quot;+ 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.

IC Attention.pngThe above syntax only works for individual static string filters. If you need to chain multiple hardcoded filters, such as filtering in several UIDs, they need one String resource each, and must be added to the FilterExpression expression individually as in the below example.


Multiple Static Criteria

Experimentation has shown that the dynamic filter struggles to combine a single string made of multiple expressions, with additional expressions via the FilterExpression. However, it is still possible to combine user inputs and multiple static criteria, simply by breaking up the static criteria into separate strings.

Of course, when for example filtering various MetaDefinitions.UIDs, the '&' (and) comparison condition is no longer appropriate, as there will never be matches for multiple of these UIDs that returns a true result. In these cases, the comparison condition needs to be set to '||' (Or). The below example shows the correct syntax.

UWP


⛔️ Don't Do:

<x:String x:Key="Static_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;6f73cde9-ed38-4cbd-8ca1-4597cc2ae621&quot;)==true &amp;&amp; Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;8ca1-4597cc2ae621-6f73cde9-ed38-4cbd&quot;)==true</x:String>


Instead Do:

<x:String x:Key="Static1_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;6f73cde9-ed38-4cbd-8ca1-4597cc2ae621&quot;)==true</x:String>
<x:String x:Key="Static2_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;8ca1-4597cc2ae621-6f73cde9-ed38-4cbd&quot;)==true</x:String>

<controls:EvalExpression x:Name="FilterExpression" Expression="NAME +&quot;&amp;&amp;&quot;+ STAT1 +&quot;||&quot;+ STAT2" Context="{Binding}">
        <controls:EvalExpressionParameter Name="NAME" Value="{Binding ElementName=Name_FilterExpression, Path=Result}" />
        <controls:EvalExpressionParameter Name="STAT1" Value="{StaticResource Static1_FilterExpression}" />
        <controls:EvalExpressionParameter Name="STAT2" Value="{StaticResource Static2_FilterExpression}" />
</controls:EvalExpression>

Xamarin


⛔️ Don't Do:

<x:String x:Key="Static_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;6f73cde9-ed38-4cbd-8ca1-4597cc2ae621&quot;)==true &amp;&amp; Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;4597cc2ae621-6f73cde9-ed38-4cbd-8ca1&quot;)==true</x:String>


Instead Do:

<x:String x:Key="Static1_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;6f73cde9-ed38-4cbd-8ca1-4597cc2ae621&quot;)==true</x:String>
<x:String x:Key="Static2_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals(&quot;4597cc2ae621-6f73cde9-ed38-4cbd-8ca1&quot;)==true</x:String>

<controls:EvalExpression x:Key="FilterExpression" Expression="NAME +&quot;&amp;&amp;&quot;+ STAT1 +&quot;||&quot;+ STAT2" Context="{Binding}">
        <controls:EvalExpressionParameter Name="NAME" Value="{Binding Source={StaticResource Name_FilterExpression}, Path=Result}" />
        <controls:EvalExpressionParameter Name="STAT1" Value="{StaticResource Static1_FilterExpression}" />
        <controls:EvalExpressionParameter Name="STAT2" Value="{StaticResource Static2_FilterExpression}" />
</controls:EvalExpression>
IC Hint square.pngNote that the above strings make use of the .Equals() method, which is an alternate option to the more commonly seen 'Item.Content.MetaDefinition.UID.ToString().ToLower()==...'







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:

ConverterParameter='Item.Values[&quot;INSTRUCTIONID&quot;].ToString().ToLower().Contains(&quot;{0}&quot;.ToLower())==true'


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.

Note that this example uses UWP xaml syntax:

<controls:EvalExpression x:Name="Bool_FilterExpression" Expression="(INPUT==false) ? &quot;true==true&quot; : EXP" Context="{Binding}">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=CheckBox, Path=IsChecked, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Values[&quot;MP_BOOL&quot;]==&quot;{0}&quot;'}" />
    <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

<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=NameInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Properties.VisibleItems[&quot;LK_GUID_PROPERTY&quot;].DisplayString.ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />

Xamarin

<controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference NameInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Properties.VisibleItems[&quot;LK_GUID_PROPERTY&quot;].DisplayString.ToLower().Contains(&quot;{0}&quot;.ToLower())==true'}" />

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).

Wiki Under Construction Start.PNG

Filtering a DateTime Property

Filtering a DateTime property is more complex than other properties, because one has to consider both the input, and the type of filtering.

Input can be done both as freetext, and using a DatePicker. Freetext input has the downside that users have to take care to enter the correct string format (ie. yyyy/MM/dd), whereas the DatePicker outputs a DateTime string which can then be formatted for use in the EvalExpression. The DatePicker does however have certain downsides, chief of which being that it is not nullable, the current date is shown when no date has been selected, making the non-filtered UI indistinguishable from the filtered one.

Filtering can be done as a simple comparison, such as finding an object whose date matches the inputted date. However, it is also possible to ranges; either 'open', that is, dates before or after the user's selection, or 'closed', which is a date falling inside a range created by two user inputs.


Input

The input method is the first important consideration for filtering DateTime properties. A DatePicker is the generally accepted method. Textbox input is also possible, though users will need to correctly format their input to 'yyyy/MM/dd' (such as "2032/12/31") in order to achieve a valid comparison.

DatePicker

UWP

<DatePicker x:Name="DatePicker" Tag="{Binding SelectedDate, ElementName=DatePicker, Converter={StaticResource StringFormatConverter}, ConverterParameter='{0:yyyy/MM/dd}'}" />

Xamarin

<DatePicker x:Name="DateInput" />
<Label x:Name="DateString" Text="{Binding Path=Date, Source={x:Reference DateInput}, StringFormat='{0:yyyy/MM/dd}'}"/>
IC Attention.pngIn both cases, it is necessary that the format of the input matches the format of the property value, which will be explained more in the section on filtering. In the above examples, this is applied in the Tag property (UWP) or through use of a secondary control (a Label in the Xamarin example).


Freetext

UWP

<TextBox x:Name="DateInput" PlaceholderText="Date"/>

Xamarin

<Entry x:Name="DateInput" Placeholder="Date"/>
IC Attention.pngRemember that no matches will be found unless the date is inputted following a 'yyyy/MM/dd' format.
IC Hint square.pngFor greater control over the input format, it is also possible to provide multiple TextBoxes, which could then either be concatenated in an EvalExpression, or used to filter by the DateTime.Day/Week/Month values of the property individually, if such functionality fits the usecase.


Comparison

Since the EvalExpression approach utilizes string comparisons, not only is it necessary to format the input string, but the property value must also be formatted to match. The 'yyyy/MM/dd' format includes several measures to create the best conditions for comparing the two strings;

  • months and days below the value of 10 cannot be a single digit, but must be padded with a preceding 0 in order to keep the string lengths equivalent; 2032/1/1 cannot be property compared with 2032/12/31 for example, as the strings are different lengths.
  • putting the year and month first also improves the chance of a correct comparison, as a day 01 can be after a day 31 if the month and year are greater.
IC Attention.pngFor the rest of this section, a DatePicker input is assumed.


Single

Comparison of a single date against a property value works the same way as other dynamic filters. Therefore it is sufficient to make use of the previously used .Contains() or .Equals() methods. Remember that the Date attribute is not directly usable from the DatePicker, and it's formatted version received via the Tag attribute or an additional label is what is actually used by the EvalExpressions.

UWP

<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=DatePicker, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).Contains(&quot;{0}&quot;)==true'}" />
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=DatePicker, Path=Tag}" />
</controls:EvalExpression>

Xamarin

<controls:EvalExpression x:Key="FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding Path=Text, Source={x:Reference DateString}, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).Contains(&quot;{0}&quot;)==true'}" />
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Path=Text, Source={x:Reference DateString}}" />
</controls:EvalExpression>


Range
It is also possible to filter not for a specific date, but rather for all items whose property falls after or before the user input (depending on whether a 'Starting From' or 'Ending By' comparison is implemented. An 'open' range filter is created when only one value is inputted by the user, whereas a 'closed' range has a start and end date, and must fall on or in between these two.

For range comparison, we use the Microsoft .CompareTo method. As documented there, this method outputs a -1/0/1 depending on the compared string's location in the range. Our expression can target one output for an 'exclusive' filter (in which the inputted date is not considered for the range), however, a more likely scenario is that the user wants the inputted date included in the filter. This is why the expression in the StringFormatConverter should be defined for start date as !=-1, and for end date as !=1; the instance should be not less than than the start date (where true would be an output of 0 or 1), and not greater than the end date (true = -1 or 0), respectively.

Similar to the earlier section on multiple inputs, separate EvalExpressions are used for each half of the range, meaning that the filtering also works (as an 'open Range' filter) when only one value is inputted by the user. If an open range filter is all that is required by the usecase, the second input and EvalExpression can simply be eliminated.

UWP

<controls:EvalExpression x:Name="Start_FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=StartInput, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;-1&quot;'}" />
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=StartInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="End_FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=EndInput, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;1&quot;'}" />
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=EndInput, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="START +&quot;&amp;&amp;&quot;+ END">
    <controls:EvalExpressionParameter Name="START" Value="{Binding ElementName=Start_FilterExpression, Path=Result}" />
    <controls:EvalExpressionParameter Name="END" Value="{Binding ElementName=End_FilterExpression, Path=Result}" />
</controls:EvalExpression>

Xamarin

<controls:EvalExpression x:Key="Start_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference StartString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;-1&quot;'}" />
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference StartString}, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Key="End_FilterExpression" Expression="(INPUT==null||INPUT==&quot;&quot;) ? &quot;true==true&quot; : EXP " Context="{Binding}">
    <controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference EndString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems[&quot;DATE&quot;].Value.ToString(&quot;yyyy/MM/dd&quot;).CompareTo(&quot;{0}&quot;)!=&quot;1&quot;'}" />
    <controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference EndString}, Path=Text}" />
</controls:EvalExpression>

<controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="START +&quot;&amp;&amp;&quot;+ END">
    <controls:EvalExpressionParameter Name="START" Value="{Binding Source={StaticResource Start_FilterExpression}, Path=Result}" />
    <controls:EvalExpressionParameter Name="END" Value="{Binding Source={StaticResource End_FilterExpression}, Path=Result}" />
</controls:EvalExpression>
IC Hint square.pngNote the .CompareTo method used in each half of the ranged filter expression; the start date comparison is !=-1, whereas the end date comparison !=1.



Wiki Under Construction End.PNG

Adjustments

'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 '||'):

+&quot;||&quot;+


Displaying a Count

To display the filtered count, use the following syntax:

"{Binding Path=Count, Source={StaticResource FilterView}}"


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.





See also