The event-driven architecture of Acumatica delivers a rich user experience. As a new Acumatica developer, a few event handlers seemed to float to the top, but even after 19 months, the power and proper use of events can prove to be challenging. Some things come pretty quickly, like using RowSelected to enable/disable elements of the user interface, but some things… not so much.
The Acumatica help provides a great, albeit a little confusing to a beginner, graphic on the event handlers. Across the top is the action being performed on the Cache. (These are the CRUD actions on the Cache + Save/Persist to the database.) Each of the blocks in the graphic shows where the possible event handlers are executed. As you can see, there are 15 event handlers for row and field level events included in Acumatica. (RowSelecting is missing from the graphic.) While knowing what they are and when they fire is great, knowing what to do with them would be even better. This is the challenge I face most often every time I write code. In this post, I’ll describe some of the lessons I’ve learned about many of the event handlers, although I won’t be describing all of them here.

Source: https://help-2019r2.acumatica.com/Help?ScreenId=ShowWiki&pageid=d9cf6274-f5c8-43e7-9d13-9b423113d67e
RowSelected
The RowSelected event handler is used primarily to control the user interface, although you may also need to populate unbound fields in the DAC in this event handler if the situation calls for it. In my earliest development, I made the mistake of setting values in other fields or DAC’s in this event handler. While I can’t say with certainty that you never should be setting values in this case, I can’t think of any legitimate cases in my experience so far.
FieldDefaulting
The FieldDefaulting event handler baffled me for a while. The DAC provides a way to set a default value on a field, so it took a while for me to understand the value of FieldDefaulting. The event fires when inserting a row (creating a record in the cache) which for me rarely involved having enough data in the record to know what to default. The magic that I was missing was the SetDefaultExt<T>() method of the Cache. By defining the way to determine the value in a FieldDefaulting event handler, you can invoke the event from another event handler when you have the necessary data to set the value.
For me, it seemed easier to Set the value in a RowUpdating event handler, but that was just wrong. Instead, invoke e.Cache.SetDefaultExt<T>() in the FieldUpdated event that captures the data change that means a new default might be in order. ExtCost might be handled by a formula on the field in the DAC, but what if the UnitCost is based on price breaks? When you detect an update to the QtyOrdered field, you might use SetDefaultExt to recalculate the UnitCost based on the price breaks. See below for an example that watches for a change in the ExampleCD field and then triggers the FieldDefaulting event on Descr using our DAC from an earlier post… BBExampleDAC.
#region Event Handlers
#region BBExampleDAC_Descr_FieldDefaulting
protected virtual void _(Events.FieldDefaulting<BBExampleDAC.descr> e)
{
BBExampleDAC row = (BBExampleDAC)e.Row;
e.NewValue = string.Format("Description for {0}", row.ExampleCD);
}
#endregion
#region BBExampleDAC_ExampleCD_FieldUpdated
protected virtual void _(Events.FieldUpdated<BBExampleDAC.exampleCD> e)
{
e.Cache.SetDefaultExt<BBExampleDAC.descr>(e.Row);
}
#endregion
#endregion
FieldSelecting
Sometimes, advanced business logic is required to determine the value for the field. To perform this advanced business logic, use the FieldSelecting event handler and populate e.ReturnValue with the value. See the example below from the POOrderEntry graph.
#region SOLineSplit3 events
protected virtual void SOLineSplit3_POUOM_FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
SOLineSplit3 soline = (SOLineSplit3)e.Row;
if (soline == null) return;
POLine orig_line = PXSelect<POLine, Where<POLine.orderType, Equal<Current<SOLineSplit3.pOType>>,
And<POLine.orderNbr, Equal<Current<SOLineSplit3.pONbr>>,
And<POLine.lineNbr, Equal<Current<SOLineSplit3.pOLineNbr>>>>>>.SelectSingleBound(this, new object[] { soline });
e.ReturnValue = (orig_line != null && orig_line.UOM != null) ? orig_line.UOM : soline.UOM;
}
FieldUpdating/FieldVerifying
While FieldVerifying seems by name to be where you verify the field, I found myself doing more validations in FieldUpdating. My issue stems from FieldVerifying providing the value from the UI rather than the value that will be stored in the database. That means the InventoryCD value which is the substitute key for InventoryID is what FieldVerifying provides as the NewValue. FieldUpdating, however, provides direct access to the InventoryID value in this case. This is a bad practice that will cost me a fair amount of time to correct in my existing code.
FieldUpdating is used to override the value to be stored, such as in ARInvoiceEntry in the ARAdjust.CuryDocBal field. FieldVerifying is used to ensure the value entered is valid and present errors back to the UI.
RowPersisting
While it seems obvious that you might want to validate your data and provide realtime feedback through the UI, some complex validations cannot be performed efficiently at the field level, such as in FieldVerifying or FieldUpdated. You need to know that all data entry is complete and ensure that the entire record is valid in whole. The RowPersisting event handler provides a way to do that “last ditch effort” to ensure data integrity from the UI before saving the record.
RowPersisted
Sometimes you need to execute “follow-on” code when a record is saved. You don’t want to perform the additional actions until you know the record was saved without error. If you want to perform the action for each row that is saved, this is the place to do it. An example is related data in other DAC’s that need to be updated when this DAC is updated. If you want to perform the “follow-on” action once when the Save button is pressed, do not do that here. Instead, override the Persist method to add your additional business logic.