1. Home
  2. /
  3. Docs
  4. /
  5. Articles Report Writer
  6. /
  7. Report Scripting Guid

Report Scripting Guid

Articles Report Designer – Scripting Guide

Table of Contents

  1. Introduction to Scripting
  2. The Script Editor
  3. PascalScript Basics
  4. Common Scripting Tasks
  5. Report Events
  6. Accessing Report Objects
  7. Working with Data
  8. Complete Examples

Introduction to Scripting

Articles uses PascalScript for report scripting. This is a Pascal-like scripting language that runs inside the report engine. Scripts allow you to:

  • Control report behavior dynamically
  • Validate user input in dialogs
  • Calculate values on the fly
  • Conditionally format report elements
  • Manipulate datasets and queries
  • Control report flow and execution

Key Points:

  • Scripts are embedded in the report file (.fr3)
  • Scripts run at report execution time, not design time
  • Each report object can have its own event handlers
  • Scripts have access to all report objects, datasets, and variables

The Script Editor

Accessing the Script Editor

There are several ways to open the script editor:

Method 1: Main Menu

  1. In the Articles Designer, click Report โ†’ Code
  2. The script window opens showing all report code

Method 2: Events Tab

  1. Select any report object (band, memo, dialog control, etc.)
  2. Click the Events tab in the Object Inspector
  3. Double-click any event (e.g., OnBeforePrint, OnClick)
  4. The script editor opens with the event handler created

Method 3: Toolbar

  • Click the Code button on the designer toolbar

The Script Window Layout

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ File  Edit  Search  Run                 โ”‚  Menu bar
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [โ–ถ Run] [โ–  Stop] [Objects โ–ผ]           โ”‚  Toolbar
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                         โ”‚
โ”‚  Script Editor (code area)              โ”‚  Main editing area
โ”‚                                         โ”‚
โ”‚  procedure MemoHeaderOnBeforePrint...  โ”‚
โ”‚  begin                                  โ”‚
โ”‚    ...                                  โ”‚
โ”‚                                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Messages / Errors                       โ”‚  Output panel
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Features:

  • Syntax highlighting: Keywords are colored for readability
  • Code completion: Press Ctrl+Space to see available properties/methods
  • Object dropdown: Quick navigation to different event handlers
  • Run button: Test your script before running the full report
  • Error messages: Shows compilation errors with line numbers

Script Organization

All scripts are contained in one code unit per report:

begin
  // Global initialization code (runs once at report start)
end.

// Event handlers appear below
procedure Page1OnBeforePrint(Sender: TfrxComponent);
begin
  // Page-level code
end;

procedure MemoHeaderOnBeforePrint(Sender: TfrxComponent);
begin
  // Memo-level code
end;

procedure DialogPage1OnShow(Sender: TfrxComponent);
begin
  // Dialog code
end;

PascalScript Basics

Variable Declaration

Variables must be declared at the top of each procedure:

procedure Page1OnBeforePrint(Sender: TfrxComponent);
var
  CustomerName: String;
  TotalAmount: Double;
  RecordCount: Integer;
  IsValid: Boolean;
begin
  CustomerName := 'Acme Corp';
  TotalAmount := 1500.50;
  RecordCount := 10;
  IsValid := True;
end;

Data Types

Common types:

var
  S: String;           // Text
  I: Integer;          // Whole numbers
  D: Double;           // Decimal numbers
  B: Boolean;          // True/False
  DT: TDateTime;       // Dates and times
  V: Variant;          // Any type (flexible but slower)

Operators

Arithmetic:

Result := 10 + 5;    // Addition: 15
Result := 10 - 5;    // Subtraction: 5
Result := 10 * 5;    // Multiplication: 50
Result := 10 / 5;    // Division: 2.0
Result := 10 div 5;  // Integer division: 2
Result := 10 mod 3;  // Modulus (remainder): 1

Comparison:

if Amount > 100 then ...      // Greater than
if Amount < 100 then ...      // Less than
if Amount >= 100 then ...     // Greater or equal
if Amount <= 100 then ...     // Less or equal
if Amount = 100 then ...      // Equal
if Amount <> 100 then ...     // Not equal

Logical:

if (Age > 18) and (Age < 65) then ...    // AND
if (Status = 'A') or (Status = 'P') then ...  // OR
if not IsProcessed then ...               // NOT

Control Structures

If-Then-Else:

if Amount > 1000 then
  Memo1.Text := 'High Value'
else if Amount > 500 then
  Memo1.Text := 'Medium Value'
else
  Memo1.Text := 'Low Value';

Case Statement:

case Status of
  'A': Memo1.Text := 'Active';
  'I': Memo1.Text := 'Inactive';
  'P': Memo1.Text := 'Pending';
