<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">	<channel>	<title>TMS Software</title>	<link>https://www.tmssoftware.com</link>	<language>en</language>	<pubDate>Tue, 15 Sep 2026 15:37:00 +0100</pubDate>	<docs>http://blogs.law.harvard.edu/tech/rss</docs>	<ttl>1440</ttl>	<generator>TMS Software</generator>	<image>		<url>https://www.tmssoftware.com/site/img/tmslogo.png</url>		<title>TMS Software</title>		<link>https://www.tmssoftware.com</link>	</image>
	<item>
		<title><![CDATA[﻿Data Report with Filtering and Conditional Formatting]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2515</link>
		<author>Gjalt Vanhouwaert</author>
		<pubDate>Tue, 8 Sep 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		<p> In our previous blog post, <a href="https://tmssoftware.com/site/blog.asp?post=2503" target="_blank"> Creating a Database Report Generator with TMS FNC DataGrid </a>, we created a flexible report generator capable of loading database data and exporting the current view to several different formats. </p>

<p> This time, we&#39;re taking that same project a step further. </p>

<p> Instead of focusing on generating the report, we want to give the user more control over <b>what they see and how they see it</b>. </p>

<p>For this, we&#39;ve added:</p>

<ul> <li>Filtering</li> <li>Advanced filtering</li> <li>Conditional formatting</li> <li>A built-in conditional formatting editor</li> <li>Saving and loading formatting and filter settings</li></ul><ul> </ul>

<br><div style="text-align: center;"><a href="https://github.com/tmssoftware/fnc_database_report_generator"><p><span style="background-color: #12237f; color: white; padding: 16px 32px; text-align: center; border-radius: 6px; font-size: 1.35em;">Free Sample</span></p></a></div><br>

<p> What makes these additions particularly interesting is not only what they add to the application, but <b>how little code is actually required to enable them</b>.</p><p><img src="https://www.tmssoftware.com/site/img/blog/FNCDBReportGenerator/FNCDBReporter.png" style="width: 829px;" alt="TMS Software Delphi  Components tmsfncuipack"><br></p>

<p> A lot of this functionality is already built directly into <code>TTMSFNCDataGrid</code>. </p>

<h3>Starting from our Report Generator</h3>

<p> We&#39;ll continue with the database report generator from the <a href="https://tmssoftware.com/site/blog.asp?post=2503" target="_blank">previous blog post</a>. </p>

<p> That project already takes care of connecting to the database, displaying the data in <code>TTMSFNCDataGrid</code>, and exporting the resulting report. We won&#39;t repeat that implementation here. </p>

<p> Instead, we&#39;ll focus entirely on making that data more interactive. </p>

<h3>Adding Filtering: One Line</h3>

<p> Giving users the ability to filter the data displayed in the grid requires: </p>

<pre><code class="language-delphi">Grid.Options.Filtering.Enabled := True;</code></pre>

<p> That&#39;s it. </p>

<p> With one property, the DataGrid gets its filtering interface. </p>

<p> In the sample, we&#39;ve exposed this through a checkbox: </p>

<pre><code class="language-delphi">Grid.Options.Filtering.Enabled := CheckFiltering.IsChecked;</code></pre>

<p> The user can now enable or disable filtering without us having to create a separate filtering interface, parse values or manually build a filtering system around the grid. </p>

<p> This is a good example of the low-code approach behind the DataGrid: <b>enable the capability and let the component provide the UI and behaviour.</b> </p>

<h3>Need More? Enable Advanced Filtering</h3>

<p> For more complex datasets, a simple filter value isn&#39;t always enough. </p>

<p> A user might want to specify whether a value should be equal to, greater than, smaller than or otherwise compared against another value. </p>

<p> For that, we can switch the existing filtering functionality into its advanced mode: </p>

<pre><code class="language-delphi">Grid.Options.Filtering.Advanced := True;</code></pre>

<p> Again, we&#39;re talking about <b>one line of Delphi code</b>. </p>

<p> Instead of building an advanced filter interface ourselves, the DataGrid provides the richer operator-based filtering UI. </p>

<p> In our application, the user can switch between both modes: </p>

<pre><code class="language-delphi">Grid.Options.Filtering.Advanced := CheckAdvancedFilter.IsChecked;</code></pre>

<p> This means we can progressively expose more functionality without making the basic interface more complicated than necessary. </p>

<p> Enable filtering when you need it. Enable advanced filtering when you need more control. </p>

<p> No separate filtering system needs to be developed. </p>
<p style="text-align: center; "><img src="https://www.tmssoftware.com/site/img/blog/FNCDBReportGenerator/FNCDBReporterFilter.png" style="width:70%;" alt="TMS Software Delphi  Components tmsfncuipack"><br></p>

<h3>Conditional Formatting: A Brand-New DataGrid Feature</h3>

<p> Filtering helps users find the information they&#39;re interested in. Conditional formatting helps them <b>see important information immediately</b>. </p>

<p> Conditional formatting is a brand-new feature in <code>TTMSFNCDataGrid</code>. We introduced the feature in more detail in: </p>

<p> <a href="https://tmssoftware.com/site/blog.asp?post=2514" target="_blank"> <b>our dedicated conditional formatting blog post</b> </a>. </p>

<p> If you want to learn more about the available rules and how conditional formatting works internally, that blog post is the best place to start. Here, we&#39;ll focus on how easily we can add it to our existing report generator. </p>

<p> Enabling conditional formatting requires: </p>

<pre><code class="language-delphi">Grid.ConditionalFormatting.Enabled := True;</code></pre>

<p> Once again, one line is enough to enable an entirely new layer of functionality in our application. </p>

<p> Conditional formatting can then be used to visually distinguish values using rules such as: </p>

<ul> <li>Data bars</li> <li>Colour scales</li> <li>Top-N values</li> <li>Value-based highlights</li> </ul>

<p> This can turn a regular table of database records into something much easier to scan and interpret.</p><p style="text-align: center; "><img src="https://www.tmssoftware.com/site/img/blog/FNCDBReportGenerator/FNCDBReporterFormat.png" style="width:70%;" alt="TMS Software Delphi  Components tmsfncuipack"><br></p><p> </p>

<h3>Let the User Create the Rules</h3>

<p> Enabling conditional formatting is useful, but we don&#39;t necessarily want to decide all the rules beforehand. </p>

<p> The person using the report may know much better which values deserve attention. </p>

<p> So why not give them access to the built-in conditional formatting editor? </p>

<pre><code class="language-delphi">Grid.Root.ShowConditionalFormattingEditor;</code></pre>

<p> With this single call, we can open the DataGrid&#39;s conditional formatting editor. </p>

<p> Users can add, remove and configure their own conditional formatting rules at runtime. </p>


<p> That means we don&#39;t need to create a custom rule editor, separate forms for the different rule types, or the logic required to maintain all those rules ourselves.</p>

<pre><code class="language-delphi">procedure TMainForm.ButtonEditRulesClick(Sender: TObject); <br>begin <br>  Grid.Root.ShowConditionalFormattingEditor; <br>end;</code></pre>

<p> This is where the low-code aspect becomes particularly valuable. </p>

<p> We&#39;re not simply enabling a visual effect with one line of code. We&#39;re exposing an <b>entire configuration interface</b> with it. </p>

<h3>Configure Once, Use Again</h3>

<p> After spending time configuring filters and conditional formatting, users probably don&#39;t want to recreate everything the next time they start the application. </p>

<p> So the sample also adds the ability to save the current configuration. </p>

<p> For conditional formatting, <code>TTMSFNCDataGrid</code> already provides serialization of its rules: </p>

<pre><code class="language-delphi">Grid.ConditionalFormatting.SaveToJSONStream(Stream);</code></pre>

<p> The resulting JSON can later be loaded again with: </p>

<pre><code class="language-delphi">Grid.ConditionalFormatting.LoadFromJSONStream(Stream);</code></pre>

<p> The sample stores the conditional formatting configuration in: </p>

<pre><code>FNCDBReporterFormatting.json</code></pre>

<p> For filtering, we can retrieve the current filter expression through: </p>

<pre><code class="language-delphi">Grid.Filter.FilterText</code></pre>

<p> The sample stores this expression separately in: </p>

<pre><code>FNCDBReporterFilter.ini</code></pre>

<p> When the application starts again, the saved expression can be assigned back to the FilterBuilder: </p>

<pre><code class="language-delphi">Grid.FilterBuilder.FilterText := FilterText;</code></pre>

<p> and applied: </p>

<pre><code class="language-delphi">Grid.RemoveFilter; Grid.ApplyFilter;</code></pre>

<p> This gives us an easy way to restore a previously configured report view. </p>

<h3>Save Your Report View</h3>

<p> Together, these features introduce an interesting concept to our original report generator. </p>

<p> We&#39;re no longer just saving or exporting <b>data</b>. </p>

<p> We&#39;re allowing users to configure a <b>view of that data</b>. </p>

<p> For example, imagine a sales report where somebody: </p>

<ol> <li>Enables advanced filtering.</li> <li>Filters the report to a particular region or product range.</li> <li>Adds a conditional formatting rule to highlight high-value orders.</li> <li>Adds another rule to draw attention to low-performing values.</li> <li>Saves the configuration.</li> </ol>

<p> The next time the report is opened, that configuration can be restored and applied to the latest data. </p>

<p> The data may have changed, but the way the user wants to analyse it hasn&#39;t. </p>

<h3>A Lot of Functionality, Very Little Code</h3>

<p> This extension of the sample demonstrates something that is easy to overlook when looking at a feature list. </p>

<p> Adding filtering sounds simple until you have to create the filtering UI. Advanced filtering means operators, different value types and additional UI. Conditional formatting means defining rules, rendering them and preferably providing an editor. Saving those settings means serialization and restoration. </p>

<p> With <code>TTMSFNCDataGrid</code>, the core functionality is already available. </p>

<p> The essential calls are remarkably small: </p>

<pre><code class="language-delphi">// Filtering <br>Grid.Options.Filtering.Enabled := True; <br>// Advanced filtering <br>Grid.Options.Filtering.Advanced := True; <br>// Conditional formatting <br>Grid.ConditionalFormatting.Enabled := True; <br>// Open the rule editor <br>Grid.Root.ShowConditionalFormattingEditor; <br>// Save formatting <br>Grid.ConditionalFormatting.SaveToJSONStream(Stream); <br>// Load formatting <br>Grid.ConditionalFormatting.LoadFromJSONStream(Stream);</code></pre>

<p> This is the real advantage of the approach. </p>

<p> <b>A few lines of Delphi code can expose functionality that would otherwise require complete interfaces and considerable application logic.</b> </p>

<p> You remain in control of when and where the functionality is available, while <code>TTMSFNCDataGrid</code> takes care of most of the implementation. </p>

<h3>From Static Report to Interactive Report</h3>

<p> Our original project demonstrated how <code>TTMSFNCDataGrid</code> could be used as the foundation of a database report generator. </p>

<p> With these additions, that report becomes much more interactive. </p>

<p> Users can decide which information matters, narrow down larger datasets, visually identify interesting values and preserve their preferred configuration for later use. </p>

<p> And perhaps most importantly for us as Delphi developers, we didn&#39;t have to build all those systems from scratch. </p>

<p> Sometimes adding a powerful feature really can be just <b>one line of code</b>. </p>
		
		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Filtering in Delphi: Building a Highlighting Property-Based Search]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2510</link>
		<author>Gjalt Vanhouwaert</author>
		<pubDate>Mon, 7 Sep 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		<p>In the previous blog post, we introduced the new <b>TTMSFNCFilterRulesManager</b> and looked at how filtering can go beyond datasets and grid columns.</p>

<p>Instead of only filtering data, the Filter Rules Manager can evaluate properties of Delphi objects and use the result to change other properties or execute application logic.</p>

<p>We also showed the following example:</p>

<p style="text-align:center;"><img src="https://www.tmssoftware.com/site/img/blog/filterseries/filterrulesplannerexample.png" style="width: 828px;" alt="TMS Software Delphi  Components "><br></p>

<p>In this blog post, we&#39;re going to take a closer look at that example and see how it was built.</p>

<p>The application contains a <b>TTMSFNCPlanner</b> with several appointments and a search box at the top.</p>

<p>As the user types, appointments with a matching title keep their original appearance, while appointments that don&#39;t match are dimmed.</p>

<p>There is no dataset filter involved.</p>

<p>Instead, we&#39;re going to filter directly on the <code>Title</code> property of each <b>TTMSFNCPlannerItem</b>.</p><p><br></p>

<h3>Setting Up the Planner</h3>

<p>For this example, the Planner contains a number of appointments spread across the working week.</p>

<p>Adding an appointment is nothing special:</p>

<pre class="delphi" name="code">Item := TMSFNCPlanner1.Items.Add;
Item.Title := ATitle;
Item.Text := AText;
Item.StartTime := AStartTime;
Item.EndTime := AEndTime;
Item.Color := AFill;</pre>

<p>Some examples used in the demo are:</p>

<ul>
<li>Team Standup</li>
<li>Sprint Planning</li>
<li>Architecture Review</li>
<li>Design Review</li>
<li>Product Demo</li>
<li>Training Session</li>
<li>Sprint Retrospective</li>
</ul>

<p>What matters for our rule is that every appointment already has a <code>Title</code> property.</p>

<p>That&#39;s the property we&#39;re going to search.</p><p><br></p>

<h3>Creating the Filter Rules Manager</h3>

<p>We start by creating a <b>TTMSFNCFilterRulesManager</b>:</p>

<pre class="delphi" name="code">FRM := TTMSFNCFilterRulesManager.Create(Self);</pre>

<p>Next, we need to connect the text entered by the user to our rule.</p>

<p>We could recreate the filter expression every time the user types something, but there is a better option: <b>placeholders</b>.</p>

<h3>Adding a Dynamic Search Value</h3>

<p>A placeholder lets a rule retrieve a value from another object when the rule is evaluated.</p>

<p>Our search text comes from a <code>TEdit</code> called <code>SearchEdit</code>.</p>

<p>We register its <code>Text</code> property as a placeholder:</p>

<pre class="delphi" name="code">FRM.AddPlaceholder(
  &#39;Search&#39;,
  SearchEdit,
  &#39;Text&#39;
);</pre>

