Skip to main content

Form Event Handlers

Event handlers allow you to subscribe to lifecycle events on forms without modifying the base code. They are defined as static methods decorated with event attributes. Form event handlers operate at four levels: form, data source, data source field, and control.

tip

Naming convention: Name the handler class <Prefix><FormName>EventHandlers (e.g., SAMOSalesTableEventHandlers). Group all handlers for one form in a single class.


Form-Level Events

Register with [FormEventHandler(formStr(FormName), FormEventType::EventName)].

[FormEventHandler(formStr(SalesTable), FormEventType::Initialized)]
public static void SalesTable_OnInitialized(xFormRun _sender, FormEventArgs _e)
{
FormRun formRun = _sender as FormRun;
// Post-init setup: access controls, set state
}

FormEventType Reference

EventWhen It FiresTypical Use
InitializingBefore init() super().Pre-init configuration.
InitializedAfter init() completes.Post-init setup, control state.
ClosingForm is about to close.Cleanup, refresh caller.
ActivatedForm receives focus.Refresh data on reactivation.
TabChangedActive tab changes.Lazy-load tab content.

Example: Post-Initialisation Setup

class SAMOSalesTableEventHandlers
{
[FormEventHandler(formStr(SalesTable), FormEventType::Initialized)]
public static void SalesTable_OnInitialized(xFormRun _sender, FormEventArgs _e)
{
FormRun formRun = _sender as FormRun;
FormControl tierControl = formRun.design().controlName('SAMOCustTier');

if (tierControl)
{
tierControl.visible(SAMOFeatureToggle::isEnabled());
}
}
}

Data Source Events

Register with [FormDataSourceEventHandler(formDataSourceStr(FormName, DataSourceName), FormDataSourceEventType::EventName)].

[FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesTable), FormDataSourceEventType::QueryExecuted)]
public static void SalesTable_OnQueryExecuted(FormDataSource _sender, FormDataSourceEventArgs _e)
{
// Runs after executeQuery() completes
}

FormDataSourceEventType Reference

EventWhen It FiresTypical Use
InitializedAfter DS.init().Add dynamic ranges post-init.
QueryExecutedAfter DS.executeQuery().Post-query processing, update counts.
SelectionChangedAfter DS.active() — record changed.Update controls for current record.
WrittenAfter record saved (insert or update).Post-save processing, logging.
DeletedAfter record deleted.Cleanup, refresh related data.
CreatedAfter new record created.Set custom defaults.
ValidatingWriteDuring DS.validateWrite().Add write validation.
ValidatedWriteAfter DS.validateWrite() returns.Post-validation logic.
ValidatingDeleteDuring DS.validateDelete().Add delete validation.
ValidatedDeleteAfter DS.validateDelete() returns.Post-validation logic.

Example: Adding Dynamic Ranges After Query

class SAMOSalesTableEventHandlers
{
[FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesTable), FormDataSourceEventType::Initialized)]
public static void SalesTableDS_OnInitialized(FormDataSource _sender, FormDataSourceEventArgs _e)
{
QueryBuildDataSource qbds = _sender.query()
.dataSourceTable(tableNum(SalesTable));
qbds.addRange(fieldNum(SalesTable, SAMOCustTier))
.value(queryValue(SAMOCustTier::Premium));
}

[FormDataSourceEventHandler(formDataSourceStr(SalesTable, SalesTable), FormDataSourceEventType::SelectionChanged)]
public static void SalesTable_OnSelectionChanged(FormDataSource _sender, FormDataSourceEventArgs _e)
{
SalesTable salesTable = _sender.cursor();
FormRun formRun = _sender.formRun();

// Update controls based on selected record
FormControl postBtn = formRun.design().controlName('PostButton');
if (postBtn)
{
postBtn.enabled(salesTable.SalesStatus == SalesStatus::Backorder);
}
}
}

Data Source Field Events

Register with [FormDataFieldEventHandler(formDataFieldStr(FormName, DataSourceName, FieldName), FormDataFieldEventType::EventName)].