else
  Memo1.Text := 'Unknown';
end;

For Loop:

var
  I: Integer;
  Total: Double;
begin
  Total := 0;
  for I := 1 to 10 do
    Total := Total + I;
end;

While Loop:

var
  Counter: Integer;
begin
  Counter := 0;
  while Counter < 10 do
  begin
    ShowMessage(IntToStr(Counter));
    Counter := Counter + 1;
  end;
end;

String Functions

var
  S: String;
begin
  S := 'Hello World';
  
  // Length
  ShowMessage(IntToStr(Length(S)));  // "11"
  
  // Substring
  S := Copy('Hello World', 1, 5);    // "Hello"
  
  // Position
  I := Pos('World', S);               // 7
  
  // Uppercase/Lowercase
  S := UpperCase('hello');            // "HELLO"
  S := LowerCase('HELLO');            // "hello"
  
  // Trim spaces
  S := Trim('  hello  ');             // "hello"
  
  // Concatenation
  S := 'Hello' + ' ' + 'World';       // "Hello World"
end;

Type Conversion Functions

var
  S: String;
  I: Integer;
  D: Double;
begin
  // String to Integer
  I := StrToInt('123');
  
  // String to Float
  D := StrToFloat('123.45');
  
  // Integer to String
  S := IntToStr(123);
  
  // Float to String
  S := FloatToStr(123.45);
  
  // Variant to String
  S := VarToStr(SomeVariant);
  
  // Format numbers
  S := FormatFloat('#,##0.00', 1234.5);  // "1,234.50"
end;

Date/Time Functions

var
  D: TDateTime;
  S: String;
begin
  // Current date/time
  D := Now;           // Current date and time
  D := Date;          // Current date only
  D := Time;          // Current time only
  
  // Date arithmetic
  D := Date + 7;      // 7 days from now
  D := Date - 30;     // 30 days ago
  
  // Extract components
  I := DayOf(D);      // Day of month
  I := MonthOf(D);    // Month number
  I := YearOf(D);     // Year
  
  // Format dates
  S := FormatDateTime('mm/dd/yyyy', Date);  // "02/17/2026"
  S := FormatDateTime('dd-mmm-yyyy', Date); // "17-Feb-2026"
end;

Common Scripting Tasks

Setting Memo Text Dynamically

procedure MemoTitleOnBeforePrint(Sender: TfrxComponent);
begin
  // Set text from a variable
  TfrxMemoView(Sender).Text := 'Report for ' + Report.Variables['CustomerName'];
  
  // Or access memo directly
  Memo1.Text := 'Total: ' + FloatToStr(TotalAmount);
end;

Conditional Formatting

procedure MemoAmountOnBeforePrint(Sender: TfrxComponent);
var
  Amount: Double;
begin
  Amount := <Orders."Amount">;
  
  // Change color based on value
  if Amount > 1000 then
  begin
    TfrxMemoView(Sender).Font.Color := clRed;
    TfrxMemoView(Sender).Font.Style := [fsBold];
  end
  else
  begin
    TfrxMemoView(Sender).Font.Color := clBlack;
    TfrxMemoView(Sender).Font.Style := [];
  end;
end;

Hiding/Showing Bands

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
  // Hide band if condition is met
  if <Orders."Status"> = 'Cancelled' then
    TfrxMasterData(Sender).Visible := False;
end;

Running Totals

var
  PageTotal: Double;

procedure ReportTitleOnBeforePrint(Sender: TfrxComponent);
begin
  // Initialize at start of report
  PageTotal := 0;
end;

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
  // Accumulate as records print
  PageTotal := PageTotal + <Orders."Amount">;
end;

procedure PageFooterOnBeforePrint(Sender: TfrxComponent);
begin
  // Display total in footer
  MemoPageTotal.Text := 'Page Total: ' + FormatFloat('#,##0.00', PageTotal);
  PageTotal := 0;  // Reset for next page
end;

Alternating Row Colors

var
  RowNumber: Integer;

procedure ReportTitleOnBeforePrint(Sender: TfrxComponent);
begin
  RowNumber := 0;
end;

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
  RowNumber := RowNumber + 1;
  
  if (RowNumber mod 2) = 0 then
    TfrxMasterData(Sender).Color := clSilver
  else
    TfrxMasterData(Sender).Color := clWhite;
end;

Report Events

Main Report Events

OnStartReport

Fires once when the report starts building.

procedure Page1OnStartReport(Sender: TfrxComponent);
begin
  // Initialize variables
  GrandTotal := 0;
  RecordCounter := 0;
  
  // Set report title
  Report.ReportOptions.Name := 'Sales Report - ' + FormatDateTime('mm/dd/yyyy', Date);
