Table of Contents

Values, events, and interaction

TTMSFNCRangeSlider is a two-thumb slider for selecting a numeric range rather than a single value. Reach for it whenever a user needs to bound a quantity from both sides — a price band, a date span expressed as numbers, a min/max threshold for a filter, or a zoom window. It shares the appearance and interaction infrastructure of TTMSFNCTrackBar, but instead of one Value it exposes two independent positions, ValueLeft and ValueRight, that always stay ordered within the Min/Max bounds. This guide covers the value model, the snapping behaviour, the three change events and when each one fires, and how to wire the slider to live data. For visual styling — orientation, fill zones, tick marks, and custom thumb drawing — see Appearance and rendering.

TTMSFNCRangeSlider with two thumbs and a highlighted middle range

Quick start

Drop a TTMSFNCRangeSlider on a form, define the bounds with Min/Max, set the initial selection with ValueLeft/ValueRight, and handle OnValueChanged to read the result after the user releases a thumb. The ALeft parameter tells you which thumb moved, but in most cases you simply read both bounds.

// Configure a price range filter from 0 to 1000
TMSFNCRangeSlider1.Min := 0;
TMSFNCRangeSlider1.Max := 1000;
TMSFNCRangeSlider1.ValueLeft  := 100;
TMSFNCRangeSlider1.ValueRight := 750;

procedure TForm1.TMSFNCRangeSlider1ValueChanged(Sender: TObject;
  AValue: Single; ALeft: Boolean);
begin
  if ALeft then
    LabelMin.Caption := 'Min: ' + Format('%.0f', [TMSFNCRangeSlider1.ValueLeft])
  else
    LabelMax.Caption := 'Max: ' + Format('%.0f', [TMSFNCRangeSlider1.ValueRight]);
end;

The value model

Four Single properties define the slider:

Property Meaning Constraint
Min Lower bound of the track.
Max Upper bound of the track. Must be greater than Min.
ValueLeft Position of the left (lower) thumb. Clamped between Min and ValueRight.
ValueRight Position of the right (upper) thumb. Clamped between ValueLeft and Max.

The two thumbs can never cross: assigning a ValueLeft greater than ValueRight (or vice versa) is clamped to the neighbouring thumb, so your handlers never see an inverted range. This means you can read ValueLeft and ValueRight directly without re-sorting them.

Snapping with Frequency

By default the thumbs move continuously. Set Interaction.Frequency to snap both thumbs to a fixed step while dragging — for example Frequency := 1 for whole numbers, or Frequency := 5 to allow only multiples of five. A Frequency of 0 restores free, continuous movement.

{ Allow only multiples of 5 between the thumbs: }
TMSFNCRangeSlider1.Min := 0;
TMSFNCRangeSlider1.Max := 100;
TMSFNCRangeSlider1.Interaction.Frequency := 5;

Frequency is a presentation-and-input constraint: it governs the values the user can land on by dragging, but assigning ValueLeft/ValueRight in code is not rounded to the frequency, so keep code-set values aligned to the step if you want them to match the user-reachable grid.

Change events and timing

The range slider raises three distinct events. Choosing the right one matters for performance: the difference between while dragging and after release is the difference between recomputing a filter on every pixel of movement versus once per gesture.

Event Signature When it fires Use for
OnValueChange (Sender; AValue: Single; ALeft: Boolean) Continuously while a thumb is being dragged. Cheap live preview — updating a caption or a highlight.
OnValueChanged (Sender; AValue: Single; ALeft: Boolean) Once after the thumb is released. The expensive commit — querying, filtering, saving.
OnChanged (Sender) Whenever the control state changes and is invalidated. Coarse "something changed" notifications.

In both value events, AValue is the new value of the thumb that moved and ALeft is True for the left thumb, False for the right. Because the control keeps the bounds ordered for you, reading ValueLeft and ValueRight inside the handler is always safe.

{ Inside your form's OnCreate, after dropping a TTMSFNCRangeSlider: }
procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCRangeSlider1.Min := 0;
  TMSFNCRangeSlider1.Max := 100;
  TMSFNCRangeSlider1.ValueLeft  := 25;
  TMSFNCRangeSlider1.ValueRight := 75;

  // Snap both thumbs to whole units while dragging.
  TMSFNCRangeSlider1.Interaction.Frequency := 1;

  TMSFNCRangeSlider1.OnValueChange  := RangeSliderValueChange;
  TMSFNCRangeSlider1.OnValueChanged := RangeSliderValueChanged;
end;

// Fires continuously WHILE a thumb is being dragged - use for live preview only.
procedure TForm1.RangeSliderValueChange(Sender: TObject; AValue: Single;
  ALeft: Boolean);
begin
  LabelRange.Caption := Format('%.0f - %.0f',
    [TMSFNCRangeSlider1.ValueLeft, TMSFNCRangeSlider1.ValueRight]);
end;

// Fires once AFTER the thumb is released - use for the expensive commit (query, filter, save).
procedure TForm1.RangeSliderValueChanged(Sender: TObject; AValue: Single;
  ALeft: Boolean);
begin
  ApplyPriceFilter(TMSFNCRangeSlider1.ValueLeft, TMSFNCRangeSlider1.ValueRight);
end;
Note

OnValueChange was added in version 1.0.3.0. On older builds only OnValueChanged (the after-release event) is available.

Tip

The range slider also participates in TMS FNC data binding: as a thumb moves it notifies any attached TTMSFNCDataBinder, so a property bound through the data binding editor updates without manual event wiring. When the slider is bound this way, keep your own commit logic in OnValueChanged so you do not duplicate work the binder already performs during the drag.

Combining live preview with a committed filter

The highest-value pattern wires both value events together: OnValueChange keeps an on-screen label in sync as the user drags, while OnValueChanged runs the actual filter only once the thumb is released. This keeps the UI responsive without re-querying the data on every pixel of movement.

// Apply a range filter to a list whenever either thumb moves
procedure TForm1.TMSFNCRangeSlider1ValueChanged(Sender: TObject;
  AValue: Single; ALeft: Boolean);
var
  lo, hi: Single;
begin
  lo := TMSFNCRangeSlider1.ValueLeft;
  hi := TMSFNCRangeSlider1.ValueRight;

  LabelRange.Caption := Format('%.0f – %.0f', [lo, hi]);

  // Re-filter the data grid to show only rows within the price range
  DataGrid1.BeginUpdate;
  try
    FilterProductsByPrice(lo, hi);
  finally
    DataGrid1.EndUpdate;
  end;
end;

The filter handler reads ValueLeft and ValueRight together in a single place, so the logic is identical regardless of which thumb triggered the event, and the grid is refreshed inside a BeginUpdate/EndUpdate pair to avoid flicker.

Common pitfalls

  • Filtering inside OnValueChange. This event fires on every step of the drag; running a database query or full re-filter there causes visible lag. Do live, cheap work in OnValueChange and the expensive commit in OnValueChanged.
  • Expecting ValueLeft > ValueRight. The thumbs cannot cross. If you need an inverted interpretation, swap the values yourself when reading them.
  • Code-set values ignoring Frequency. Frequency snaps user dragging only; values assigned in code are not rounded. Align them to the step if they must match.

See also