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.
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
| Event | When It Fires | Typical Use |
|---|---|---|
Initializing | Before init() super(). | Pre-init configuration. |
Initialized | After init() completes. | Post-init setup, control state. |
Closing | Form is about to close. | Cleanup, refresh caller. |
Activated | Form receives focus. | Refresh data on reactivation. |
TabChanged | Active 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
| Event | When It Fires | Typical Use |
|---|---|---|
Initialized | After DS.init(). | Add dynamic ranges post-init. |
QueryExecuted | After DS.executeQuery(). | Post-query processing, update counts. |
SelectionChanged | After DS.active() — record changed. | Update controls for current record. |
Written | After record saved (insert or update). | Post-save processing, logging. |
Deleted | After record deleted. | Cleanup, refresh related data. |
Created | After new record created. | Set custom defaults. |
ValidatingWrite | During DS.validateWrite(). | Add write validation. |
ValidatedWrite | After DS.validateWrite() returns. | Post-validation logic. |
ValidatingDelete | During DS.validateDelete(). | Add delete validation. |
ValidatedDelete | After 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
| Event | When It Fires | Typical Use |
|---|---|---|
Modified | After field value changes. | Cascade updates, lookups. |
Validating | Before field validation. | Add pre-validation logic. |
Validated | After 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
| Event | When It Fires | Typical Use |
|---|---|---|
Clicked | Button is clicked. | Execute business logic. |
Modified | Field control value changes. | React to user input. |
Validated | After field control validation. | Additional validation. |
Enter | Control receives focus. | Show context-sensitive help. |
Leave | Control loses focus. | Trigger post-edit processing. |
GotFocus | Control gets keyboard focus. | UI state updates. |
LostFocus | Control 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
| Level | Attribute | Key Parameter | Delegate Signature |
|---|---|---|---|
| Form | [FormEventHandler] | formStr(), FormEventType | static void(xFormRun, FormEventArgs) |
| Data Source | [FormDataSourceEventHandler] | formDataSourceStr(), FormDataSourceEventType | static void(FormDataSource, FormDataSourceEventArgs) |
| Data Source Field | [FormDataFieldEventHandler] | formDataFieldStr(), FormDataFieldEventType | static void(FormDataObject, FormDataFieldEventArgs) |
| Control | [FormControlEventHandler] | formControlStr(), FormControlEventType | static void(FormControl, FormControlEventArgs) |