end;

OnBeforePrint

Fires before each object prints. Most commonly used event.

procedure MemoCustomerOnBeforePrint(Sender: TfrxComponent);
begin
  // Format or modify content just before printing
  if <Customers."Country"> = 'USA' then
    TfrxMemoView(Sender).Text := TfrxMemoView(Sender).Text + ' (Domestic)';
end;

OnAfterPrint

Fires after each object prints.

procedure MasterData1OnAfterPrint(Sender: TfrxComponent);
begin
  // Track what was printed
  RecordCounter := RecordCounter + 1;
end;

OnStopReport

Fires once when report finishes building.

procedure Page1OnStopReport(Sender: TfrxComponent);
begin
  // Cleanup or final calculations
  ShowMessage('Report complete. ' + IntToStr(RecordCounter) + ' records printed.');
end;

Band Events

All bands (Header, MasterData, GroupHeader, etc.) support:

  • OnBeforePrint: Before the band prints
  • OnAfterPrint: After the band prints
  • OnAfterData: After data is fetched but before printing
procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent);
begin
  // Start new page for each group
  if Engine.CurY > 0 then
    Engine.NewPage;
end;

Accessing Report Objects

Finding Objects by Name

procedure DialogPage1OnShow(Sender: TfrxComponent);
var
  MyMemo: TfrxMemoView;
  MyQuery: TfrxFDQuery;
begin
  // Find a memo
  MyMemo := TfrxMemoView(Report.FindObject('Memo1'));
  if Assigned(MyMemo) then
    MyMemo.Text := 'Hello';
  
  // Find a query
  MyQuery := TfrxFDQuery(Report.FindObject('qryCustomers'));
  if Assigned(MyQuery) then
    MyQuery.Open;
end;

Accessing Dataset Fields

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
var
  CustomerName: String;
  OrderAmount: Double;
begin
  // Direct field access
  CustomerName := <Customers."CompanyName">;
  OrderAmount := <Orders."Amount">;
  
  // Or using dataset
  CustomerName := VarToStr(Report.GetDataset('Customers').Value['CompanyName']);
end;

Modifying Object Properties

procedure Memo1OnBeforePrint(Sender: TfrxComponent);
var
  M: TfrxMemoView;
begin
  M := TfrxMemoView(Sender);
  
  // Font properties
  M.Font.Name := 'Arial';
  M.Font.Size := 12;
  M.Font.Color := clBlue;
  M.Font.Style := [fsBold, fsItalic];
  
  // Alignment
  M.HAlign := haCenter;
  M.VAlign := vaCenter;
  
  // Background
  M.Color := clYellow;
  
  // Frame
  M.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
  M.Frame.Width := 2;
end;

Working with Data

Opening and Closing Queries

procedure DialogPage1OnCloseQuery(Sender: TfrxComponent; var CanClose: Boolean);
var
  Query: TfrxFDQuery;
begin
  Query := TfrxFDQuery(Report.FindObject('qryOrders'));
  
  if Assigned(Query) then
  begin
    Query.Close;
    Query.ParamByName('CustomerID').Value := DBLookupComboBox1.KeyValue;
    Query.Open;
  end;
end;

Looping Through Dataset

procedure Page1OnStartReport(Sender: TfrxComponent);
var
  DS: TfrxDataSet;
  Total: Double;
begin
  Total := 0;
  DS := Report.GetDataset('Orders');
  
  if Assigned(DS) then
  begin
    DS.First;
    while not DS.Eof do
    begin
      Total := Total + DS.Value['Amount'];
      DS.Next;
    end;
  end;
  
  Report.Variables['GrandTotal'] := Total;
end;

Checking for Null Values

procedure Memo1OnBeforePrint(Sender: TfrxComponent);
var
  V: Variant;
begin
  V := <Customers."Phone">;
  
  if VarIsNull(V) or VarIsEmpty(V) then
    TfrxMemoView(Sender).Text := 'N/A'
  else
    TfrxMemoView(Sender).Text := VarToStr(V);
end;

Complete Examples

Example 1: Invoice Report with Calculations

var
  InvoiceTotal: Double;
  TaxRate: Double;

procedure ReportTitleOnBeforePrint(Sender: TfrxComponent);
begin
  // Initialize
  InvoiceTotal := 0;
  TaxRate := 0.08;  // 8% tax
  
  // Set invoice date
  MemoInvoiceDate.Text := FormatDateTime('mm/dd/yyyy', Date);
end;

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
var
  LineTotal: Double;