[FormDataFieldEventHandler(formDataFieldStr(SalesTable, SalesTable, CustAccount), FormDataFieldEventType::Modified)]
public static void CustAccount_OnModified(FormDataObject _sender, FormDataFieldEventArgs _e)
{
FormDataSource fds = _sender.datasource();
SalesTable salesTable = fds.cursor();
// React to CustAccount field change
}

FormDataFieldEventType Reference

EventWhen It FiresTypical Use
ModifiedAfter field value changes.Cascade updates, lookups.
ValidatingBefore field validation.Add pre-validation logic.
ValidatedAfter field validation.Add post-validation checks.

Example: Cascading Field Updates

class SAMOSalesTableEventHandlers
{
[FormDataFieldEventHandler(formDataFieldStr(SalesTable, SalesTable, CustAccount), FormDataFieldEventType::Modified)]
public static void CustAccount_OnModified(FormDataObject _sender, FormDataFieldEventArgs _e)
{
FormDataSource fds = _sender.datasource();
SalesTable salesTable = fds.cursor();

// Look up the customer tier when account changes
CustTable custTable = CustTable::find(salesTable.CustAccount);
salesTable.SAMOCustTier = custTable.SAMOCustTier;
}

[FormDataFieldEventHandler(formDataFieldStr(SalesTable, SalesTable, SAMOCustTier), FormDataFieldEventType::Validated)]
public static void SAMOCustTier_OnValidated(FormDataObject _sender, FormDataFieldEventArgs _e)
{
FormDataSource fds = _sender.datasource();
SalesTable salesTable = fds.cursor();

if (salesTable.SAMOCustTier == SAMOCustTier::Premium
&& !salesTable.CustAccount)
{
checkFailed("@SAMO:PremiumRequiresAccount");
}
}
}

Control Events

Register with [FormControlEventHandler(formControlStr(FormName, ControlName), FormControlEventType::EventName)].

[FormControlEventHandler(formControlStr(SalesTable, SAMOPostButton), FormControlEventType::Clicked)]
public static void SAMOPostButton_OnClicked(FormControl _sender, FormControlEventArgs _e)
{
FormRun formRun = _sender.formRun() as FormRun;
// Handle button click
}

FormControlEventType Reference

EventWhen It FiresTypical Use
ClickedButton is clicked.Execute business logic.
ModifiedField control value changes.React to user input.
ValidatedAfter field control validation.Additional validation.
EnterControl receives focus.Show context-sensitive help.
LeaveControl loses focus.Trigger post-edit processing.
GotFocusControl gets keyboard focus.UI state updates.
LostFocusControl loses keyboard focus.UI state updates.

Example: Button Click Handler

class SAMOSalesTableEventHandlers
{
[FormControlEventHandler(formControlStr(SalesTable, SAMOProcessButton), FormControlEventType::Clicked)]
public static void SAMOProcessButton_OnClicked(FormControl _sender, FormControlEventArgs _e)
{
FormRun formRun = _sender.formRun() as FormRun;
FormDataSource salesTableDS = formRun.dataSource(formDataSourceStr(SalesTable, SalesTable));
SalesTable salesTable = salesTableDS.cursor();

if (salesTable.RecId)
{
SAMOSalesProcessor::process(salesTable);
salesTableDS.reread();
salesTableDS.refresh();
}
}
}

Complete Event Handler Summary

LevelAttributeKey ParameterDelegate Signature
Form[FormEventHandler]formStr(), FormEventTypestatic void(xFormRun, FormEventArgs)
Data Source[FormDataSourceEventHandler]formDataSourceStr(), FormDataSourceEventTypestatic void(FormDataSource, FormDataSourceEventArgs)
Data Source Field[FormDataFieldEventHandler]formDataFieldStr(), FormDataFieldEventTypestatic void(FormDataObject, FormDataFieldEventArgs)
Control[FormControlEventHandler]formControlStr(), FormControlEventTypestatic void(FormControl, FormControlEventArgs)