Table of Contents

Navigation and Arrows

TTMSFNCCalendar supports four navigation modes, swipe gestures, custom arrow rendering, and interactive year/month selection popups.


Set Interaction.NavigationMode:

Mode Behaviour
nmMonth Arrows navigate one month at a time (default).
nmYear Arrows navigate one year at a time.
nmFocusedDate Arrows move the focused date one day at a time.
nmCustom Call OnCustomNavigation to implement your own logic.
TMSFNCCalendar1.Interaction.NavigationMode := nmYear;

Custom navigation

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCCalendar1.Interaction.NavigationMode := nmCustom;
end;

procedure TForm1.TMSFNCCalendar1CustomNavigation(Sender: TObject;
  ADate, AFocusedDate: TDate; ADirection: Boolean);
begin
  if ADirection then
    TMSFNCCalendar1.Year := TMSFNCCalendar1.Year - 10
  else
    TMSFNCCalendar1.Year := TMSFNCCalendar1.Year + 10;
end;

ADirection = True means the right (forward) arrow was pressed; False means the left (back) arrow.


Swipe on header

TMSFNCCalendar1.Interaction.SwipeOnHeader := True;

When enabled, swiping left/right on the header area also triggers navigation.


Arrow types

Change the left and right arrow style via Header.Arrow.ArrowLeftType and Header.Arrow.ArrowRightType:

Type Appearance
atArrow Chevron arrow (default).
atTriangle Filled triangle.
atBitmap Custom bitmap. Assign Header.Arrow.ArrowLeftBitmap / ArrowRightBitmap.
atCustom Fully custom — implement OnCustomArrowDraw.
Custom arrow styles on the calendar header

High-DPI arrow bitmaps

ArrowLeftBitmap and ArrowRightBitmap are not single bitmaps but TTMSFNCScaledBitmaps collections — one entry per display scale, each carrying its own Scale value. When the calendar draws an atBitmap arrow it asks the collection for the entry matching the current display scale (falling back to the largest one available), so a single 16x16 PNG that looks fine at 100% turns soft on a 150% or 200% monitor while a 24x24 and 32x32 sibling stay sharp.

Register the variants once at startup and keep ArrowLeftBitmapSize in logical units: the size property frames the arrow, the collection supplies the pixels.

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCCalendar1.BeginUpdate;
  try
    TMSFNCCalendar1.Header.Arrow.ArrowLeftType  := atBitmap;
    TMSFNCCalendar1.Header.Arrow.ArrowRightType := atBitmap;

    { ArrowLeftBitmap / ArrowRightBitmap are TTMSFNCScaledBitmaps collections:
      add one variant per display scale and the calendar picks the closest match
      for the monitor it is drawn on. Author the 1.0 artwork at the logical size
      and the others at that size multiplied by the scale. }
    TMSFNCCalendar1.Header.Arrow.ArrowLeftBitmap.Clear;
    TMSFNCCalendar1.Header.Arrow.ArrowLeftBitmap.AddBitmapFromFile('arrow-left.png', 1.0);
    TMSFNCCalendar1.Header.Arrow.ArrowLeftBitmap.AddBitmapFromFile('arrow-left@1.5x.png', 1.5);
    TMSFNCCalendar1.Header.Arrow.ArrowLeftBitmap.AddBitmapFromFile('arrow-left@2x.png', 2.0);

    TMSFNCCalendar1.Header.Arrow.ArrowRightBitmap.Clear;
    TMSFNCCalendar1.Header.Arrow.ArrowRightBitmap.AddBitmapFromFile('arrow-right.png', 1.0);
    TMSFNCCalendar1.Header.Arrow.ArrowRightBitmap.AddBitmapFromFile('arrow-right@1.5x.png', 1.5);
    TMSFNCCalendar1.Header.Arrow.ArrowRightBitmap.AddBitmapFromFile('arrow-right@2x.png', 2.0);

    { The bitmap is drawn into a box of this logical size, so keep it in
      design units - the scaled variant above supplies the extra pixels. }
    TMSFNCCalendar1.Header.Arrow.ArrowLeftBitmapSize  := 16;
    TMSFNCCalendar1.Header.Arrow.ArrowRightBitmapSize := 16;
  finally
    TMSFNCCalendar1.EndUpdate;
  end;

  { TTMSFNCUtils reports the scale the calendar will resolve against, which is
    useful when only some scales are shipped or when a high-DPI-only asset set
    has to be loaded lazily. }
  if TTMSFNCUtils.IsHighDPIScale(Self) then
    StatusLabel.Text := Format('Running at %.2fx', [TTMSFNCUtils.GetDPIScale(Self)]);
end;

The rest of the calendar chrome — header, footer and day-name heights, fonts, badge size and arrow size — is rescaled by the control itself when the form's DPI changes, so those are authored once at design DPI and need no runtime arithmetic. Bitmaps are the exception, because there is nothing to scale up from.

Custom arrow drawing

procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSFNCCalendar1.Header.Arrow.ArrowLeftType  := atCustom;
  TMSFNCCalendar1.Header.Arrow.ArrowRightType := atCustom;
end;

procedure TForm1.TMSFNCCalendar1CustomArrowDraw(Sender: TObject;
  AGraphics: TTMSFNCGraphics; ARect: TRectF; ADirection: Boolean);
var
  pl, pr: TPointF;
  h, m: Single;
begin
  h  := ARect.Bottom - ARect.Top;
  pl := PointF(ARect.Left,  ARect.Top + h / 2);
  pr := PointF(ARect.Right, ARect.Top + h / 2);
  m  := ARect.Left + (ARect.Right - ARect.Left) / 2;
  AGraphics.Stroke.Width := 2;
  if ADirection then
  begin
    AGraphics.DrawLine(PointF(m, ARect.Top), pl);
    AGraphics.DrawLine(pl, PointF(m, ARect.Bottom));
    AGraphics.DrawLine(pl, pr);
  end
  else
  begin
    AGraphics.DrawLine(PointF(m, ARect.Top), pr);
    AGraphics.DrawLine(PointF(m, ARect.Bottom), pr);
    AGraphics.DrawLine(pr, pl);
  end;
end;
Calendar with custom-drawn arrows

Year and month selection popups

Enable clicking the year or month text in the header to open a selector popup:

TMSFNCCalendar1.Interaction.AllowYearSelection  := True;
TMSFNCCalendar1.Interaction.AllowMonthSelection := True;

Configure the year popup via Interaction.YearOptions. Subscribe to OnBeforeShowYearPopup or OnBeforeShowMonthPopup to act before the popup appears.


Programmatic navigation

TMSFNCCalendar1.Date  := EncodeDate(2025, 6, 15);  // jump to a date
TMSFNCCalendar1.Month := 3;   // jump to March in the current year
TMSFNCCalendar1.Year  := 2026;

Navigation utilities:

TMSFNCCalendar1.Date := TMSFNCCalendar1.NextMonth;
TMSFNCCalendar1.Date := TMSFNCCalendar1.PreviousMonth;
TMSFNCCalendar1.Date := TMSFNCCalendar1.NextYear;

See also