begin
  // Calculate line total
  LineTotal := <InvoiceItems."Quantity"> * <InvoiceItems."UnitPrice">;
  
  // Display formatted
  MemoLineTotal.Text := FormatFloat('$#,##0.00', LineTotal);
  
  // Add to invoice total
  InvoiceTotal := InvoiceTotal + LineTotal;
end;

procedure ReportSummaryOnBeforePrint(Sender: TfrxComponent);
var
  Tax: Double;
  GrandTotal: Double;
begin
  // Calculate tax and grand total
  Tax := InvoiceTotal * TaxRate;
  GrandTotal := InvoiceTotal + Tax;
  
  // Display
  MemoSubtotal.Text := FormatFloat('$#,##0.00', InvoiceTotal);
  MemoTax.Text := FormatFloat('$#,##0.00', Tax);
  MemoGrandTotal.Text := FormatFloat('$#,##0.00', GrandTotal);
end;

Example 2: Conditional Section Display

procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent);
var
  Category: String;
begin
  Category := <Products."Category">;
  
  // Show different header image based on category
  if Category = 'Electronics' then
    PictureHeader.Picture.LoadFromFile('C:\Images\electronics.jpg')
  else if Category = 'Clothing' then
    PictureHeader.Picture.LoadFromFile('C:\Images\clothing.jpg')
  else
    PictureHeader.Visible := False;
end;

procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
var
  StockLevel: Integer;
begin
  StockLevel := <Products."InStock">;
  
  // Highlight low stock items
  if StockLevel < 10 then
  begin
    MemoProductName.Font.Color := clRed;
    MemoProductName.Font.Style := [fsBold];
    MemoStock.Text := 'LOW STOCK: ' + IntToStr(StockLevel);
  end
  else
  begin
    MemoProductName.Font.Color := clBlack;
    MemoProductName.Font.Style := [];
    MemoStock.Text := IntToStr(StockLevel);
  end;
end;

Example 3: Complex Dialog Validation

procedure DialogPage1OnShow(Sender: TfrxComponent);
var
  ds: TfrxDataSet;
begin
  // Set default date range
  DateEditStart.Date := Date - 30;
  DateEditEnd.Date := Date;
  
  // Load customer list
  qryCustomers.Open;
  
  // Select first customer
  ds := DBLookupComboBoxCustomer.ListSource.DataSet;
  if Assigned(ds) and (ds.RecordCount > 0) then
  begin
    ds.First;
    DBLookupComboBoxCustomer.KeyValue := ds.Value['CustomerID'];
  end;
  
  // Set default status
  if ComboBoxStatus.Items.Count > 0 then
    ComboBoxStatus.ItemIndex := 0;
end;

procedure DialogPage1OnCloseQuery(Sender: TfrxComponent; var CanClose: Boolean);
var
  MinAmount: Double;
begin
  CanClose := False;  // Assume failure until all validation passes
  
  // Validate customer selection
  if VarIsNull(DBLookupComboBoxCustomer.KeyValue) then
  begin
    ShowMessage('Please select a customer');
    Exit;
  end;
  
  // Validate date range
  if DateEditStart.Date > DateEditEnd.Date then
  begin
    ShowMessage('Start date must be before end date');
    Exit;
  end;
  
  // Validate minimum amount
  try
    MinAmount := StrToFloat(EditMinAmount.Text);
    if MinAmount < 0 then
    begin
      ShowMessage('Minimum amount cannot be negative');
      Exit;
    end;
  except
    ShowMessage('Please enter a valid minimum amount');
    Exit;
  end;
  
  // All validation passed - set query parameters
  qryOrders.Close;
  qryOrders.ParamByName('CustomerID').Value := DBLookupComboBoxCustomer.KeyValue;
  qryOrders.ParamByName('StartDate').Value := DateEditStart.Date;
  qryOrders.ParamByName('EndDate').Value := DateEditEnd.Date;
  qryOrders.ParamByName('Status').Value := ComboBoxStatus.Text;
  qryOrders.ParamByName('MinAmount').Value := MinAmount;
  qryOrders.Open;
  
  CanClose := True;
end;

Example 4: Page Numbering with Conditions

var
  SectionPage: Integer;

procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent);
begin
  // Reset page counter for each section
  SectionPage := 1;
  
  // Start new page for each group
  if Engine.CurY > 0 then
    Engine.NewPage;
end;

procedure PageFooterOnBeforePrint(Sender: TfrxComponent);
var
  Category: String;
begin
  Category := <Products."Category">;
  
  // Custom page numbering
  MemoPageNumber.Text := Category + ' - Page ' + IntToStr(SectionPage) + 
                        ' (Overall: ' + IntToStr(<Page>) + ' of ' + IntToStr(<TotalPages>) + ')';
  
  SectionPage := SectionPage + 1;
end;