Difference between revisions of "HowTo:Implement Custom Filtering"
m (→Persisting Filter Values) |
m (→Troubleshooting) |
||
Line 626: | Line 626: | ||
** Ensure that the string you are trying to filter by matches what is returned as the DisplayValue. | ** 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 [[HowTo:Implement_Custom_Filtering#Other_Property_Types|Other Property Types]] section. | ** If the property is a INT / DBL (Integer or Double) type: The string comparison doesn't work directly with non-string types. Check the [[HowTo:Implement_Custom_Filtering#Other_Property_Types|Other Property Types]] section. | ||
+ | ** If filtering a DateTime property, ensure the formatting of the input matches that of the property value in the EXP string. | ||
+ | ** If filtering a GUID property, ensure the same values are compared; DisplayString if the user is inputting readable text, UID if the user has selected an item from a list. | ||
− | + | [[Category:Client|Custom Filtering]] | |
− | + | [[Category:Filtering|Custom Filtering]] | |
− | + | [[Category:Styling|Custom Filtering]] | |
− | + | [[Category:WinX|Custom Filtering]] | |
− | + | [[Category:XAML|Implement Custom Filtering]] | |
− | + | [[Category:Xamarin|Custom Filtering]] | |
== See also == | == See also == |
Revision as of 07:49, 30 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.
![]() | 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.
Contents
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}"
Unloaded="{Binding SkipFiltering}"/>
</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.
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:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=FilterInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.Title.ToLower().Contains("{0}".ToLower())==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=FilterInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="SubTitle_FilterExpression" Expression="(INPUT==null||INPUT=="") ? "true==true" : EXP " Context="{Binding}">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=FilterInput, Path=Text, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.SubTitle.ToLower().Contains("{0}".ToLower())==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=FilterInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="TIT +"||"+ 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:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference FilterInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.Title.ToLower().Contains("{0}".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=="") ? "true==true" : EXP " Context="{Binding}">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference FilterInput}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.SubTitle.ToLower().Contains("{0}".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 +"||"+ 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>
![]() | Note 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
<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.
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:
✅ Instead Do:
<x:String x:Key="Static2_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals("8ca1-4597cc2ae621-6f73cde9-ed38-4cbd")==true</x:String>
<controls:EvalExpression x:Name="FilterExpression" Expression="NAME +"&&"+ STAT1 +"||"+ 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:
✅ Instead Do:
<x:String x:Key="Static2_FilterExpression">Item.Content.MetaDefinition.UID.ToString().ToLower().Equals("4597cc2ae621-6f73cde9-ed38-4cbd-8ca1")==true</x:String>
<controls:EvalExpression x:Key="FilterExpression" Expression="NAME +"&&"+ STAT1 +"||"+ 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>
![]() | Note 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:
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).
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
Xamarin
<Label x:Name="DateString" Text="{Binding Path=Date, Source={x:Reference DateInput}, StringFormat='{0:yyyy/MM/dd}'}"/>
Freetext
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.
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:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=DatePicker, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems["DATE"].Value.ToString("yyyy/MM/dd").Contains("{0}")==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=DatePicker, Path=Tag}" />
</controls:EvalExpression>
Xamarin
<controls:EvalExpressionParameter Name="EXP" Value="{Binding Path=Text, Source={x:Reference DateString}, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems["DATE"].Value.ToString("yyyy/MM/dd").Contains("{0}")==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:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=StartInput, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems["DATE"].Value.ToString("yyyy/MM/dd").CompareTo("{0}")!="-1"'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=StartInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="End_FilterExpression" Context="{Binding}" Expression="(INPUT==null||INPUT=="") ? "true==true" : EXP">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding ElementName=EndInput, Path=Tag, Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems["DATE"].Value.ToString("yyyy/MM/dd").CompareTo("{0}")!="1"'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding ElementName=EndInput, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Name="FilterExpression" Context="{Binding}" Expression="START +"&&"+ 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:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference StartString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems["DATE"].Value.ToString("yyyy/MM/dd").CompareTo("{0}")!="-1"'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference StartString}, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Key="End_FilterExpression" Expression="(INPUT==null||INPUT=="") ? "true==true" : EXP " Context="{Binding}">
<controls:EvalExpressionParameter Name="EXP" Value="{Binding Source={x:Reference EndString}, Path=Text, Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems["DATE"].Value.ToString("yyyy/MM/dd").CompareTo("{0}")!="1"'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding Source={x:Reference EndString}, Path=Text}" />
</controls:EvalExpression>
<controls:EvalExpression x:Key="FilterExpression" Context="{Binding}" Expression="START +"&&"+ 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>
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 '||'):
Displaying a Count
To display the filtered count, use the following syntax:
Persisting Filter Values
One of the drawbacks of this method of filtering is the inability for the UI to 'remember' user inputs when they navigate away from the page. This is because this implementation of dynamic filters exists solely on the UI thread (that is, is defined in XAML only with no actual connection to the data, opposite to Property Based Content Filters, for example), which is re-rendered according to the xaml definition every time the page is loaded. This section describes how to implement a limited solution to this issue, the limitation being that values can currently only be persisted during the 'session', and are lost when the user closes the client.
This solution involves use of the StoreProfileParametersCommand to create key-value pairs (a simple list of items made up of a name and a value), which can then be read using the 'StoredProfileParameters[key]'. The StoreProfileParameterCommand accepts a CommandParameter with the format 'key=value', where 'key' represents the name of the key-value pair that can be used to reference it later (in the case below, "name"), and 'value' represents what string should be returned by referencing it (in this case, the Text attribute of the text field control).
When a user completes their input, we use the StoreProfileParametersCommand to create or update a key-value pair associated with a particular filter, instead of passively reading the value from the input as was done previously.
Furthermore, the associated StoredProfileParameter is fed back to the input control so that if any value is set for that particular key (ie. if a filter value has been created), it will be displayed on the input control. Otherwise, the input control will be reset when next loading the page, as was previously the case with dynamic filtering, whereas the StoredProfileParameter will be persisted, leading to a mismatch between the UI and filtering state.
UWP
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="LostFocus">
<core:InvokeCommandAction Command="{Binding StoreProfileParameterCommand}" CommandParameter="{Binding Text, ElementName=NameInput, Converter={StaticResource StringFormatConverter}, ConverterParameter='name={0}'}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
Xamarin
<Entry.Behaviors>
<behaviors:EventHandlerBehavior EventName="Completed">
<behaviors:InvokeCommandAction Command="{Binding StoreProfileParameterCommand}" CommandParameter="{Binding Path=Text, Source={x:Reference NameInput}, StringFormat='name={0}'}" />
</behaviors:EventHandlerBehavior>
</Entry.Behaviors>
</Entry>
In this implementation, the EvalExpression also needs to be adapted to check whether a particular filter value exists within the StoredProfileParameters list, instead of simply whether the associated input control has a value or selection:
UWP
<controls:EvalExpressionParameter Name="EXP" Value="{Binding StoredProfileParameters[name], Converter={StaticResource StringFormatConverter}, ConverterParameter='Item.PropertyItems["NAME"].ToString("yyyy/MM/dd").Contains("{0}")==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding StoredProfileParameters[name]}" />
</controls:EvalExpression>
Xamarin
<controls:EvalExpressionParameter Name="EXP" Value="{Binding StoredProfileParameters[name], Converter={StaticResource StringFormat}, ConverterParameter='Item.PropertyItems["NAME"].Value.ToString("yyyy/MM/dd").Contains("{0}")==true'}" />
<controls:EvalExpressionParameter Name="INPUT" Value="{Binding StoredProfileParameters[name]}" />
</controls:EvalExpression>
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.
- If filtering a DateTime property, ensure the formatting of the input matches that of the property value in the EXP string.
- If filtering a GUID property, ensure the same values are compared; DisplayString if the user is inputting readable text, UID if the user has selected an item from a list.