You can override a .NET abstract event (defined with the C# abstract option) inherited by an ABL class. The rules for overriding a .NET abstract event are very similar to overriding an ABL abstract event using the OVERRIDE option of the DEFINE EVENT statement. For example, the overriding ABL event must have an ABL access mode that is equivalent to, or less restrictive than, the access level of the overridden .NET abstract event. The overriding ABL event can also be defined as abstract using the ABSTRACT option as long as the inheriting ABL class is also abstract. However, as for any ABL abstract event, the first non-abstract ABL subclass must implement the inherited .NET abstract event unless an abstract ABL subclass implements the event higher in the class hierarchy.
To define the same signature as the .NET abstract event, you must use the DELEGATE option of the ABL DEFINE EVENT statement to specify the same .NET delegate type that is used to define the .NET abstract event. For more information on .NET delegate types, see Handling .NETevents.
When you implement a .NET abstract event in an ABL class, you can directly publish the implemented event like any non-abstract ABL event defined in the overriding ABL class. For more information, see Managing events for ABL-derived .NET classes.
For example, you might have an ABL class implement the .NET abstract event, DataEvent, from the .NET abstract super class, CheckScanner, which is in the Microsoft.PointOfService namespace provided by Microsoft Point of Service for .NET as described on MSDN:
public abstract event DataEventHandler DataEvent
Among other abstract members, you have to implement the RetrieveImage( ) method to get a check image from a particular check scanning device and the ImageData property to hold the scanned check image, the prototypes for which are defined in CheckScanner as follows:
public abstract void RetrieveImage ( int cropAreaId )
public abstract Bitmap ImageData { get; }
The DataEvent event is raised when a scanned check image is made available to the application. So, you might inherit the .NET CheckScanner class and implement this event (along with its other abstract members) in an ABL class defined as in the following CheckProcessing.cls class file fragment.
Note that one reason for .NET providing an abstract class, in this case, is to accommodate any possible scanning device that a given check scanning application needs to support.
This fragment starts out showing the CheckProcessing class inheriting the .NET CheckScanner class, defining some variable data members to hold the event arguments and a scanning status indicator, and implementing the abstract DataEvent event (with reference to the DataEventHandler delegate), the abstract ImageData property, and the abstract RetrieveImage( ) method for getting and storing check images in the ImageData property.
The public RetrieveImage( ) method enters a loop to retrieve scanned check images and publish DataEvent for each scan using the private ScanCheck( ) method, possibly with the help of other members of the .NET abstract CheckScanner class (not shown). Note that DataEvent is published by ScanCheck( ) regardless if check scanning input has ended or if a valid check image is available, leaving it to any subscribed event handler to process the results.
USING Microsoft.PointOfService.* FROM ASSEMBLY.
USING System.Drawing.* FROM ASSEMBLY.
CLASS CheckProcessing INHERITS CheckScanner:
/* Variables for event arguments and status */
DEFINE PRIVATE VARIABLE rEventArgs AS CLASS DataEventArgs NO-UNDO.
DEFINE PRIVATE VARIABLE iStat AS INTEGER NO-UNDO.
/* Implement abstract members from CheckScanner abstract class */
DEFINE PUBLICOVERRIDE EVENT DataEvent DELEGATE CLASS DataEventHandler.
DEFINE PUBLIC PROPERTY OVERRIDE ImageData AS Bitmap NO-UNDO
GET...
SET...
METHOD PUBLIC OVERRIDE VOID RetrieveImage ( INPUT piCropAreaID AS INTEGER ):
/* Get first check image and publish DataEvent */
THIS-OBJECT:ScanCheck( piCropAreaID ).
/* Loop to get more scanned check images and publish
DataEvent while device is scanning checks */
iStat = rEventArgs.Status. /* Status from DataEvent arguments */
DO WHILE iStat = 0: /* Check scanner input is still available */
...
THIS-OBJECT:ScanCheck( piCropAreaID ).
iStat = rEventArgs.Status. END.
END METHOD.
/* Try to retrieve a check image and publish DataEvent with the result */
METHOD PRIVATE OVERRIDE VOID ScanCheck ( INPUT piCropAreaID AS INTEGER ):
IF /* device scanning check input */ THEN DO:
rEventArgs = NEW DataEventArgs( 0 ). /* Device is scanning checks */ IF /* check image available */ THEN DO:
THIS-OBJECT:ImageData = /* Get check image from scanner */. /* May be do something with piCropAreaID */
...
END.
ELSE
THIS-OBJECT:ImageData = ?. /* No image available */ END.
ELSE
ASSIGN
rEventArgs = NEW DataEventArgs( 1 ) /* Check scanning has ended */ THIS-OBJECT:ImageData = ?.
When executed, the private ScanCheck( ) method tests the check scanning device input status and instantiates the event arguments class (DataEventArgs) accordingly, passing the status value to the constructor and assigning its object reference to rEventArgs. It then gets the check image, if one is available, and assigns it to the ImageData property, or sets ImageData to the Unknown value (?) if a check image is not available. Finally, the method publishes DataEvent, passing parameters with the results according to the DataEventHandler delegate.
The following ProcessCheckImages.p is a procedure fragment that demonstrates how an ABL application might use the CheckProcessing class and its DataEvent event to process check images. The procedure first creates an instance of CheckProcessing, assigning its object reference to rScanner. The procedure then subscribes the internal procedure, DataEvent_CheckHandler, as a handler for the DataEvent on rScanner. Finally, it then calls RetrieveImage( ) on rScanner to retrieve the image and publish the DataEvent for each scanned check.
The DataEvent_CheckHandler procedure is defined with a signature that is compatible with the signature specified by the DataEventHandler delegate, including the input System.Object (prSender), which is the instance of CheckProcessing that published the event, and the input DataEventArgs (prArgs), which is the specified event arguments class instance.
USING Microsoft.PointOfService.* FROM ASSEMBLY.
USING System.Drawing.* FROM ASSEMBLY.
DEFINE VARIABLE rCheck AS CLASS Bitmap NO-UNDO.
DEFINE VARIABLE rScanner AS CLASS CheckProcessing NO-UNDO.
rScanner = NEW CheckProcessing( ). rScanner:DataEvent:Subscribe( DataEvent_CheckHandler ).
/* Process any valid check image returned by the ImageData property */
iStat = prArgs:Status.
IF iStat = 0 THEN /* Device is still scanning checks */
IF VALID-OBJECT( rScanInstance:ImageData ) THEN DO:
rCheck = rScanInstance:ImageData. /* Process check image */
...
END. /* Otherwise, ignore this event without a check image */
END.
ELSE DO: /* Report scanning has ended and exit the procedure */
MESSAGE "Check scanning has ended with a status of" iStat
VIEW-AS ALERT-BOX.
END.
END PROCEDURE.
When a DataEvent is published on rScanner (during execution of the RetrieveImage( ) method), the DataEvent_CheckHandler procedure executes, first casting prSender to a CheckProcessing object reference (rScanInstance) in order to access CheckProcessing public members for the current scan result, especially the ImageData property, which holds any check image data. The event handler then checks the Status property on prArgs for the scanner input status and the ImageData property on rScanInstance to see if it holds a valid check image.
If a check image is available, the event handler processes the image. If the device is still scanning checks, but no check image is available for this event, the handler ignores the event. In either of these cases, the procedure continues with the next DataEvent to be published. If the scanner has scanned its last check, or otherwise stopped scanning, the handler displays a status message and RetrieveImage( ) also returns along with ProcessCheckImages.p.