Blog
All Blog Posts | Next Post | Previous Post
Filtering in Delphi: From Filtering Data to Filtering Properties
Today
Throughout this series, we've looked at different ways of building and applying filters.
We started with the TTMSFNCFilterBuilder, moved on to visual filter dialogs, and then used TTMSFNCFilterView to let users build filters naturally through controls in the application.
Until now, the result of a filter was mainly used to answer one question:
Does this item match the filter?
With the new TTMSFNCFilterRulesManager, we can take that answer and do something with it.
Instead of only filtering data, a matching expression can now change a property, show or hide controls, enable or disable functionality, or execute your own application logic.
From Filters to Rules
The easiest way to understand the Filter Rules Manager is to look at a rule as three parts:
- Where? - Which objects should be evaluated?
- When? - Which filter condition should match?
- What? - What should happen when it matches?
For example:
For all employees where Country = 'Belgium' set Visible = True
Or:
For all controls where Name starts with 'btn' set Visible = False
The filter itself is still handled by the same filtering logic we've introduced throughout this series.
The difference is that we now attach one or more actions to the result.
A First Rule
Let's start with a small example.
Suppose we have a panel containing several controls and only want controls whose name starts with btn to be visible.
With the Filter Rules Manager, this can be expressed in one rule:
mgr.AddRuleStartsWith( 'ShowActionButtons', pnlControls, 'Controls[*]', 'Name', 'btn', False ).AddVisibilityAction;
There is quite a lot happening in only a few lines, so let's break it down.
The Source
Every rule starts with a Source.
The source is the object from which the Filter Rules Manager starts looking for the items it should process.
In our example, that is:
pnlControls
But the source itself does not necessarily have to be the object we want to change.
Instead, we can navigate from the source to other objects using an ItemsPath.
The ItemsPath
For our panel example, the ItemsPath is:
Controls[*]
This tells the Filter Rules Manager to process all controls belonging to the panel.
The * indicates that every item in the collection should be evaluated.
So our rule now effectively says:
For every control inside pnlControls...
If the ItemsPath is left empty, the rule can also be applied directly to the Source itself.
This makes rules useful for both individual objects and collections of objects.
The Filter
Next, we need to decide when the rule matches.
In our example, we evaluate the Name property:
Name starts with 'btn'
Internally, the rule uses TTMSFNCFilterBuilder to evaluate this expression.
This is important because it means the Filter Rules Manager isn't introducing another expression language.
The same operators and structured filtering concepts from the earlier parts of this series are reused here.
You can create a rule using one of the helper methods, as we did above, or provide filter text directly.
Rule.FilterText := 'Name LIKE ''btn%''';
More complex expressions can also be used, including combinations of multiple conditions.
The Action
So far, we know which objects we are processing and which ones match.
Now comes the new part: what should happen?
In our example, we added:
AddVisibilityAction
This changes the Visible property depending on whether the item matches the filter.
A matching control becomes visible, while a non-matching control becomes hidden.
The complete rule can therefore be read almost as a sentence:
mgr.AddRuleStartsWith( 'ShowActionButtons', pnlControls, 'Controls[*]', 'Name', 'btn', False ).AddVisibilityAction;
For all controls on this panel, find the controls whose Name starts with "btn", and set their visibility accordingly.
Actions Are Not Limited to Visibility
AddVisibilityAction is a convenient shortcut, but actions are not limited to the Visible property.
An action can target another property using an RTTI path.
Conceptually, an action consists of:
- A target property
- A value when the filter matches
- Optionally, a value when it doesn't match
- Optionally, a value when the rule is inactive
For example:
Rule.AddAction( 'Enabled', True, False );
A matching item will have Enabled set to True, while a non-matching item will have it set to False.
There are also convenience methods for common scenarios:
Rule.AddVisibilityAction; Rule.AddEnableAction; Rule.AddDisableAction;
And because the target is a property path, actions can go beyond simple top-level properties.
This opens the door to changing appearance and other properties based on filter conditions as well.
Multiple Actions on One Rule
A rule isn't limited to one action either.
Once an item matches, multiple properties can be changed as part of the same rule.
For example, a rule could make an item visible and enable it at the same time:
Rule .AddVisibilityAction .AddEnableAction;
This keeps the condition in one place.
You don't need to evaluate the same expression several times just because multiple things should happen when it matches.
Rules Can Be Active or Inactive
Every rule has an Active property.
This gives you an easy way to switch behavior on or off.
mgr.Rules[0].Active := not mgr.Rules[0].Active; mgr.Rules[0].Apply;
When a rule is active, its filter is evaluated and the match or no-match actions are applied.
When it is inactive, the rule can use an InactiveValue instead.
This is useful when disabling a rule should also restore the objects to a known state.
So an action can effectively have three outcomes:
- Match - the item matches the filter.
- No match - the item doesn't match.
- Inactive - the rule itself is disabled.
Rules on Your Own Objects
The Filter Rules Manager is not limited to visual FNC controls.
One of the more interesting use cases is applying rules to your own Delphi objects.
Suppose we have an employee object:
TEmployee = class(TComponent) private FName: string; FCompany: string; FCity: string; FCountry: string; FVisible: Boolean; published property Name: string read FName write FName; property Company: string read FCompany write FCompany; property City: string read FCity write FCity; property Country: string read FCountry write FCountry; property Visible: Boolean read FVisible write FVisible; end;
We can then create a rule that evaluates these objects and writes the result directly to their Visible property.
FManager
.AddRuleStartsWith(
'NameFilter',
FEmployees,
'Items[0..*]',
'Name',
'A'
)
.AddVisibilityAction;When the rule is applied, the manager iterates over the employees, evaluates the Name property and updates Visible.
Your application can then decide how that property is used.
A grid could display only visible employees, for example, without the Filter Rules Manager needing to know anything about that grid.
Dynamic Rules with Placeholders
So far, the values in our rules have been fixed.
But application rules are often based on values that change at runtime.
This is where placeholders become useful.
A placeholder connects part of a rule to a property on another object.
For example, suppose we have a search edit:
FManager.AddPlaceholder( 'SearchText', EditSearch, 'Text' );
We can then use that value inside the rule:
{SearchText}Every time the rule is applied, the current value of EditSearch.Text is used.
This means the rule doesn't have to be recreated whenever the search text changes.
Even the Field Can Be Dynamic
Placeholders are not limited to filter values.
They can also be used in other parts of the rule, including the field being filtered.
For example, imagine a ComboBox where the user selects whether to search by Name, Company, City or Country.
We can first retrieve the selected index:
FManager.AddPlaceholder( 'SearchFieldComboIndex', SearchFieldCombo, 'ItemIndex' );
Then use that index to retrieve the selected field:
FManager.AddPlaceholder(
'FilterField',
SearchFieldCombo,
'Items[{SearchFieldComboIndex}]'
);And finally use both placeholders in the rule:
FManager
.AddRuleStartsWith(
'EmployeeFilter',
FEmployees,
'Items[0..*]',
'{FilterField}',
'{SearchText}'
)
.AddVisibilityAction;The same rule can now search different properties without being rebuilt.
Change the ComboBox, enter another search value, call Apply, and the current values are resolved automatically.
Applying the Rules
Once the rules have been configured, they can be applied through the manager:
FManager.Apply;
The manager processes its rules and, for each rule:
- Resolves the source and item path.
- Resolves any placeholders.
- Builds the filter.
- Evaluates the filter against every item.
- Determines whether the item matches.
- Applies the corresponding actions.
A rule can also be applied individually:
FManager.Rules[0].Apply;
This is useful when only one particular rule needs to be refreshed.
What If an Action Is Not a Property?
Not every action can be expressed as setting a property.
Sometimes you need to execute application-specific code.
For those situations, rules also support callbacks.
The callback receives the rule, the current item, its index and whether it matched.
This gives you the structured rule evaluation while leaving the final behavior completely up to your application.
There are also manager events available when you need more control over whether a rule or item should be processed, or when you want to override the resulting match.
For most common cases, however, property actions keep the implementation much simpler and avoid unnecessary event handlers.
Counting Matches Without Applying Actions
Sometimes you only want to know how many items currently match a rule.
For that, a rule provides CountMatchingItems.
Count := Rule.CountMatchingItems;
This evaluates the filter without writing the action values to the objects.
It can be useful for previews, counters or showing the user how many items a rule will affect before applying it.
Why a Filter Rules Manager?
Of course, all of this could also be written manually.
You could iterate over a collection, write several if statements, change properties and repeat that logic whenever the application state changes.
But as the number of conditions grows, that logic quickly becomes spread throughout the application.
The Filter Rules Manager brings those pieces together:
- The condition is represented by a filter.
- The objects to evaluate are defined by a source and item path.
- The result is connected to one or more actions.
- Runtime values can be supplied through placeholders.
- The complete rule can be enabled, disabled and reapplied.
Instead of writing:
if Employee.Country = 'Belgium' then begin Employee.Visible := True; Employee.Enabled := True; end else begin Employee.Visible := False; Employee.Enabled := False; end;
The condition and its effects become a reusable rule.
Where Can This Be Used?
The Filter Rules Manager isn't specifically a grid filtering component or a UI visibility component.
It is deliberately more generic.
Some examples include:
- Show or hide controls depending on application state.
- Enable actions only when certain conditions are met.
- Change the appearance of matching items.
- Apply rules to collections of your own objects.
- Highlight records requiring attention.
- Control which application actions are currently available.
- Apply multiple property changes from a single condition.
And because the logic is based on the same filtering foundation used throughout this series, you don't need to learn another way of describing conditions.
From Filtering Data to Application Logic
This is ultimately the idea behind TTMSFNCFilterRulesManager.
A filter doesn't necessarily have to mean:
"Remove everything that doesn't match."
It can simply describe a condition:
"When this matches, do this."
By separating the condition from the action, the same structured filtering logic can be used in many more places throughout an application.
And because rules can work with component properties as well as your own objects, the Filter Rules Manager isn't tied to one particular control or use case.
It takes the FilterBuilder we've been using throughout this series and turns it into a more general rules system for Delphi applications.
Gjalt Vanhouwaert
Related Blog Posts
-
Filtering in Delphi: From Strings to Structured Logic
-
Filtering in Delphi: Generating, Parsing and Matching Filters
-
Filtering in Delphi: Visual Filter Building
-
Filtering in Delphi: See the Filter Dialog in Action
-
Filtering in Delphi: Let Users Filter Naturally
-
Filtering in Delphi: From Filtering Data to Filtering Properties
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post