<p>From this point on, we can use:</p>

<pre class="delphi" name="code">{Search}</pre>

<p>inside our rule.</p>

<p>Whenever the rule is applied, <code>{Search}</code> is replaced with the current value of <code>SearchEdit.Text</code>.</p>

<p>This is an important detail: the value isn&#39;t fixed when the rule is created.</p>

<p>The same rule can therefore remain in place while the user continues typing.</p><p><br></p>

<h3>Creating the Rule</h3>

<p>Now we can create the actual rule:</p>

<pre class="delphi" name="code">FRule1 := FRM.AddRule(
  &#39;SpotlightRule&#39;,
  TMSFNCPlanner1,
  &#39;Items&#39;,
  &#39;[Title] LIKE &#39;&#39;%{Search}%&#39;&#39;&#39;
);</pre>

<p>This small piece of code contains most of the logic for our search.</p>

<p>Let&#39;s break it down.</p><p><br></p>

<h3>1. The Rule Name</h3>

<pre class="delphi" name="code">&#39;SpotlightRule&#39;</pre>

<p>Every rule has a name so it can be identified later.</p><p><br></p>

<h3>2. The Source</h3>

<pre class="delphi" name="code">TMSFNCPlanner1</pre>

<p>This is the object where the Filter Rules Manager starts.</p>

<p>But we don&#39;t want to evaluate the Planner itself. We want to evaluate its appointments.</p>

<p>That&#39;s where the next parameter comes in.</p><p><br></p>

<h3>3. The Items Path</h3>

<pre class="delphi" name="code">&#39;Items&#39;</pre>

<p>The Filter Rules Manager uses this path to retrieve the collection of objects that should be evaluated.</p>

<p>In this example, that means every <b>TTMSFNCPlannerItem</b> inside:</p>

<pre class="delphi" name="code">TMSFNCPlanner1.Items</pre>

<p>The rule is therefore applied to each appointment individually.</p><p><br></p>

<h3>4. The Filter Expression</h3>

<p>Finally, we have the actual condition:</p>

<pre class="delphi" name="code">&#39;[Title] LIKE &#39;&#39;%{Search}%&#39;&#39;&#39;</pre>

<p>This is where things become interesting.</p>

<p><code>Title</code> is not a field from a dataset.</p>

<p>It is the <b>Title property of TTMSFNCPlannerItem</b>.</p>

<p>For every planner item, the Rules Manager retrieves that property and evaluates it using the filter expression.</p>

<p>If the user enters:</p>

<pre class="delphi" name="code">Team</pre>

<p>the effective condition becomes:</p>

<pre class="delphi" name="code">[Title] LIKE &#39;%Team%&#39;</pre>

<p>Appointments such as <code>Team Standup</code> match, while appointments such as <code>Product Demo</code> don&#39;t.</p>

<p>This is the key difference with the filtering examples from earlier in this series.</p>

<p>We&#39;re using the same structured filtering concepts, but now we&#39;re evaluating properties directly on Delphi objects.</p><p><br></p>

<h3>What Should Happen When an Item Matches?</h3>

<p>We now know whether each appointment matches the search.</p>

<p>The next question is what we want to do with that information.</p>

<p>We could hide appointments that don&#39;t match.</p>

<p>But for this example, we want to create more of a <i>spotlight</i> effect.</p>

<p>Matching appointments remain unchanged, while the other appointments are visually dimmed.</p>

<p>For that, we use <code>CallBackAction</code>:</p>

<pre class="delphi" name="code">FRule1.CallBackAction :=
  procedure(
    const ARule: TTMSFNCFilterRule;
    AItem: TObject;
    AIndex: Integer;
    const AMatched: Boolean)
  var
    Item: TTMSFNCPlannerItem;
    Idx: Integer;
  begin
    Item := AItem as TTMSFNCPlannerItem;
    Idx := Item.Index;

    if AMatched then
    begin
      Item.Color := FColors[Idx].Fill;
      Item.StrokeColor := FColors[Idx].Stroke;
      Item.FontColor := FColors[Idx].Font;
      Item.TitleFontColor := FColors[Idx].Font;
      Item.MarkColor := FColors[Idx].Mark;
      Inc(FMatchCount);
    end
    else
    begin
      Item.Color := DimColor(FColors[Idx].Fill);
      Item.StrokeColor := DimColor(FColors[Idx].Stroke);
      Item.FontColor := DimColor(FColors[Idx].Font);
      Item.TitleFontColor := DimColor(FColors[Idx].Font);
      Item.MarkColor := DimColor(FColors[Idx].Mark);
    end;
  end;</pre>

<p>The callback is executed for every item processed by the rule.</p>

<p>The <code>AMatched</code> parameter immediately tells us whether the current planner item matched our <code>Title</code> condition.</p>

<p>When it matches, we restore its original colors.</p>

<p>When it doesn&#39;t, we apply a dimmed version of those colors.</p><p><br></p>

<h3>One Match, Multiple Property Changes</h3>

<p>This example also demonstrates another useful aspect of the Filter Rules Manager.</p>

<p>One condition can affect multiple properties.</p>

<p>For a non-matching appointment, we&#39;re changing:</p>

<ul>
<li><code>Color</code></li>
<li><code>StrokeColor</code></li>
<li><code>FontColor</code></li>
<li><code>TitleFontColor</code></li>
<li><code>MarkColor</code></li>
</ul>

<p>All of these changes are driven by the result of a single condition:</p>

<pre class="delphi" name="code">[Title] LIKE &#39;%{Search}%&#39;</pre>

<p>For this example we use a callback because we want precise control over several appearance properties and we also keep track of the number of matching items.</p>

<p>For simpler cases, the Filter Rules Manager also provides actions that can directly update properties without requiring a callback.</p><p><br></p>

<h3>Making the Search Live</h3>

<p>We have created the rule only once.</p>

<p>To turn it into a live search, all we need to do is reapply it when the text changes.</p>

<pre class="delphi" name="code">procedure TFormFilterRules.SearchEditChangeTracking(
  Sender: TObject);
begin
  UpdateFilter;
end;</pre>

<p>The <code>UpdateFilter</code> method itself remains very small:</p>

<pre class="delphi" name="code">procedure TFormFilterRules.UpdateFilter;
begin
  FMatchCount := 0;

  FRM.ApplyRule(FRule1);

  CountLbl.Text := Format(
    &#39;%d / %d&#39;,
    [FMatchCount, TMSFNCPlanner1.Items.Count]
  );
end;</pre>

<p>Notice what isn&#39;t happening here.</p>

<p>We aren&#39;t:</p>

<ul>
<li>Iterating over the planner items ourselves.</li>
<li>Reading every <code>Title</code> property manually.</li>
<li>Rebuilding the filter expression.</li>
<li>Copying the current search value into the rule.</li>
</ul>

<p>We simply apply the existing rule again.</p>

<p>The Rules Manager resolves <code>{Search}</code> from <code>SearchEdit.Text</code>, iterates over the Planner items, evaluates their <code>Title</code> properties and calls the appropriate action.</p><p><br></p>

<h3>What Happens When the Rule Is Disabled?</h3>

<p>Because our rule changes the appearance of the Planner items, we also need to consider what should happen when the rule itself becomes inactive.</p>

<p>For that, we can provide an <code>InactiveCallBackAction</code>.</p>

<pre class="delphi" name="code">FRule1.InactiveCallBackAction :=
  procedure(
    const ARule: TTMSFNCFilterRule;
    AItem: TObject;
    AIndex: Integer)
  var
    Item: TTMSFNCPlannerItem;
    Idx: Integer;
  begin
    Item := AItem as TTMSFNCPlannerItem;
    Idx := Item.Index;

    Item.Color := FColors[Idx].Fill;
    Item.StrokeColor := FColors[Idx].Stroke;
    Item.FontColor := FColors[Idx].Font;
    Item.TitleFontColor := FColors[Idx].Font;
    Item.MarkColor := FColors[Idx].Mark;
  end;</pre>

<p>When the rule is inactive, every appointment is restored to its original appearance.</p>

<p>This gives us three clear states:</p>

<ul>
<li><b>Match</b> - keep the original appearance.</li>
<li><b>No match</b> - dim the appointment.</li>
<li><b>Rule inactive</b> - restore everything.</li></ul><p><br></p><ul>
</ul>

<h3>The Complete Rule Setup</h3>

<p>If we remove the supporting UI and styling code, the actual rule setup is surprisingly compact:</p>

<pre class="delphi" name="code">FRM := TTMSFNCFilterRulesManager.Create(Self);

FRM.AddPlaceholder(
  &#39;Search&#39;,
  SearchEdit,
  &#39;Text&#39;
);

FRule1 := FRM.AddRule(
  &#39;SpotlightRule&#39;,
  TMSFNCPlanner1,
  &#39;Items&#39;,
  &#39;[Title] LIKE &#39;&#39;%{Search}%&#39;&#39;&#39;
);

FRule1.CallBackAction :=
  procedure(
    const ARule: TTMSFNCFilterRule;
    AItem: TObject;
    AIndex: Integer;
    const AMatched: Boolean)
  begin
    // Apply the matching or non-matching appearance here.
  end;</pre>

<p>And when the search changes:</p>

<pre class="delphi" name="code">FRM.ApplyRule(FRule1);</pre>

<p>That&#39;s the core of the example.</p><p><br></p>

<h3>Not Just a Planner Feature</h3>

<p>Although we&#39;re using <b>TTMSFNCPlanner</b> here, there is nothing Planner-specific about the rule itself.</p>

<p>The important ingredients are:</p>

<ul>
<li>An object that acts as the source.</li>
<li>A path to the objects you want to evaluate.</li>
<li>A property on those objects.</li>
<li>A filter expression.</li>
<li>An action to perform with the result.</li>
</ul>

<p>In this example:</p>

<pre class="delphi" name="code">Source      : TMSFNCPlanner1
Items       : Items
Property    : Title
Condition   : LIKE &#39;%{Search}%&#39;
Action      : Highlight matching appointments</pre>

<p>Change the source, item path and property, and the same approach can be used elsewhere in your application.</p>

<p>You could evaluate properties on visual controls, items in another FNC component, or your own Delphi objects.</p><p><br></p>

<h3>Filtering Without Removing Anything</h3>

<p>This example also shows why the term <i>filtering</i> becomes broader with the Filter Rules Manager.</p>

<p>Traditionally, filtering means that something either remains visible or disappears from the result.</p>

<p>Here, every appointment remains in the Planner.</p>

<p>The filter is simply being used to determine which rule should be applied to each item.</p>

<p>That result could change appearance, visibility, enabled state or another property. Or, as we&#39;ve done here, it can be passed to custom application logic through a callback.</p>

<p>And because the condition can work directly with object properties such as <code>Title</code>, this isn&#39;t limited to traditional data filtering.</p>

<p>That&#39;s where <b>TTMSFNCFilterRulesManager</b> starts to become particularly useful: the filtering engine becomes a way to describe conditions throughout your application, while rules determine what should happen when those conditions are met.</p>
```

		
		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Translate Your Delphi Application with Ease - Introducing TMS FNC Localization BETA]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2513</link>
		<author>Tunde Keller</author>
		<pubDate>Thu, 3 Sep 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		<div><img src="https://www.tmssoftware.com/site/img/blog/tmsfnclocalizationblog.png" style="width: 33%; float: right;" class="note-float-right" alt="TMS Software Delphi  Components tmsfnclocalization">When starting a new application, supporting multiple languages can easily end up somewhere near the bottom of the priority list. And when localization is added later to an existing or even long-running application, suddenly there are captions, messages, dialogs and other texts scattered all over the project that all need to be found, translated and kept in sync.</div><div><br></div><div>How do you collect all those texts? How do you keep translations organized? And how do you apply them to your application? This is exactly the kind of problem we like to solve with easy-to-use components!</div><div><br></div><div>Today, we are very excited to introduce a brand-new product entering BETA: <b>TMS FNC Localization</b>.</div><div><br></div><h4>From a Real-World Need to TMS FNC Localization</h4><div><div>For readers who have been following our blog, the name <b>Erwin Denissen</b> might already sound familiar.</div><div><br></div><div>Erwin is the developer behind <b>FontCreator </b>from High-Logic, which we featured earlier in <a href="https://www.tmssoftware.com/site/blog.asp?post=1292" target="_blank">one of our customer showcase</a> blog posts. As part of his own software development, Erwin also ran into the challenges that come with adding and maintaining localization in an application.</div><div><br></div><div>Not finding a solution that matched the workflow he was looking for, he started building his own.</div><div><br></div><div>What began as a solution for his own needs eventually brought Erwin and TMS Software together. We saw a lot of potential in his work, and from that cooperation the project grew further into the product we are presenting today as <b>TMS FNC Localization</b>.</div><div><br></div><div>We want to explicitly thank Erwin for the work, ideas and experience he has contributed to making this possible!</div><div><br></div><div><h4>Collect, Translate, Apply!</h4><div>At its core, TMS FNC Localization is built around a straightforward workflow: <b>collect </b>the texts, <b>translate </b>them and <b>apply </b>the selected language. Instead of having to build the complete localization infrastructure yourself, you get dedicated components for each step!</div><div><br></div><div><b>Collect</b></div><div><b><br></b></div><div>The first challenge is finding everything that needs to be translated, especially in an existing application. The <b>TTMSFNCLocalizationCollector </b>helps gather localizable content from your application instead of forcing you to manually hunt down every caption and text property.</div><div></div><pre class="delphi" name="code">//Analyze and collect the application - only applies to live forms
TMSFNCLocalizationCollector.AnalyzeApplication;
//Analyze and collect a given form
TMSFNCLocalizationCollector.AnalyzeForm(Self);</pre><div><img src="https://www.tmssoftware.com/site/img/blog/tmsfnclocalizationblog1.png" style="width: 100%;" alt="TMS Software Delphi  Components tmsfnclocalization"><br></div><div><br></div><div><b>Translate</b></div><div><b><br></b></div><div><div>Once the strings have been collected, they need to be managed and translated.</div><div><br></div><div>The <b>TTMSFNCLocalizationEditor</b> provides an easy way to work with the collected texts and the translations for the different languages. Opening the editor at runtime comes with the benefit of being able to see the translation changes immediately in your running application.</div><div><pre class="delphi" name="code">TMSFNCLocalizationEditor.Execute;</pre></div><div>Alternatively, open it at design time from the context menu of the TTMSFNCLocalizationCollector or any TForm. When the editor is triggered at design time, you can also see which forms haven&#39;t been discovered yet!</div><div><br></div><div>What if you don&#39;t want to manually translate every item? Use the <b>optional AI assistance</b> to generate translations and you&#39;ll only need to verify the results! We&#39;ve added support for a variety of AI services that you can use with your API key. These include <b>OpenAI, Claude, Gemini, Grok, Mistral and DeepSeek</b>. If you prefer local models, we have you covered via <b>Ollama and llama.cpp</b>.</div><div><br></div><div>Would you rather leave it to a professional? <b>XLIFF export and import </b>support is available, allowing you to outsource the work to professional translators using their own tools!</div><div><br></div><div>To help keep translations <b>reliable</b>, TMS FNC Localization comes with <b>built-in automatic text validation</b> for format specifiers, placeholders, accelerator keys, line breaks and whitespace, whether translations are entered manually, imported or generated with AI. It supports <b>plural-aware translations</b> as well, allowing the correct wording to be used depending on the quantity being displayed.&nbsp;This is especially useful for languages where pluralization rules go beyond a simple singular-versus-plural distinction.</div><div><br></div><div>On top of editing and verifying the translations, the TTMSFNCLocalizationEditor also lets you <b>manage the list of languages</b> you want to support, along with a <b>set of exclusion rules</b> for what should <b>not</b> be collected. Using the editor also means collection happens automatically, no need for additional code!</div><div><br></div><div>The editor <b>keeps the translation process separate</b> from the application itself. Adding another language or updating an existing translation becomes a matter of maintaining the localization data instead of changing forms or source code.</div><div><br></div><div><img src="https://www.tmssoftware.com/site/img/blog/tmsfnclocalizationblog2.png" style="width: 100%;" alt="TMS Software Delphi  Components tmsfnclocalization"><br></div><div><br></div><div><b>Apply</b></div><div><div><br></div><div>Finally, the <b>TTMSFNCLocalizationLocalizer </b>takes care of applying the selected language to the application! Point it to the folder where the localization files live, and calling the simple <b>TrySetLocale </b>method will immediately switch to the target language. This gives you fine control over when and how a language is set. If you&#39;d rather hand that choice to your users, TMS FNC Localization ships with a ready-made language picker, <b>TTMSFNCLocalizationCombobox</b>, that you can drop straight into your forms!</div><div><br></div><div>Together, these components turn localization into a <b>repeatable workflow</b> instead of a one-off solution. When another language needs to be added later, you can simply repeat the same step again!</div><div><br></div><div><img src="https://www.tmssoftware.com/site/img/blog/tmsfnclocalizationblog3.png" style="width: 100%;" alt="TMS Software Delphi  Components tmsfnclocalization"><br></div><div><br></div><h4>New or Existing, Localization Fits</h4><div><div>Of course, the easiest moment to think about localization is before writing the first line of an application. From the start, you can keep your strings in one place with <b>TTMSFNCLocalizationStringCatalog</b>. That way you can keep everything organized, central and easy to collect.</div><div><br></div><div>That said, one of the goals behind TMS FNC Localization is to make localization approachable <b>not only for new projects, but also for applications that already exist</b>.&nbsp;Localization no longer has to be an all-or-nothing decision made on day one. You can introduce it gradually and <b>grow it as the application grows</b>.</div><div><br></div><h4>Get Started with the BETA</h4><div><b><a href="https://www.tmssoftware.com/site/tmsallaccess.asp" target="_blank">TMS ALL-ACCESS</a></b> users can start experimenting with TMS FNC Localization <b>today</b>! The BETA is available through <a href="https://doc.tmssoftware.com/smartsetup/" target="_blank">TMS Smart Setup</a>, making it easy to add the product to your development environment and keep up with new BETA versions as they become available.</div><div><br></div><div>To help you get started, we have included demos with the installation and prepared documentation covering both the component API and the overall workflow.&nbsp;<a href="https://doc.tmssoftware.com/fnc/localization/" target="_blank">Click here</a> to open it on our new TMS Documentation site, the <a href="https://tmssoftware.com/site/blog.asp?post=2501" target="_blank">new home for all TMS product documentation</a>.</div><div><br></div><h4>Help Us Shape TMS FNC Localization</h4><div><div>Localization requirements can differ significantly between applications, and feedback from developers using the product on real projects is <b>extremely valuable</b>.</div><div><br></div><div>If you are an ALL-ACCESS user, we encourage you to <b>put it to work</b>! Try it on a new application or, even better, try it on that older project that someone has just decided needs to conquer three new markets. Let us know what works well, what could work better and what is missing from your localization workflow!</div><div><br></div><div>Feedback and findings can be shared with us through the dedicated <a href="https://support.tmssoftware.com/c/fnc/tms-fnc-localization/131" target="_blank">TMS Support Center category</a>. We are looking forward to seeing what you build with it and in how many languages!</div></div></div></div></div></div></div>
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Make Your Data Speak: Conditional Formatting in TMS FNC Data Grid 2.0]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2514</link>
		<author>Pieter Scheldeman</author>
		<pubDate>Wed, 2 Sep 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		<p><span style="background-color: darkgray; color: white; padding: 4px 8px; text-align: center; border-radius: 5px; font-size: 1.5em;">TMS FNC UI Pack 7.3</span></p><p><span style="background-color: darkorange; color: white; padding: 4px 8px; text-align: center; border-radius: 5px; font-size: 1.5em;">TMS FNC Data Grid 2.0</span></p>

<h4 style="color: rgb(0, 0, 0);">Intro</h4>
<p>A grid can contain every answer and still make the important ones hard to find. Which sales representatives are leading the quarter? Which targets are at risk? Where is growth slowing down? Traditionally, answering those questions visually meant writing custom drawing events and maintaining the same business logic in several places.</p>

<p>With <strong>TMS FNC UI Pack 7.3</strong>, we are releasing <strong>TMS FNC Data Grid 2.0</strong> and introducing a new declarative conditional-formatting engine for <code>TTMSFNCDataGrid</code>.</p>

<p>Conditional formatting lets the data decide how a cell or row should look. Add rules for values, text, dates, rankings, averages, duplicates or complete expressions, and the grid automatically applies fills, font styles, color scales, data bars and icon sets while it renders.</p>

<p><img src="https://www.tmssoftware.com/site/img/tmsfncuipack_7_3/conditional-formatting-grid.png" style="max-width: 100%;" data-filename="conditional-formatting-grid.png" alt="TMS FNC Data Grid 2.0 sales dashboard with color scales, data bars, icon sets and status highlights"></p>

<h4>From Raw Values to Instant Insight</h4>
<p>The new <code>ConditionalFormatting</code> property owns a collection of rules. Each rule defines three things:</p>

<ul>
<li><p>where it applies;</p></li>
<li><p>when it matches;</p></li>
<li><p>how the matching cell or row should be presented.</p></li>
</ul>

<p>Rules are evaluated automatically as the grid paints, and remain effective when users scroll, sort or filter. There is no need to repeat the same decisions in <code>OnGetCellLayout</code> or a custom drawing event.</p>

<p>The engine includes ten rule families:</p>

<ul>
<li><p>cell-value comparisons;</p></li>
<li><p>text matching;</p></li>
<li><p>date conditions;</p></li>
<li><p>top and bottom rankings;</p></li>
<li><p>above- and below-average rules;</p></li>
<li><p>unique and duplicate values;</p></li>
<li><p>two- and three-color scales;</p></li>
<li><p>data bars;</p></li>
<li><p>icon sets;</p></li>
<li><p>custom expressions.</p></li>
</ul>

<h4>Start with One Line</h4>
<p>The convenience methods make common formatting rules deliberately small. To highlight every value below 100 in the <code>Quota %</code> column:</p>

<pre class="delphi" name="code">with TMSFNCDataGrid1.ConditionalFormatting.AddCellValueRule(
  COL_QUOTA, gfcLess, &#39;100&#39;) do
  Appearance.Font.Color := gcRed;</pre>

<p><code>ConditionalFormatting.Enabled</code> is enabled by default, so the rule takes effect immediately.</p>

<h4>Color Scales: See the Range</h4>
<p>A color scale turns a numeric column into a compact heat map. The lowest, middle and highest values receive the selected colors, with the colors in between calculated automatically.</p>

<pre class="delphi" name="code">TMSFNCDataGrid1.ConditionalFormatting.AddColorScale(
  COL_SALES,
  gcRed,
  gcYellow,
  gcLimegreen);</pre>

<p>This is useful for sales, margins, temperatures, scores or any other measurement where relative position matters as much as the number itself.</p>

<h4>Data Bars and Icon Sets: Compare at a Glance</h4>
<p>Data bars show magnitude directly inside a cell while keeping the original value visible. Icon sets divide values into recognizable bands using arrows, traffic lights, stars, flags and rating symbols.</p>

<pre class="delphi" name="code">// Compare quota attainment with proportional bars.
TMSFNCDataGrid1.ConditionalFormatting.AddDataBar(
  COL_QUOTA, gcDodgerblue);

// Show the direction of growth with traffic lights.
TMSFNCDataGrid1.ConditionalFormatting.AddIconSet(
  COL_GROWTH, gisTrafficLights3);

// Turn customer satisfaction into a five-star signal.
TMSFNCDataGrid1.ConditionalFormatting.AddIconSet(
  COL_CSAT, gisStars5);</pre>

<p>The minimum and maximum values can be detected automatically or defined as numbers, percentages or percentiles. Values can remain visible next to the bar or icon, or the visual can stand on its own.</p>

<h4>Highlight the Rows That Matter</h4>
<p>Some conditions should draw attention to a complete record. <code>ApplyToEntireRow</code> extends the appearance of a matching rule across the row while the condition itself remains tied to one column.</p>

<pre class="delphi" name="code">with TMSFNCDataGrid1.ConditionalFormatting.AddTopBottomRule(
  COL_SALES, rkTop, 5) do
begin
  Priority := 10;
  ApplyToEntireRow := True;
  Appearance.Fill.Color := gcLightgoldenrodyellow;
  Appearance.Font.Style := [TFontStyle.fsBold];
end;</pre>

<p>This example highlights the five best sales results without calculating or updating a threshold manually.</p>

<h4>Expressions Across Columns</h4>
<p>For decisions that depend on more than one value, expression rules use the same expression language as the Data Grid filter builder. Column names can be combined into a readable condition that is evaluated for each row.</p>

<pre class="delphi" name="code">with TMSFNCDataGrid1.ConditionalFormatting.AddExpressionRule(
  &#39;([Growth %] &lt; 0) AND ([Quota %] &gt;= 100)&#39;) do
begin
  ApplyToEntireRow := True;
  Appearance.Fill.Color := gcMistyrose;
  Appearance.Font.Color := gcRed;
end;</pre>

<p>This makes it easy to reveal combinations that deserve attention: growth declining despite reaching quota, stock falling below the reorder level, an overdue task that is still open, or a customer whose health indicators disagree.</p>

<h4>Combining Rules with Priority</h4>
<p>Conditional formatting becomes especially useful when several rules work together. A sales column can use a color scale, a quota column can show data bars, growth can display an icon set, and status can apply text-based colors&#151;all in the same grid.</p>

<p>When rules overlap, a lower <code>Priority</code> number has higher precedence. The first matching rule claims the appearance aspects it sets, while later rules can still contribute aspects that remain unset. <code>StopIfTrue</code> can stop evaluation of the rules that follow it.</p>

<p>This gives you predictable layering without moving presentation logic into paint handlers.</p>

<h4>A Visual Editor for VCL and FireMonkey</h4>
<p>Not every formatting rule needs to start in code. TMS FNC Data Grid 2.0 includes a visual conditional-formatting editor for VCL and FireMonkey at design time.</p>

<p><img src="https://www.tmssoftware.com/site/img/tmsfncuipack_7_3/conditional-formatting-editor.png" style="max-width: 100%;" data-filename="conditional-formatting-editor.png" alt="Visual conditional-formatting editor for TMS FNC Data Grid 2.0 with a populated rule list and live preview"></p>

<p>The editor lets you:</p>

<ul>
<li><p>add, duplicate, delete and reorder rules;</p></li>
<li><p>choose a rule type and its scope;</p></li>
<li><p>select columns by name;</p></li>
<li><p>configure comparisons, values and expressions;</p></li>
<li><p>select fills, text colors, borders and font styles;</p></li>
<li><p>configure color-scale bounds, data bars and icon sets;</p></li>
<li><p>preview the selected rule before accepting it.</p></li>
</ul>

<p>The rule collection is stored with the grid, so design-time configuration becomes part of the form just like the grid&#39;s other properties. Desktop applications can also open the same workflow at runtime with <code>ShowConditionalFormattingEditor</code>.</p>

<h4>Cross-Framework by Design</h4>
<p>The conditional-formatting engine is available in <code>TTMSFNCDataGrid</code> for <strong>VCL</strong>, <strong>FireMonkey</strong> and <strong>TMS WEB Core</strong>. The visual editor is available for VCL and FireMonkey, while the same rule collection and fluent helper methods can be configured in code on all three frameworks.</p>

<p>That means the formatting logic can travel with your application instead of being rewritten for each UI framework.</p>

<h4>Installing TMS FNC UI Pack 7.3</h4>
<p>TMS FNC UI Pack 7.3 is available through the modern Smart Setup based installation workflow:</p>

<pre class="delphi" name="code">tms self-update
tms install tms.fnc.*</pre>

<p>If you are moving from the classic installers, uninstall older TMS FNC UI Pack installations from the previous setup flow first. This keeps IDE paths and package registrations clean.</p>

<p>You can learn more about <a href="https://www.tmssoftware.com/site/tmsfncuipack.asp" target="_blank">TMS FNC UI Pack</a> on the product page.</p>

<h4>Conclusion</h4>
<p>Conditional formatting in TMS FNC Data Grid 2.0 turns visual data analysis into a reusable part of the grid configuration. From one-line threshold rules to color scales, rankings, icon sets and expressions across multiple columns, the new engine makes important values stand out without custom painting code.</p>

<p>Together with the visual editor and the fluent runtime API, TMS FNC UI Pack 7.3 gives developers a faster way to build grids that do more than display data&#151;they explain it.</p>

		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Embarcadero Conference Brazil - ECON26]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2512</link>
		<author>Aaron Decramer</author>
		<pubDate>Thu, 20 Aug 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
	<div style="width: 100%; color: #1a1a1a; line-height: 1.6;">

  
   	<p><span style="background-color: #12233f; color: white; padding: 6px 12px; text-align: center; border-radius: 6px; font-size: 1.35em;">Event</span>   
      
      
      
  </p><p style="margin:0 0 18px 0;"><img src="https://www.tmssoftware.com/site/img/blog/econ26.png" style="" alt="TMS Software Delphi  Components "><br></p><p style="margin:0 0 18px 0;">On September 1st, 2026, the Delphi community in Brazil comes together at the Amcham Business Center in S&#227;o Paulo for ECON26. If you spend your days developing, modernizing, or maintaining Delphi applications, or if you&#39;re the one making the calls on enterprise systems and technology direction, this is a day worth setting aside.</p>

  <p style="margin:0 0 32px 0;">The program runs across six stages, with five tracks in the main arena and a stage dedicated entirely to DevOps Best Practices. The format is built to feel less like a lecture hall and more like a day of exchange, with a gamified structure that carries you from one session to the next, and plenty of room to meet people from across the country working on the same kinds of problems you are.</p>

  <h3 style="font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: #009EE2; margin: 0 0 16px 0;">Sessions with TMS Software Products</h3>

  <!-- Session card 1 -->
  <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; background-color:#ffffff; border:1px solid #e6e6e6; border-radius:10px; margin:0 0 20px 0;">
    <tbody><tr>
      <td style="padding:24px;">

        <span style="display:inline-block; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.5px; color:#009EE2; background-color:#e6f6ff; padding:4px 10px; border-radius:100px; margin-bottom:12px;">AI &amp; Innovation Track</span>

        <div style="font-size:18px; font-weight:700; line-height:1.35; margin:4px 0 14px 0; color:#111;">AI Beyond Prompts: Understanding MCP, Tool Calling, Subagents, Skills, Slash Commands, Spec-Driven Development, Harness, and More</div>

        <table role="presentation" cellpadding="0" cellspacing="0" style="margin:0 0 14px 0;">
          <tbody><tr>
                  <td style="padding-right:12px;"><img src="https://www.tmssoftware.com/site/img/blog/cesar-cardoso.jpg" alt="C&#233;sar Cardoso" width="44" height="44" style="width:44px;height:44px;border-radius:50%;display:block;object-fit:cover;"></td>
            <td style="font-size:14px; color:#444;"><strong>C&#233;sar Cardoso</strong><br><span style="color:#888;">Code4Delphi</span></td>
          </tr>
        </tbody></table>

        <p style="margin:0 0 18px 0; font-size:14.5px; color:#333;">With the evolution of Artificial Intelligence, several terms, techniques, and approaches are emerging, such as MCP, Tool/Function Calling, Skills, Agents, Subagents, Slash Commands, Spec-Driven Development, Harness, among others. In this presentation, we will unravel these concepts, understanding what they are, what they are for, and where each one fits into the modern AI ecosystem. We will also see practical examples of how these techniques work and how they can be used in everyday life, both to develop better and faster and to create smarter and more integrated AI applications, always connecting everything to the Delphi ecosystem.</p>

        <div style="border-top:1px solid #eee; padding-top:18px;">
          <div style="font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:0.6px; color:#999; margin:0 0 8px 2px;">Featured in this session</div>
          <a href="https://www.tmssoftware.com/site/tmsaistudio.asp" style="text-decoration:none; color:inherit; display:block;">
            <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; background-color:#ffffff; border:1px solid #d8edf9; border-radius:10px; box-shadow:0 1px 3px rgba(0,158,226,0.08);">
              <tbody><tr>
                <td style="padding:14px; width:56px;">
                  <table role="presentation" cellpadding="0" cellspacing="0" style="width:48px; height:48px; background-color:#f0f9ff; border-radius:10px;">
                    <tbody><tr><td style="width:48px; height:48px; text-align:center; vertical-align:middle;"><img src="https://www.tmssoftware.com/site/img/blog/tms-ai-studio-icon.png" alt="TMS AI Studio" width="30" height="30" style="width:30px;height:30px;border-radius:4px;display:inline-block;object-fit:cover;vertical-align:middle;"></td></tr>
                  </tbody></table>
                </td>
                <td style="padding:14px 4px; vertical-align:middle;">
                  <div style="font-size:15px; font-weight:700; color:#111;">TMS AI Studio</div>
                </td>
                <td style="padding:14px 16px; text-align:right; vertical-align:middle; width:36px;">
                  <table role="presentation" cellpadding="0" cellspacing="0" style="width:28px; height:28px; background-color:#009EE2; border-radius:50%;"><tbody><tr><td style="width:28px; height:28px; text-align:center; vertical-align:middle; line-height:28px; color:#ffffff; font-size:15px;">&#39;</td></tr></tbody></table>
                </td>
              </tr>
            </tbody></table>
          </a>
        </div>

      </td>
    </tr>
  </tbody></table>

  <!-- Session card 2 -->
  <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; background-color:#ffffff; border:1px solid #e6e6e6; border-radius:10px; margin:0 0 32px 0;">
    <tbody><tr>
      <td style="padding:24px;">

        <span style="display:inline-block; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:0.5px; color:#009EE2; background-color:#e6f6ff; padding:4px 10px; border-radius:100px; margin-bottom:12px;">Modernization &amp; Database Track</span>

        <div style="font-size:18px; font-weight:700; line-height:1.35; margin:4px 0 14px 0; color:#111;">Dependencies, Versioning, and Builds: A New Paradigm</div>

        <table role="presentation" cellpadding="0" cellspacing="0" style="margin:0 0 14px 0;">
          <tbody><tr>
            <td style="padding-right:12px;"><img src="https://www.tmssoftware.com//site/img/blog/wagner-landgraf.jpg" alt="Wagner Landgraf" width="44" height="44" style="width:44px;height:44px;border-radius:50%;display:block;object-fit:cover;"></td>
            <td style="font-size:14px; color:#444;"><strong>Wagner Landgraf</strong><br><span style="color:#888;">TMS Software</span></td>
          </tr>
        </tbody></table>

        <p style="margin:0 0 18px 0; font-size:14.5px; color:#333;">Modern Delphi projects require more than just writing code. Managing dependencies, controlling versions, and automating builds has become fundamental to increasing productivity and ensuring reproducible environments. In this talk, we&#39;ll see how to modernize the Delphi development workflow through dependency automation, versioning, and build processes, both on workstations and in CI/CD environments. A practical approach to reduce manual configurations, avoid environment problems, and make your projects easier to maintain and evolve.</p>

        <div style="border-top:1px solid #eee; padding-top:18px;">
          <div style="font-size:10.5px; font-weight:700; text-transform:uppercase; letter-spacing:0.6px; color:#999; margin:0 0 8px 2px;">Featured in this session</div>
          <a href="https://www.tmssoftware.com/site/products.asp?t=dt" style="text-decoration:none; color:inherit; display:block;">
            <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; background-color:#ffffff; border:1px solid #d8edf9; border-radius:10px; box-shadow:0 1px 3px rgba(0,158,226,0.08);">
              <tbody><tr>
                <td style="padding:14px; width:56px;">
                  <table role="presentation" cellpadding="0" cellspacing="0" style="width:48px; height:48px; background-color:#f0f9ff; border-radius:10px;">
                    <tbody><tr><td style="width:48px; height:48px; text-align:center; vertical-align:middle;"><img src="https://www.tmssoftware.com/site/img/blog/tms-biz-icon.png" alt="TMS BIZ" width="30" height="30" style="width:30px;height:30px;border-radius:4px;display:inline-block;object-fit:cover;vertical-align:middle;"></td></tr>
                  </tbody></table>
                </td>
                <td style="padding:14px 4px; vertical-align:middle;">
                  <div style="font-size:15px; font-weight:700; color:#111;">TMS BIZ products</div>
                </td>
                <td style="padding:14px 16px; text-align:right; vertical-align:middle; width:36px;">
                  <table role="presentation" cellpadding="0" cellspacing="0" style="width:28px; height:28px; background-color:#009EE2; border-radius:50%;"><tbody><tr><td style="width:28px; height:28px; text-align:center; vertical-align:middle; line-height:28px; color:#ffffff; font-size:15px;">&#39;</td></tr></tbody></table>
                </td>
              </tr>
            </tbody></table>
          </a>
        </div>

      </td>
    </tr>
  </tbody></table>

  <h3 style="font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: #009EE2; margin: 36px 0 16px 0;">More Info &amp; Tickets</h3>

  <p style="margin:0 0 20px 0;">Whether you&#39;re coming for the AI talks, the DevOps track, or simply to spend a day with people who understand exactly what you&#39;re working on, ECON26 is shaping up to be worth the trip.</p>

  <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; background-color:#ffffff; border:1px solid #e6e6e6; border-radius:10px; margin:0 0 20px 0;">
    <tbody><tr>
      <td style="padding:22px 24px;">
        <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;">
          <tbody><tr>
            <td style="padding:0 0 16px 0; border-bottom:1px solid #f0f0f0;">
              <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;">
                <tbody><tr>
                  <td style="width:36px; vertical-align:middle;">
                    <table role="presentation" cellpadding="0" cellspacing="0" style="width:32px; height:32px; background-color:#e6f6ff; border-radius:50%;">
                      <tbody><tr><td style="width:32px; height:32px; text-align:center; vertical-align:middle; font-size:15px;">...</td></tr>
                    </tbody></table>
                  </td>
                  <td style="padding-left:12px; vertical-align:middle; font-size:14.5px; color:#222;"><strong>September 1st, 2026</strong></td>
                </tr>
              </tbody></table>
            </td>
          </tr>
          <tr>
            <td style="padding:16px 0; border-bottom:1px solid #f0f0f0;">
              <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;">
                <tbody><tr>
                  <td style="width:36px; vertical-align:middle;">
                    <table role="presentation" cellpadding="0" cellspacing="0" style="width:32px; height:32px; background-color:#e6f6ff; border-radius:50%;">
                      <tbody><tr><td style="width:32px; height:32px; text-align:center; vertical-align:middle; font-size:15px;">📍</td></tr>
                    </tbody></table>
                  </td>
                  <td style="padding-left:12px; vertical-align:middle; font-size:14.5px; color:#222;"><strong>Amcham Business Center, S&#227;o Paulo</strong></td>
                </tr>
              </tbody></table>
            </td>
          </tr>
          <tr>
            <td style="padding:16px 0 0 0;">
              <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;">
                <tbody><tr>
                  <td style="width:36px; vertical-align:middle;">
                    <table role="presentation" cellpadding="0" cellspacing="0" style="width:32px; height:32px; background-color:#e6f6ff; border-radius:50%;">
                      <tbody><tr><td style="width:32px; height:32px; text-align:center; vertical-align:middle; font-size:15px;">🎧</td></tr>
                    </tbody></table>
                  </td>
                  <td style="padding-left:12px; vertical-align:middle; font-size:14.5px; color:#222;"><strong>6 stages &#183; 40+ lectures &#183; 9 hours of content</strong></td>
                </tr>
              </tbody></table>
            </td>
          </tr>
        </tbody></table>
      </td>
    </tr>
  </tbody></table>

  <table role="presentation" cellpadding="0" cellspacing="0" style="width:100%; background: linear-gradient(135deg, #12233f 0%, #234a88 100%); border-radius:12px; margin:0 0 8px 0;">
    <tbody><tr>
      <td style="padding:28px; text-align:center;">
        <p style="margin:0 0 6px 0; color:#ffffff; font-size:19px; font-weight:700;">Get your ticket for ECON26</p>
        <p style="margin:0 0 20px 0; color:#c9dcf5; font-size:14px;">Full program, speaker list, and registration on the official site.</p>
        <a href="https://embarcaderoconference.com.br/" style="display:inline-block; background-color:#ffffff; color:#12233f; text-decoration:none; padding:13px 32px; border-radius:6px; font-weight:700; font-size:15px;">Tickets &amp; Full Program &#39;</a>
      </td>
    </tr>
  </tbody></table>

</div>
	
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Filtering in Delphi: From Filtering Data to Filtering Properties]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2508</link>
		<author>Gjalt Vanhouwaert</author>
		<pubDate>Thu, 20 Aug 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		
		<p>Throughout this series, we&#39;ve looked at different ways of building and applying filters.</p>

<p>We started with the <b>TTMSFNCFilterBuilder</b>, moved on to visual filter dialogs, and then used <b>TTMSFNCFilterView</b> to let users build filters naturally through controls in the application.</p>

<p>Until now, the result of a filter was mainly used to answer one question:</p>

<p><b>Does this item match the filter?</b></p>

<p>With the new <b>TTMSFNCFilterRulesManager</b>, we can take that answer and do something with it.</p>

<p>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.</p><p style="text-align: center; "><img src="https://www.tmssoftware.com/site/img/blog/FilterSeries/FilterRulesPlannerExample.png" style="width: 828px;" alt="TMS Software Delphi  Components tmsfnccore"><br></p>

<h3>From Filters to Rules</h3>

<p>The easiest way to understand the Filter Rules Manager is to look at a rule as three parts:</p>

<ul>
<li><b>Where?</b> - Which objects should be evaluated?</li>
<li><b>When?</b> - Which filter condition should match?</li>
<li><b>What?</b> - What should happen when it matches?</li>
</ul>

<p>For example:</p>

<pre class="delphi" name="code">For all employees
where Country = &#39;Belgium&#39;
set Visible = True</pre>

<p>Or:</p>

<pre class="delphi" name="code">For all controls
where Name starts with &#39;btn&#39;
set Visible = False</pre>

<p>The filter itself is still handled by the same filtering logic we&#39;ve introduced throughout this series.</p>

<p>The difference is that we now attach one or more <b>actions</b> to the result.</p><p style="text-align: center; "><img src="https://www.tmssoftware.com/site/img/blog/FilterSeries/FilterRulesControlExample.png" style="width: 80%;" alt="TMS Software Delphi  Components tmsfnccore"><br></p>

<h3>A First Rule</h3>

<p>Let&#39;s start with a small example.</p>

<p>Suppose we have a panel containing several controls and only want controls whose name starts with <code>btn</code> to be visible.</p>

<p>With the Filter Rules Manager, this can be expressed in one rule:</p>

<pre class="delphi" name="code">mgr.AddRuleStartsWith(
  &#39;ShowActionButtons&#39;,
  pnlControls,
  &#39;Controls[*]&#39;,
  &#39;Name&#39;,
  &#39;btn&#39;,
  False
).AddVisibilityAction;</pre>

<p>There is quite a lot happening in only a few lines, so let&#39;s break it down.</p><p><br></p>

<h3>The Source</h3>

<p>Every rule starts with a <b>Source</b>.</p>

<p>The source is the object from which the Filter Rules Manager starts looking for the items it should process.</p>

<p>In our example, that is:</p>

<pre class="delphi" name="code">pnlControls</pre>

<p>But the source itself does not necessarily have to be the object we want to change.</p>

<p>Instead, we can navigate from the source to other objects in the source using an <b>ItemsPath</b>.</p><p><br></p>

<h3>The ItemsPath</h3>

<p>For our panel example, the ItemsPath is:</p>

<pre class="delphi" name="code">Controls[*]</pre>

<p>This tells the Filter Rules Manager to process all controls belonging to the panel.</p>

<p>The <code>*</code> indicates that every item in the collection should be evaluated.</p>

<p>So our rule now effectively says:</p>

<pre class="delphi" name="code">For every control inside pnlControls...</pre>

<p>If the ItemsPath is left empty, the rule can also be applied directly to the Source itself.</p>

<p>This makes rules useful for both individual objects and collections of objects.</p><p><br></p>

<h3>The Filter</h3>

<p>Next, we need to decide when the rule matches.</p>

<p>In our example, we evaluate the <code>Name</code> property:</p>

<pre class="delphi" name="code">Name starts with &#39;btn&#39;</pre>

<p>Internally, the rule uses <b>TTMSFNCFilterBuilder</b> to evaluate this expression.</p>

<p>This is important because it means the Filter Rules Manager isn&#39;t introducing another expression language.</p>

<p>The same operators and structured filtering concepts from the earlier parts of this series are reused here.</p>

<p>You can create a rule using one of the helper methods, as we did above, or provide filter text directly.</p>

<pre class="delphi" name="code">Rule.FilterText := &#39;Name LIKE &#39;&#39;btn%&#39;&#39;&#39;;</pre>

<p>More complex expressions can also be used, including combinations of multiple conditions.</p><p><br></p>

<h3>The Action</h3>

<p>So far, we know which objects we are processing and which ones match.</p>

<p>Now comes the new part: <b>what should happen?</b></p>

<p>In our example, we added:</p>

<pre class="delphi" name="code">AddVisibilityAction</pre>

<p>This changes the <code>Visible</code> property depending on whether the item matches the filter.</p>

<p>A matching control becomes visible, while a non-matching control becomes hidden.</p>

<p>The complete rule can therefore be read almost as a sentence:</p>

<pre class="delphi" name="code">mgr.AddRuleStartsWith(
  &#39;ShowActionButtons&#39;,
  pnlControls,
  &#39;Controls[*]&#39;,
  &#39;Name&#39;,
  &#39;btn&#39;,
  False
).AddVisibilityAction;</pre>

<p><i>For all controls on this panel, find the controls whose Name starts with "btn", and set their visibility accordingly.</i></p><p><i><br></i></p>

<h3>Actions Are Not Limited to Visibility</h3>

<p><code>AddVisibilityAction</code> is a convenient shortcut, but actions are not limited to the <code>Visible</code> property.</p>

<p>An action can target another property using an RTTI path.</p>

<p>Conceptually, an action consists of:</p>

<ul>
<li>A target property</li>
<li>A value when the filter matches</li>
<li>Optionally, a value when it doesn&#39;t match</li>
<li>Optionally, a value when the rule is inactive</li>
</ul>

<p>For example:</p>

<pre class="delphi" name="code">Rule.AddAction(
  &#39;Enabled&#39;,
  True,
  False
);</pre>

<p>A matching item will have <code>Enabled</code> set to <code>True</code>, while a non-matching item will have it set to <code>False</code>.</p>

<p>There are also convenience methods for common scenarios:</p>

<pre class="delphi" name="code">Rule.AddVisibilityAction;
Rule.AddEnableAction;
Rule.AddDisableAction;</pre>

<p>And because the target is a property path, actions can go beyond simple top-level properties.</p>

<p>This opens the door to changing appearance and other properties based on filter conditions as well.</p><p><br></p>

<h3>Multiple Actions on One Rule</h3>

<p>A rule isn&#39;t limited to one action either.</p>

<p>Once an item matches, multiple properties can be changed as part of the same rule.</p>

<p>For example, a rule could make an item visible and enable it at the same time:</p>

<pre class="delphi" name="code">Rule
  .AddVisibilityAction
  .AddEnableAction;</pre>

<p>This keeps the condition in one place.</p>

<p>You don&#39;t need to evaluate the same expression several times just because multiple things should happen when it matches.</p><p><br></p>

<h3>Rules Can Be Active or Inactive</h3>

<p>Every rule has an <code>Active</code> property.</p>

<p>This gives you an easy way to switch behavior on or off.</p>

<pre class="delphi" name="code">mgr.Rules[0].Active := not mgr.Rules[0].Active;
mgr.Rules[0].Apply;</pre>

<p>When a rule is active, its filter is evaluated and the match or no-match actions are applied.</p>

<p>When it is inactive, the rule can use an <b>InactiveValue</b> instead.</p>

<p>This is useful when disabling a rule should also restore the objects to a known state.</p>

<p>So an action can effectively have three outcomes:</p>

<ul>
<li><b>Match</b> - the item matches the filter.</li>
<li><b>No match</b> - the item doesn&#39;t match.</li>
<li><b>Inactive</b> - the rule itself is disabled.</li></ul><p><br></p><ul>
</ul>

<h3>Rules on Your Own Objects</h3>

<p>The Filter Rules Manager is not limited to visual FNC controls.</p>

<p>One of the more interesting use cases is applying rules to your own Delphi objects.</p>

<p>Suppose we have an employee object:</p>

<pre class="delphi" name="code">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;</pre>

<p>We can then create a rule that evaluates these objects and writes the result directly to their <code>Visible</code> property.</p>

<pre class="delphi" name="code">FManager
  .AddRuleStartsWith(
    &#39;NameFilter&#39;,
    FEmployees,
    &#39;Items[0..*]&#39;,
    &#39;Name&#39;,
    &#39;A&#39;
  )
  .AddVisibilityAction;</pre>

<p>When the rule is applied, the manager iterates over the employees, evaluates the <code>Name</code> property and updates <code>Visible</code>.</p>

<p>Your application can then decide how that property is used.</p>

<p>A grid could display only visible employees, for example, without the Filter Rules Manager needing to know anything about that grid.</p><p><br></p>

<h3>Dynamic Rules with Placeholders</h3>

<p>So far, the values in our rules have been fixed.</p>

<p>But application rules are often based on values that change at runtime.</p>

<p>This is where <b>placeholders</b> become useful.</p>

<p>A placeholder connects part of a rule to a property on another object.</p>

<p>For example, suppose we have a search edit:</p>

<pre class="delphi" name="code">FManager.AddPlaceholder(
  &#39;SearchText&#39;,
  EditSearch,
  &#39;Text&#39;
);</pre>

<p>We can then use that value inside the rule:</p>

<pre class="delphi" name="code">{SearchText}</pre>

<p>Every time the rule is applied, the current value of <code>EditSearch.Text</code> is used.</p>

<p>This means the rule doesn&#39;t have to be recreated whenever the search text changes.</p><p><br></p>

<h3>Even the Field Can Be Dynamic</h3>

<p>Placeholders are not limited to filter values.</p>

<p>They can also be used in other parts of the rule, including the field being filtered.</p>

<p>For example, imagine a ComboBox where the user selects whether to search by Name, Company, City or Country.</p>

<p>We can first retrieve the selected index:</p>

<pre class="delphi" name="code">FManager.AddPlaceholder(
  &#39;SearchFieldComboIndex&#39;,
  SearchFieldCombo,
  &#39;ItemIndex&#39;
);</pre>

<p>Then use that index to retrieve the selected field:</p>

<pre class="delphi" name="code">FManager.AddPlaceholder(
  &#39;FilterField&#39;,
  SearchFieldCombo,
  &#39;Items[{SearchFieldComboIndex}]&#39;
);</pre>

<p>And finally use both placeholders in the rule:</p>

<pre class="delphi" name="code">FManager
  .AddRuleStartsWith(
    &#39;EmployeeFilter&#39;,
    FEmployees,
    &#39;Items[0..*]&#39;,
    &#39;{FilterField}&#39;,
    &#39;{SearchText}&#39;
  )
  .AddVisibilityAction;</pre>

<p>The same rule can now search different properties without being rebuilt.</p>

<p>Change the ComboBox, enter another search value, call <code>Apply</code>, and the current values are resolved automatically.</p><p><br></p>

<h3>Applying the Rules</h3>

<p>Once the rules have been configured, they can be applied through the manager:</p>

<pre class="delphi" name="code">FManager.Apply;</pre>

<p>The manager processes its rules and, for each rule:</p>

<ul>
<li>Resolves the source and item path.</li>
<li>Resolves any placeholders.</li>
<li>Builds the filter.</li>
<li>Evaluates the filter against every item.</li>
<li>Determines whether the item matches.</li>
<li>Applies the corresponding actions.</li>
</ul>

<p>A rule can also be applied individually:</p>

<pre class="delphi" name="code">FManager.Rules[0].Apply;</pre>

<p>This is useful when only one particular rule needs to be refreshed.</p><p><br></p>

<h3>What If an Action Is Not a Property?</h3>

<p>Not every action can be expressed as setting a property.</p>

<p>Sometimes you need to execute application-specific code.</p>

<p>For those situations, rules also support callbacks.</p>

<p>The callback receives the rule, the current item, its index and whether it matched.</p>

<p>This gives you the structured rule evaluation while leaving the final behavior completely up to your application.</p>

<p>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.</p>

<p>For most common cases, however, property actions keep the implementation much simpler and avoid unnecessary event handlers.</p><p><br></p>

<h3>Counting Matches Without Applying Actions</h3>

<p>Sometimes you only want to know how many items currently match a rule.</p>

<p>For that, a rule provides <code>CountMatchingItems</code>.</p>

<pre class="delphi" name="code">Count := Rule.CountMatchingItems;</pre>

<p>This evaluates the filter without writing the action values to the objects.</p>

<p>It can be useful for previews, counters or showing the user how many items a rule will affect before applying it.</p><p><br></p>

<h3>Why a Filter Rules Manager?</h3>

<p>Of course, all of this could also be written manually.</p>

<p>You could iterate over a collection, write several <code>if</code> statements, change properties and repeat that logic whenever the application state changes.</p>

<p>But as the number of conditions grows, that logic quickly becomes spread throughout the application.</p>

<p>The Filter Rules Manager brings those pieces together:</p>

<ul>
<li>The condition is represented by a filter.</li>
<li>The objects to evaluate are defined by a source and item path.</li>
<li>The result is connected to one or more actions.</li>
<li>Runtime values can be supplied through placeholders.</li>
<li>The complete rule can be enabled, disabled and reapplied.</li>
</ul>

<p>Instead of writing:</p>

<pre class="delphi" name="code">if Employee.Country = &#39;Belgium&#39; then
begin
  Employee.Visible := True;
  Employee.Enabled := True;
end
else
begin
  Employee.Visible := False;
  Employee.Enabled := False;
end;</pre>

<p>The condition and its effects become a reusable rule.</p><p><br></p>

<h3>Where Can This Be Used?</h3>

<p>The Filter Rules Manager isn&#39;t specifically a grid filtering component or a UI visibility component.</p>

<p>It is deliberately more generic.</p>

<p>Some examples include:</p>

<ul>
<li>Show or hide controls depending on application state.</li>
<li>Enable actions only when certain conditions are met.</li>
<li>Change the appearance of matching items.</li>
<li>Apply rules to collections of your own objects.</li>
<li>Highlight records requiring attention.</li>
<li>Control which application actions are currently available.</li>
<li>Apply multiple property changes from a single condition.</li>
</ul>

<p>And because the logic is based on the same filtering foundation used throughout this series, you don&#39;t need to learn another way of describing conditions.</p><p><br></p>

<h3>From Filtering Data to Application Logic</h3>

<p>This is ultimately the idea behind <b>TTMSFNCFilterRulesManager</b>.</p>

<p>A filter doesn&#39;t necessarily have to mean:</p>

<p><i>"Remove everything that doesn&#39;t match."</i></p>

<p>It can simply describe a condition:</p>

<p><i>"When this matches, do this."</i></p>

<p>By separating the condition from the action, the same structured filtering logic can be used in many more places throughout an application.</p>

<p>And because rules can work with component properties as well as your own objects, the Filter Rules Manager isn&#39;t tied to one particular control or use case.</p>

<p>It takes the FilterBuilder we&#39;ve been using throughout this series and turns it into a more general rules system for Delphi applications.</p>

		
		
		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Protect your public MCP servers with authentication in TMS AI Studio 1.8.0.0]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2511</link>
		<author>Bradley Velghe</author>
		<pubDate>Tue, 18 Aug 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		<p><b>Model Context Protocol (MCP)</b> is the standard that lets AI clients like Claude Desktop, ChatGPT, Cursor, ... call out to your own tools. An MCP server exposes a set of functions, the assistant reads their descriptions, decides when to call them, and feeds the results back into the conversation.</p>

<p>As long as that server only talks to itself on your own machine, it doesn&#39;t need to ask "who&#39;s calling?" The moment it becomes a real server instead &#151; reachable over the network, used by a colleague, or connected to from Claude Desktop on someone else&#39;s computer &#151; that question matters a lot.</p>

<p>You&#39;ve actually already answered that question dozens of times, just not for MCP. Every time you click <i>"Continue with Google"</i> or <i>"Sign in with Microsoft"</i> on some website, a login screen pops up, you approve it, and you&#39;re dropped back into the app, now connected. That familiar dance is called OAuth, and it&#39;s exactly the mechanism MCP uses to let an AI assistant connect to your server safely.</p>

<p>In this post we build that mechanism in Delphi. No prior knowledge of OAuth required &#151; we&#39;ll explain each piece in plain terms as we go, then show the real, compiling Delphi code behind it.</p>

<ul> <li>It&#39;s the <b>same well-known approach</b> used by "Sign in with Google" and every other app you&#39;ve connected an account to &#151; not something custom or proprietary.</li> <li>Turning it on for an existing MCP server is a <b>one-line switch</b>, not a rewrite.</li> <li>It&#39;s been tested <b>against real AI assistants</b> &#151; MCP Inspector, Claude Desktop and ChatGPT have all connected through this exact code.</li> </ul><p><br></p>

<h3>The two servers involved</h3>

<p>There are two separate pieces at play here, and keeping them separate is the whole point.</p>

<p>One is your actual <b>MCP server</b> &#151; the one with the tools the AI wants to call. The other is a small <b>login and permissions service</b>, whose only job is to check who someone is and hand out a token proving it. Your MCP server never checks logins itself; it just asks the login service, every time, "is this token still good, and what&#39;s it allowed to do?"</p>

<p><svg viewBox="0 0 860 260" role="img" aria-label="An AI assistant presents a token to the MCP server, which never trusts it by itself and instead asks the login service whether it&#39;s still valid, every time." style="max-width:100%;height:auto;">
  <defs>
    <marker id="oauthArrow1" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#0E7C86"></path></marker>
    <marker id="oauthArrowGray" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"><path d="M0,0 L8,4 L0,8 z" fill="#6E7681"></path></marker>
  </defs>
  <rect x="20" y="96" width="150" height="76" rx="12" fill="#ffffff" stroke="#D8D3C9" stroke-width="1.5"></rect>
  <text x="95" y="128" text-anchor="middle" font-family="Consolas,monospace" font-size="12" fill="#14181E">AI Assistant</text>
  <text x="95" y="146" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#6E7681">Claude &#183; ChatGPT</text>
  <text x="95" y="160" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#6E7681">MCP Inspector</text>

  <rect x="230" y="50" width="250" height="170" rx="12" fill="#0F141B"></rect>
  <text x="355" y="78" text-anchor="middle" font-family="Consolas,monospace" font-size="12" fill="#D7DEE6">Your MCP Server</text>
  <text x="355" y="96" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#9FB0C0">the one with the tools</text>
  <rect x="250" y="110" width="210" height="26" rx="6" fill="#161D27" stroke="#27313D"></rect>
  <text x="355" y="127" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#9FB0C0">requires a valid token</text>
  <rect x="250" y="144" width="210" height="26" rx="6" fill="#161D27" stroke="#27313D"></rect>
  <text x="355" y="161" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#9FB0C0">checks it&#39;s meant for THIS server</text>
  <text x="355" y="200" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#6E7681">never trusts a token by itself</text>

  <rect x="560" y="50" width="250" height="170" rx="12" fill="#0F141B"></rect>
  <text x="685" y="78" text-anchor="middle" font-family="Consolas,monospace" font-size="12" fill="#D7DEE6">Login Service</text>
  <text x="685" y="96" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#9FB0C0">a separate, small process</text>
  <rect x="580" y="110" width="210" height="26" rx="6" fill="#161D27" stroke="#27313D"></rect>
  <text x="685" y="127" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#9FB0C0">handles sign-in &amp; consent</text>
  <rect x="580" y="144" width="210" height="26" rx="6" fill="#161D27" stroke="#27313D"></rect>
  <text x="685" y="161" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#9FB0C0">answers "is this still valid?"</text>
  <text x="685" y="200" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#6E7681">issues &amp; vouches for tokens</text>

  <path d="M170,124 H228" stroke="#0E7C86" stroke-width="2" fill="none" marker-end="url(#oauthArrow1)"></path>
  <text x="199" y="116" text-anchor="middle" font-family="Consolas,monospace" font-size="9.5" fill="#0A5A62">"here&#39;s my token"</text>

  <path d="M480,157 H558" stroke="#0E7C86" stroke-width="2.5" fill="none" marker-end="url(#oauthArrow1)"></path>
  <text x="519" y="149" text-anchor="middle" font-family="Consolas,monospace" font-size="10" font-weight="700" fill="#0A5A62">is this valid?</text>

  <path d="M558,185 H480" stroke="#6E7681" stroke-width="1.25" fill="none" marker-end="url(#oauthArrowGray)"></path>
  <text x="519" y="203" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#6E7681">yes &#151; here&#39;s what it can do</text>

  <path d="M95,172 L95,232 L685,232 L685,222" stroke="#94A3B8" stroke-width="1.25" stroke-dasharray="4 3" fill="none" marker-end="url(#oauthArrowGray)"></path>
  <text x="390" y="248" text-anchor="middle" font-family="Consolas,monospace" font-size="9" font-style="italic" fill="#6E7681">first time only: sign in once, get a token</text>
</svg></p>
<p><i>Two separate services; your MCP server trusts nothing it hasn&#39;t just double-checked with the login service.</i></p><p><br></p>

<h3>Turning it on</h3>

<p>If you already have an MCP server built with the TMS MCP SDK, requiring a login is not a rewrite. It&#39;s a handful of lines on the transport you already created.</p>

<pre class="delphi" name="code">Transport := TTMSMCPStreamableHTTPTransport.Create(nil, 8934, &#39;/mcp&#39;);
Transport.RequireBearerAuthentication := True;
Transport.AuthorizationServers.Add(AuthorizationServerIssuer);
Transport.ResourceScopesSupported.Add(&#39;demo:read&#39;);
Transport.ResourceScopesSupported.Add(&#39;demo:write&#39;);
Transport.RequiredScopes.Add(&#39;demo:read&#39;);
Transport.OnValidateAccessToken := Handlers.ValidateAccessToken;

Server.Transport := Transport;</pre>

<p><code>RequireBearerAuthentication := True</code> is the actual switch: every request now needs a valid token attached, or it&#39;s refused. <code>AuthorizationServers</code> tells your server which login service to trust, and <code>ResourceScopesSupported</code> lists the permissions that exist (here, "read" and "write" access to a notes list) &#151; both get automatically announced to any AI client that asks, so nothing needs to be configured by hand on the client side.</p>

<p>The one piece the SDK deliberately leaves to you is <code>OnValidateAccessToken</code>: the actual check of "is this token real, and what does it allow?" Different login services hand out tokens in different formats, so that decision is yours, not the transport&#39;s.</p><p><br></p>

<h3>Checking a token: always ask, never assume</h3>

<p>Here is the demo&#39;s real implementation of that check. It calls the login service and asks it directly, rather than trying to verify the token by itself.</p>

<pre class="delphi" name="code">procedure TDemoAuthHandlers.ValidateAccessToken(Sender: TObject; const AToken, AResourceURI: string;
  var AValidation: TTMSMCPAccessTokenValidation);
var
  Http: TIdHTTP;
  ReqBody, RespBody: TStringStream;
  Json: TJSONObject;
  Audience: string;
begin
  AValidation.Valid := False;

  Http := TIdHTTP.Create(nil);
  ReqBody := TStringStream.Create(&#39;token=&#39; + TTMSMCPUtils.URLEncode(AToken));
  RespBody := TStringStream.Create;
  try
    Http.Request.ContentType := &#39;application/x-www-form-urlencoded&#39;;
    Http.Post(AuthorizationServerIssuer + &#39;/introspect&#39;, ReqBody, RespBody);

    Json := TJSONObject.ParseJSONValue(RespBody.DataString) as TJSONObject;
    if not Assigned(Json) or not Assigned(Json.GetValue(&#39;active&#39;)) or
      not (Json.GetValue(&#39;active&#39;) as TJSONBool).AsBoolean then
      Exit; // not valid - AValidation.Valid stays False

    // Make sure this token was actually meant for THIS server, not some
    // other one the same login service happens to protect.
    Audience := &#39;&#39;;
    if Assigned(Json.GetValue(&#39;aud&#39;)) then
      Audience := Json.GetValue(&#39;aud&#39;).Value;
    if Audience &lt;&gt; AResourceURI then
      Exit; // valid token, but issued for a different server - reject anyway

    AValidation.Valid := True;
    AValidation.Subject := Json.GetValue(&#39;sub&#39;).Value;
    AValidation.Scopes := Json.GetValue(&#39;scope&#39;).Value.Split([&#39; &#39;]);
  finally
    RespBody.Free; ReqBody.Free; Http.Free;
  end;
end;</pre>

<p>That middle check &#151; confirming the token was actually meant for <i>this</i> server &#151; matters more than it looks. Without it, a token handed out for some other server (that happens to share the same login service) would be accepted here too, just because it was technically "still valid". Checking who a token was meant for is what stops it from being reused somewhere it was never supposed to work.</p>

<p>Put together, every single request runs through the same short checklist before a tool ever gets called:</p>

<p><svg viewBox="0 0 860 132" role="img" aria-label="Every request is checked for a token, that token is confirmed with the login service, and it&#39;s confirmed to be for this server, before a tool runs." style="max-width:100%;height:auto;">
  <defs>
    <marker id="oauthArrow2" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#0E7C86"></path></marker>
  </defs>
  <rect x="14" y="46" width="128" height="42" rx="9" fill="#0F141B"></rect>
  <text x="78" y="64" text-anchor="middle" font-family="Consolas,monospace" font-size="11.5" fill="#D7DEE6">request comes in</text>
  <text x="78" y="79" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#9FB0C0">with a token</text>
  <path d="M142,67 H176" stroke="#0E7C86" stroke-width="2" fill="none" marker-end="url(#oauthArrow2)"></path>
  <rect x="178" y="46" width="140" height="42" rx="9" fill="#0F141B"></rect>
  <text x="248" y="64" text-anchor="middle" font-family="Consolas,monospace" font-size="11.5" fill="#D7DEE6">read the token</text>
  <text x="248" y="79" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#9FB0C0">is one even attached?</text>
  <path d="M318,67 H352" stroke="#0E7C86" stroke-width="2" fill="none" marker-end="url(#oauthArrow2)"></path>
  <rect x="354" y="46" width="140" height="42" rx="9" fill="#0F141B"></rect>
  <text x="424" y="64" text-anchor="middle" font-family="Consolas,monospace" font-size="11.5" fill="#D7DEE6">ask the login service</text>
  <text x="424" y="79" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#9FB0C0">still valid?</text>
  <path d="M494,67 H528" stroke="#0E7C86" stroke-width="2" fill="none" marker-end="url(#oauthArrow2)"></path>
  <rect x="530" y="46" width="150" height="42" rx="9" fill="#0F141B"></rect>
  <text x="605" y="64" text-anchor="middle" font-family="Consolas,monospace" font-size="11.5" fill="#D7DEE6">check it fits</text>
  <text x="605" y="79" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#9FB0C0">this server, this permission</text>
  <path d="M680,67 H714" stroke="#0E7C86" stroke-width="2" fill="none" marker-end="url(#oauthArrow2)"></path>
  <rect x="716" y="46" width="130" height="42" rx="9" fill="#0E7C86"></rect>
  <text x="781" y="64" text-anchor="middle" font-family="Consolas,monospace" font-size="11.5" fill="#ffffff">run the tool</text>
  <text x="781" y="79" text-anchor="middle" font-family="Consolas,monospace" font-size="9" fill="#CDEDEE">or refuse</text>
</svg></p>
<p><i>Every call is double-checked with the login service before a tool ever runs.</i></p><p><br></p>

<h3>What it looks like in Claude Desktop</h3>

<p>All of the above is what makes this work under the hood. Here&#39;s what it actually looks like from the other side, adding this server to Claude Desktop as a custom connector &#151; and it will look familiar, because it&#39;s the same "sign in and approve" pattern as any other app.</p>

<p>Add the connector&#39;s address and hit Connect. Approve on the sign-in screen that opens, and the connector shows connected:</p>

<table cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:560px;margin:20px auto;">
  <tbody><tr>
    <td style="width:50%;padding:0 8px 0 0;text-align:center;vertical-align:top;">
      <div style="background:#0F141B;border-radius:12px;padding:22px 16px;box-shadow:0 2px 10px rgba(15,20,27,0.18);">
        <img src="https://www.tmssoftware.com/site/img/blog/claude-connector-connect.png" alt="Claude Desktop&#39;s custom connector row, showing a Connect button" style="max-width:100%;height:auto;display:block;margin:0 auto;">
      </div>
      <p style="margin:10px 0 0;"><i>Before &#151; not yet connected</i></p>
    </td>
    <td style="width:50%;padding:0 0 0 8px;text-align:center;vertical-align:top;">
      <div style="background:#0F141B;border-radius:12px;padding:22px 16px;box-shadow:0 2px 10px rgba(15,20,27,0.18);">
        <img src="https://www.tmssoftware.com/site/img/blog/claude-connector-connected.png" alt="Claude Desktop&#39;s custom connector row, now showing a checkmark for a connected server" style="max-width:100%;height:auto;display:block;margin:0 auto;">
      </div>
      <p style="margin:10px 0 0;"><i>After &#151; approved and connected</i></p>
    </td>
  </tr>
</tbody></table>

<p>In between sits the sign-in screen itself &#151; plain HTML, served straight from the Delphi backend, no client-side JavaScript involved:</p>

<div style="max-width:420px;margin:20px auto;background:#ffffff;border:1px solid #D8D3C9;border-radius:12px;padding:18px;box-shadow:0 2px 10px rgba(20,24,30,0.08);text-align:center;">
  <img src="https://www.tmssoftware.com/site/img/blog/claude-consent-page.png" alt="The demo login service&#39;s consent page, asking to approve or deny read and write permissions for Claude" style="max-width:100%;height:auto;display:block;margin:0 auto;border-radius:4px;">
</div>
<p style="text-align:center;"><i>The sign-in screen: plain server-rendered HTML, nothing exotic needed to make this part work.</i></p><p><br></p>

<p>The permissions you approve here decide what a token is <i>allowed</i> to do. Claude adds its own, separate layer of consent on top of that, per tool, before it will actually use anything:</p>

<div style="max-width:560px;margin:20px auto;background:#0F141B;border-radius:12px;padding:20px;box-shadow:0 2px 10px rgba(15,20,27,0.18);text-align:center;">
  <img src="https://www.tmssoftware.com/site/img/blog/claude-tool-permissions.png" alt="Claude Desktop&#39;s tool permissions panel, showing add_note, delete_note and list_notes each set to Needs approval" style="max-width:100%;height:auto;display:block;margin:0 auto;border-radius:6px;">
</div>
<p style="text-align:center;"><i>Claude&#39;s own tool-permission setting, independent of what you approved during sign-in.</i></p><p><br></p>

<p>Two separate checks, worth keeping apart: the sign-in step decides what a token is <i>entitled</i> to at all; Claude&#39;s own permission setting decides whether it will actually use a given tool <i>right now</i> without asking first. Approving "write" access during sign-in doesn&#39;t mean Claude will silently add a note &#151; that second decision stays entirely with Claude, on your machine.</p><p><br></p>

<h3>From demo to a real deployment</h3>

<p>Both servers ship two ways: as plain console apps you can run and read top to bottom in an afternoon, and as installable Windows services (a proper install command, a config file, a log file to check) for when you actually want one sitting on a server.</p>

<p>We ran that exact path start to finish: built as Windows services, put behind IIS on a real domain with a real certificate, and connected end to end by MCP Inspector, Claude Desktop and ChatGPT &#151; sign in, approve, and a live tool call, all the way through.</p>

<p>That last stretch &#151; going from "it works on my own machine" to "it works on a real server, for anyone" &#151; has its own sharp edges: what address the server needs to tell clients about once it&#39;s behind a proxy, a place where the sign-in step can get silently misdirected by proxy configuration, and a couple of real bugs we found and fixed along the way. We wrote all of that down separately, in detail, rather than compress it into a footnote here: see the <a href="https://download.tmssoftware.com/doc/tmsaistudio/components/Authentication/">documentation</a> for the full setup-to-production walkthrough.</p><p><br></p>

<h3>Why this matters</h3>

<ul> <li>Requiring a login is a one-line switch on the server you already have, not a rewrite of your tools.</li> <li>AI clients need no manual setup to connect &#151; the whole sign-in process is standard and already understood by MCP-aware clients, the same way "Sign in with Google" already works everywhere without you configuring anything.</li> <li>Your MCP server trusts nothing by default; every token is checked with the login service, every time, and checked against being reused on the wrong server.</li> <li>The demo login service is built to be strict by default &#151; it won&#39;t redirect anywhere it hasn&#39;t been explicitly told is safe.</li> <li>The same code that runs as a five-minute local demo is the code we actually deployed on a real server with a real domain &#151; this isn&#39;t a toy that stops working the moment it leaves your own machine.</li> </ul><p><br></p>

<h3>Get the demo</h3>

<p>All four projects &#151; the two console demos and their Windows-service counterparts &#151; are in the distribution folder under <code>Demos/OAuth Authorization Server Demo</code>, <code>Demos/OAuth Resource Server Demo</code>, <code>Demos/OAuth Authorization Server Service</code> and <code>Demos/OAuth Resource Server Service</code>.</p>

<p>For everything past "it runs on my machine" &#151; turning these into installable services, adding a certificate, and putting them behind IIS on a real domain, caveats included &#151; the <a href="https://download.tmssoftware.com/doc/tmsaistudio/components/Authentication/">documentation</a> walks through that in the order you&#39;d actually run into each decision.</p>

<p>Clone it, point a client at it, and watch the whole sign-in happen without writing a single line of client-side authentication code.</p><p><br></p>

<hr>

<p><b><a href="https://www.tmssoftware.com/site/tmsaistudio.asp">TMS AI Studio</a></b> is the complete toolkit for bringing AI and the Model Context Protocol to your Delphi applications. Build MCP servers and clients, connect Claude and other assistants to your own tools and data, and add cloud AI to your VCL and FMX apps &#151; all in native Delphi. <a href="https://www.tmssoftware.com/site/tmsaistudio.asp">Discover TMS AI Studio</a>.</p>
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿A technical look at the TMS AI City Guide summer project]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2509</link>
		<author>Bruno Fierens</author>
		<pubDate>Thu, 13 Aug 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		
		
<p style="margin-left: 25px;"><img src="https://www.tmssoftware.com/site/img/blog/SummerProject2026/tms-summer-2026-header.jpg" style="width: 100%;" alt="TMS Software Delphi Components tmsaistudio"></p><br><a href="https://www.tmssoftware.com/site/blog.asp?post=2504" target="_blank">The TMS AI City Guide project</a> is a <b>Delphi FireMonkey application</b> that combines interactive maps, AI-generated points of interest, GPX route export, and AI-generated speech. It demonstrates a useful pattern for cross-platform Delphi development: keep the application logic platform-independent and isolate native functionality behind small wrapper units.<div><br></div><div>The project targets Android, iOS, Windows and MacOS. The main application is defined in TMSCityGuide.dpr, while the UI logic is separated into UILogic.pas. Two particularly reusable examples are the file share functionality in uShareFile.pas and MP3 playback in uMP3Play.pas.</div><div><br></div><h4>Exporting a GPX file through the device share sheet</h4><br><div>The map component can export the current route, including its waypoints, to a GPX file using SaveToGPXFile. The resulting file can then be passed to other applications such as mapping, navigation, fitness, or file-management apps.</div><div><br></div><div style="text-align: center; "><img src="https://www.tmssoftware.com/site/img/blog/SummerProject2026/Summer2026ShareSheet.PNG" style="width: 25%;"><br></div><div><br></div><div>The form code first creates a temporary filename, exports the route, and calls one platform-independent procedure:<br></div><div><pre class="delphi" name="code">procedure TCityGuide.BtnShareClick(Sender: TObject);
var
  LFileName: string;
  FName: string;
begin
  if UILogic.City &lt;&gt; &#39;&#39; then
    FName := UILogic.City + &#39;.gpx&#39;
  else
    FName := &#39;mycitytour.gpx&#39;;

  LFileName := TPath.Combine(
    TPath.GetTempPath,
    TPath.GetFileName(FName)
  );

  UILogic.ExportToGPX(TMSFNCMaps, LFileName);

  ShareFile(LFileName, &#39;application/xml&#39;, FName);
end;<br>
</pre><br></div><div>The important design decision is that the form does not contain Android or iOS code. It only knows that ShareFile accepts a filename, MIME type, and title.</div><div><br></div><div><b>Android implementation</b></div><div><br></div><div>On Android, ShareFile creates an ACTION_SEND intent. The file is exposed through an AndroidX FileProvider, which produces a secure content URI. The URI is attached to the intent as EXTRA_STREAM, and read permission is granted to the receiving application.</div><div><pre class="delphi" name="code">LIntent := TJIntent.Create;
LIntent.setAction(TJIntent.JavaClass.ACTION_SEND);
LIntent.setType(StringToJString(AMimeType));

LIntent.putExtra(
  TJIntent.JavaClass.EXTRA_STREAM,
  TJParcelable.Wrap((LUri as ILocalObject).GetObjectID)
);

LIntent.addFlags(
  TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION
);

TAndroidHelper.Activity.startActivity(
  TJIntent.JavaClass.createChooser(
    LIntent,
    StrToJCharSequence(ATitle)
  )
);</pre><br></div><div>The Android project configuration also needs a FileProvider entry in AndroidManifest.template.xml and a deployed provider_paths.xml. The provider path maps the application&#146;s documents directory so that the file can safely be shared without exposing arbitrary filesystem paths.</div><div><br></div><div><b>iOS implementation</b></div><div><br></div><div>On iOS, the same procedure uses UIActivityViewController, the standard share sheet. The local filename is converted to an NSURL and passed as an activity item:</div><div><pre class="delphi" name="code">LURL := TNSURL.Wrap(
  TNSURL.OCClass.fileURLWithPath(
    StrToNSStr(AFilePath)
  )
);

LItems := TNSMutableArray.Create;
LItems.addObject((LURL as ILocalObject).GetObjectID);

LActivity := TUIActivityViewController.Wrap(
  TUIActivityViewController.Alloc.initWithActivityItems(
    LItems,
    nil
  )
);

LRootVC.presentViewController(LActivity, True, nil);</pre><br></div><div>The implementation also configures a popover source view and rectangle when required. This is important for iPad compatibility, where an activity controller must have a presentation source.</div><div><br></div><div>The result is a single reusable API:</div><div><pre class="delphi" name="code">ShareFile(
  &#39;/path/to/citytour.gpx&#39;,
  &#39;application/xml&#39;,
  &#39;Share city tour&#39;
);
</pre></div><div>On desktop platforms, the same wrapper falls back to a TSaveDialog. This is a useful cross-platform technique: expose one common interface while using conditional compilation only inside the implementation unit.</div><div><br></div><h4>Converting AI-generated descriptions into speech</h4><div><br></div><div>The application also uses TCloudAI to request speech audio from an AI service. When the user selects a place and presses the speak button, the description is sent to the speech service:</div><div><pre class="delphi" name="code">procedure TUILogic.Speak(Description: string);
begin
  FCloudAIAudio.Speak(Description, 1, &#39;nova&#39;);
end;</pre></div><div>The audio request is asynchronous. During initialization, the audio-enabled TCloudAI instance is connected to an event handler:</div><div><pre class="delphi" name="code">FCloudAIAudio := TCloudAI.Create(Self);
FCloudAIAudio.Service := aiOpenAI;
FCloudAIAudio.OnSpeechAudio := SoundReady;

FMP3Player := TStreamMP3Player.Create;</pre></div><div>When the response arrives, the event provides the generated audio as a TMemoryStream:</div><div><pre class="delphi" name="code">procedure TUILogic.SoundReady(
  Sender: TObject;
  HttpStatusCode: Integer;
  HttpResult: string;
  SoundBuffer: TMemoryStream
);
begin
  FMP3Player.Play(SoundBuffer);
end;</pre></div><div>This keeps the network operation and audio playback loosely coupled. TCloudAI is responsible for obtaining the speech, while TStreamMP3Player only needs to know how to play an MP3 stream.<br><br>If you want to see this feature in action, check our video:&nbsp;<a href="https://www.youtube.com/watch?v=VPMEdQxjSd4" target="_blank">https://www.youtube.com/watch?v=VPMEdQxjSd4</a></div><div><br></div><h4>Playing an MP3 stream with FireMonkey</h4><div>Mobile media frameworks generally expect a filename rather than an arbitrary Delphi stream. The TStreamMP3Player class bridges that gap:</div><div><br></div><ol><li>It creates a unique filename using a GUID.</li><li>It writes the stream to TPath.GetCachePath.</li><li>It assigns that temporary file to TMediaPlayer.</li><li>It starts playback.</li><li>It destroys the media player before deleting the temporary file.</li></ol><div><br></div><div>The public API remains small:</div><div><pre class="delphi" name="code">FMP3Player.Play(SoundBuffer);</pre></div><div>The essential implementation is:</div><div><pre class="delphi" name="code">procedure TStreamMP3Player.Play(AStream: TStream);
var
  LFileName: string;
  LFileStream: TFileStream;
begin
  if not Assigned(AStream) then
    raise EArgumentNilException.Create(&#39;AStream&#39;);

  ReleaseMedia;

  LFileName := CreateTempMP3FileName;

  AStream.Position := 0;
  LFileStream := TFileStream.Create(
    LFileName,
    fmCreate or fmShareDenyWrite
  );
  try
    LFileStream.CopyFrom(AStream, 0);
  finally
    LFileStream.Free;
  end;

  FTempFileName := LFileName;
  FMediaPlayer.FileName := FTempFileName;
  FMediaPlayer.CurrentTime := 0;
  FMediaPlayer.Play;
end;
</pre></div><div>One subtle but important detail is the cleanup order. The native media backend may still have an open handle to the MP3 file. Therefore, TMediaPlayer is stopped and destroyed first; only then is the temporary cache file removed. This avoids platform-specific &#147;file in use&#148; problems and makes the class safe to reuse.</div><div><br></div><h4>Conclusion</h4><div>TMSCityGuide illustrates several broadly useful FireMonkey techniques:</div><div><br></div><ul><li>Isolate native platform APIs behind a common Delphi wrapper</li><li>Use conditional compilation inside implementation units rather than throughout the UI</li><li>Use secure sharing mechanisms such as Android FileProvider</li><li>Use temporary and cache directories through System.IOUtils</li><li>Handle asynchronous AI responses with events</li><li>Adapt stream-based data to filename-based platform APIs</li><li>Carefully manage native resources before deleting temporary files.</li></ul><div><br></div><div>The result is application code that remains simple and readable while still integrating deeply with Android, iOS, cloud AI services, and device hardware.</div><div><br></div><div><h4 style="color: rgb(51, 51, 51);">Everything is open source</h4><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;">Like our previous summer projects, <span style="font-weight: 700;">the complete source code is available for free</span>. Clone it, learn from it, extend it, or use parts of it in your own applications.</p><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;">The full project is available on GitHub:</p><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;"><a href="https://github.com/tmssoftware/TMS-City-Guide" style="color: rgb(0, 158, 227); text-decoration: none;"><img src="https://www.tmssoftware.com/site/img/blog/github.png" alt="GitHub repository" style="max-width: 100%; width: 236px; height: 88px;"></a></p><h4 style="color: rgb(51, 51, 51);">Built with TMS components</h4><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;">TMS City Guide showcases how easy it is to combine modern AI capabilities with native cross-platform Delphi development.</p><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;">The application was built using:</p><ul style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;"><li><a href="https://www.tmssoftware.com/site/tmsfncmaps.asp" target="_blank" style="color: rgb(0, 158, 227); text-decoration: none;">TMS FNC Maps</a> for interactive mapping and routing</li><li><a href="https://www.tmssoftware.com/site/tmsfncuipack.asp" target="_blank" style="color: rgb(0, 158, 227); text-decoration: none;">TMS FNC UI Pack</a> for the modern user interface</li><li><a href="https://www.tmssoftware.com/site/tmsaistudio.asp" target="_blank" style="color: rgb(0, 158, 227); text-decoration: none;">TMS AI Studio</a> for AI integration, speech generation and intelligent content</li></ul><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;">To celebrate this summer project, we&#39;re offering a <span style="color: red; font-weight: bold;">25% discount</span> on these products until <span style="font-weight: 700;">August 31, 2026</span>.</p><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;"><img src="https://www.tmssoftware.com/site/img/blog/25-off-discount.png" alt="25% Discount" style="max-width: 100%; width: 137px; height: 137px;"></p><p style="color: rgb(51, 51, 51); font-family: Roboto, sans-serif;">Simply use coupon code&nbsp;<img src="https://www.tmssoftware.com/site/img/blog/couponcode.png" alt="Coupon code" style="max-width: 100%; width: 281px;">&nbsp;during checkout to activate the discount.</p></div>
		
		
		
		
		
		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿Filtering in Delphi: Building Modern Filter Interfaces]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2507</link>
		<author>Gjalt Vanhouwaert</author>
		<pubDate>Fri, 7 Aug 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		
		<p>In the <a href="https://tmssoftware.com/site/blog.asp?post=2500">previous blog post</a>, we introduced <b>TTMSFNCFilterView</b> and looked at how users can filter data by interacting with familiar controls instead of building filter expressions manually.</p>

<p>Checkboxes, combo boxes, sliders, date pickers and other controls all contribute to the same structured filter in the background.</p>

<p>Now it is time to see how this works in practice.</p>

<p>In this blog post, we will look at two examples:</p>

<ul>
<li>Using <b>TTMSFNCFilterView</b> together with <b><a href="https://tmssoftware.com/site/tmsfncuipack-grid.asp">TTMSFNCDataGrid</a></b>.</li>
<li>Extending the Filter View with your own controls.</li></ul><p><br></p><ul>
</ul>

<h3>TTMSFNCFilterView and TTMSFNCDataGrid</h3>

<p>The first video demonstrates how <b>TTMSFNCFilterView</b> can be combined with <b><a>TTMSFNCDataGrid</a></b><a> </a>to create a modern filtering interface.</p>

<p style="text-align: center;">
<iframe frameborder="0" src="//www.youtube.com/embed/oeGdDMOIJWI" width="640" height="360" class="note-video-clip"></iframe>
</p>

<p>The Filter View contains the controls the user interacts with, while the DataGrid displays the resulting data.</p>

<p>Whenever the user changes a filter control, the underlying filter expression is updated automatically.</p>

<p>This means you do not have to manually combine all values into one filter string or write separate filtering logic for every control.</p>

<p>In the video, you will see how:</p>

<ul>
<li>Built-in filter controls are added to the Filter View.</li>
<li>Each control is linked to a field and an operator.</li>
<li>Multiple controls contribute to one filter.</li>
<li>The generated filter is applied to <b>TTMSFNCDataGrid</b>.</li>
<li>Different filter actions can be used to determine how matching data is displayed.</li></ul><p><br></p><ul>
</ul>

<h3>Configuring the Filter Controls</h3>

<p>Every control inside the Filter View has an associated filter expression.</p>

<p>This expression determines what should be added to the filter when the user interacts with the control.</p>

<p>For example, a ComboBox can be linked to a country field:</p>

<pre class="delphi" name="code">FilterComboBox.FilterExpression.FilterFieldName := &#39;Country&#39;;
FilterComboBox.FilterExpression.FilterFieldType := fdtText;
FilterComboBox.FilterExpression.ExpressionOperator := feoEqual;</pre>

<p>A CheckBox can represent a Boolean value:</p>

<pre class="delphi" name="code">Active = True</pre>

<p>And a RangeSlider can contribute both a minimum and a maximum value.</p>

<p>Once these expressions are configured, the controls update their part of the filter automatically.</p>

<p>The application only needs to determine what should happen with the resulting filter.</p><p><br></p>

<h3>Applying the Filter</h3>

<p>Because <b>TTMSFNCFilterView</b> uses the same FilterBuilder structure introduced earlier in this series, the resulting filter can be used in different ways.</p>

<p>You can:</p>

<ul>
<li>Apply it to <b>TTMSFNCDataGrid</b>.</li>
<li>Assign the generated filter text to a <code>TDataSet</code>.</li>
<li>Validate individual rows.</li>
<li>Validate a complete array of data.</li>
<li>Use the filter with your own controls or object collections.</li>
</ul>

<p>The Filter View is responsible for building and maintaining the filter, while your application remains in control of how the result is applied.</p><p><br></p>

<h3>Using Your Own Controls</h3>

<p>The built-in controls cover many common filtering scenarios.</p>

<p>However, an application may already contain controls that should become part of the filtering interface.</p>

<p>You may also want to use a control that is specific to your application instead of one of the standard Filter View controls.</p>

<p>The second video demonstrates how your own controls can be integrated into <b>TTMSFNCFilterView</b>.</p>

<p style="text-align: center;">
<iframe frameborder="0" src="//www.youtube.com/embed/enIAg9Lz5gE" width="640" height="360" class="note-video-clip"></iframe>
</p>

<p>This allows you to create a filtering interface that matches the rest of your application without giving up the structured filtering logic.</p><p><br></p>

<h3>Filter View Control Container</h3>

<p>One way to integrate an existing control is by using <b>TTMSFNCFilterViewControlContainer</b>.</p>

<p>The Control Container acts as a bridge between the control and a filter expression.</p>

<p>You configure the expression that the control represents:</p>

<pre class="delphi" name="code">ControlContainer.FilterExpression.FilterFieldName := &#39;Status&#39;;
ControlContainer.FilterExpression.FilterFieldType := fdtText;
ControlContainer.FilterExpression.ExpressionOperator := feoEqual;</pre>

<p>When the control value changes, you update the value stored in the expression.</p>

<pre class="delphi" name="code">ControlContainer.FilterExpression.FilterValue := &#39;Office&#39;;
ControlContainer.FilterExpression.AddToFilter := True;</pre>

<p>If the control represents an option such as <i>All</i>, the expression can simply be excluded from the filter.</p>

<pre class="delphi" name="code">ControlContainer.FilterExpression.AddToFilter := False;</pre>

<p>This gives you control over the user interface while the Filter View continues to manage the complete filter.</p><p><br></p>

<h3>Registering Controls Programmatically</h3>

<p>Controls can also be registered directly with the Filter View.</p>

<p>This is useful when controls are created dynamically or when you want to connect an existing control without placing it inside a Control Container.</p>

<p>For example, a calendar can be linked to a date field:</p>

<pre class="delphi" name="code">TMSFNCFilterView1.AddCustomItemControl(
  FNCCalendar,
  FNCCalendar.SelectedDate,
  &#39;Joined&#39;,
  fdtDate,
  feoSmallerThanOrEqual
);</pre>

<p>When the selected date changes, the linked filter value can be updated:</p>

<pre class="delphi" name="code">procedure TFilterViewForm.FNCCalendarSelectDate(
  Sender: TObject;
  ADate: TDate);
begin
  TMSFNCFilterView1.UpdateCustomItemControlValue(FNCCalendar, ADate);
end;</pre>

<p>The custom control now participates in the same filter as all built-in controls.</p><p><br></p>

<h3>One Filtering Model, Different Interfaces</h3>

<p>The important part is that the FilterBuilder does not need to know whether an expression came from:</p>

<ul>
<li>A built-in Filter View control.</li>
<li>A control inside a Control Container.</li>
<li>A custom control registered programmatically.</li>
<li>A filter dialog.</li>
<li>Code written by the developer.</li>
</ul>

<p>All of these approaches create the same structured filtering model.</p>

<p>This keeps the filtering logic centralized while allowing the user interface to be designed around the requirements of the application.</p><p><br></p>

<h3>Why Build a Custom Filter Interface?</h3>

<p>A predefined filter dialog is useful when users need to create detailed or occasional filters.</p>

<p>A Filter View is more suitable when filtering is a regular part of the application workflow.</p>

<p>Users can immediately see the available filter options and adjust them without leaving the current screen.</p>

<p>By supporting both built-in and custom controls, <b>TTMSFNCFilterView</b> does not force your application into one fixed layout.</p>

<ul>
<li>You can create a webshop-style filter panel.</li>
<li>You can use toolbar buttons for status values.</li>
<li>You can use a calendar for date selection.</li>
<li>You can combine different controls inside filter groups.</li>
<li>You can reuse controls that are already part of your interface.</li>
</ul>

<p>The user interface can be adapted to the application, while all controls continue to contribute to one consistent filter.</p><p><br></p>

<h3>Less Repetitive Filtering Code</h3>

<p>Without a shared filtering component, every control would usually require its own event handling and filtering implementation.</p>

<p>The selected values would then need to be combined manually before applying the filter.</p>

<p>With the Filter View, each control only defines the expression it represents.</p>

<p>The Filter View and FilterBuilder take care of combining these expressions, adding the required groups and generating the final filter.</p>

<p>This reduces the amount of repetitive filtering code and makes it easier to add or change filter controls later.</p><p><br></p>

<h3>What&#39;s Next?</h3>

<p>So far, we have used filter expressions to determine which data matches and which data should be displayed.</p>

<p>In the next blog post, we will look at how the same filtering logic can be used to trigger other behavior.</p>

<p>With <b>TTMSFNCFilterRulesManager</b>, matching a filter can change properties, control visibility, adjust appearance or trigger application logic.</p>

		
		
		
		<br><br></P>]]></description>
	</item>
	<item>
		<title><![CDATA[﻿From Delphi Code to City: Our TMS AI City Guide Explores Bruges]]></title>
		<link>https://www.tmssoftware.com/site/blog.asp?post=2505</link>
		<author>Bruno Fierens</author>
		<pubDate>Thu, 6 Aug 2026 12:00:00 +0000</pubDate>
		<description><![CDATA[<P>﻿
		
		
		
		
		
		
		<p><img src="https://www.tmssoftware.com/site/img/blog/SummerProject2026/tms-summer-2026-header.jpg" style="width: 100%;" alt="TMS Software Delphi Components tmsaistudio"></p>

<p>Last week we launched our <b>free Summer Project 2026</b>: <b>TMS City Guide</b>, an AI-powered virtual tour guide built entirely with Delphi.</p>

<p>Instead of following a predefined route, TMS City Guide creates a <b>personalized walking tour</b> based on your location and interests. As you walk, the app introduces you to landmarks, hidden gems and historical locations, complete with AI-generated spoken descriptions directly from your smartphone.</p>

<p>If you missed the original announcement, you can read all about the project in <a href="https://www.tmssoftware.com/site/blog.asp?post=2504" target="_blank">our previous blog post</a>.</p>

<h4>See it in action</h4>

<p>A demo is worth a thousand screenshots, so we took TMS City Guide for a real-world test in the beautiful city of <a href="https://www.visitbruges.be/en" target="_blank">Bruges</a>.</p>

<p>Watch how the app generates a walking tour on the fly, navigates between points of interest and brings each location to life with AI-generated narration.</p>

<p style="text-align: center; "><iframe frameborder="0" src="//www.youtube.com/embed/VPMEdQxjSd4" width="640" height="360" class="note-video-clip"></iframe><br></p>

<h4>Everything is open source</h4>

<p>Like our previous summer projects, <b>the complete source code is available for free</b>. Clone it, learn from it, extend it, or use parts of it in your own applications.</p>

<p>The full project is available on GitHub:</p>

<p><a href="https://github.com/tmssoftware/TMS-City-Guide"><img src="https://www.tmssoftware.com/site/img/blog/github.png" style="width:236px;height:88px;" alt="GitHub repository"></a></p>

<h4>Built with TMS components</h4>

<p>TMS City Guide showcases how easy it is to combine modern AI capabilities with native cross-platform Delphi development.</p>

<p>The application was built using:</p>

<ul>
<li><a href="https://www.tmssoftware.com/site/tmsfncmaps.asp" target="_blank">TMS FNC Maps</a> for interactive mapping and routing</li>
<li><a href="https://www.tmssoftware.com/site/tmsfncuipack.asp" target="_blank">TMS FNC UI Pack</a> for the modern user interface</li>
<li><a href="https://www.tmssoftware.com/site/tmsaistudio.asp" target="_blank">TMS AI Studio</a> for AI integration, speech generation and intelligent content</li>
</ul>

<p>To celebrate this summer project, we&#39;re offering a <span style="color:red;font-weight:bold;">25% discount</span> on these products until <b>August 31, 2026</b>.</p>

<p><img src="https://www.tmssoftware.com/site/img/blog/25-off-discount.png" style="width:137px;height:137px;" alt="25% Discount"></p>

<p>Simply use coupon code <img src="https://www.tmssoftware.com/site/img/blog/couponcode.png" style="width:281px;" alt="Coupon code"> during checkout to activate the discount.</p>

<h4>Coming next: Behind the scenes</h4>

<p>The application contains several interesting Delphi techniques that deserve a closer look.</p>

<p>In an upcoming blog post we&#39;ll explore how we implemented:</p>

<ul>
<li>Exporting generated walking tours as GPX files using the native operating system Share Sheet</li>
<li>Generating and streaming AI-produced speech on the fly</li>
<li>Bringing together AI, mapping and native FireMonkey development into one cross-platform application</li>
</ul>

<p>If you&#39;re interested in the technical implementation, stay tuned&#151;we&#39;ll dive into the source code soon.</p>

<h4>Build it. Customize it. Share it.</h4>

<p>You can clone the project from GitHub and build it yourself for iOS or Android using Delphi. If you&#39;d rather get started immediately, a ready-to-use Android APK is also available in the repository.</p>

<p>To run the application you&#39;ll need:</p>

<ul>
<li>an <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI API key</a></li>
<li>a <a href="https://console.cloud.google.com/apis/dashboard" target="_blank">Google Maps API key</a></li>
<li><a href="https://www.embarcadero.com/products/delphi/starter" target="_blank">Delphi</a></li>
</ul>

<p>Once you&#39;ve created your own walking tours, we&#39;d love to see them! Share the cities you&#39;ve explored, the routes you&#39;ve generated, or the improvements you&#39;ve added. Who knows&#151;your ideas might inspire the next version of TMS City Guide.</p>

<p>Happy coding&#151;and happy exploring! 🌍🚶‍♂️</p>

		
		
		
		
		
		
		<br><br></P>]]></description>
	</item>
	</channel></rss>
