Dynamics 365 Business Central: Add a Scan Barcode action and scan multiple/Continious scanning (Invoke barcode scanning programmatically from AL)

Dynamics 365 Business Central

Hi, Readers.
Today I would like to talk about how to add a Scan Barcode action and scan multiple/Continious scanning in Business Central Saas (Cloud).

In Business Central 2023 wave 2 (BC23): Print and scan barcodes (No customization), we briefly discussed how to print and scan barcodes (No customization) in Business Central.

The simplest way to provide barcode scanning capability in the mobile app is by adding a barcode scanning button on a field that starts the barcode scanner capability of the device’s camera.

This scanning is highly efficient and responsive. Once a barcode is scanned, its value is entered in the field on the page, and the focus moves to the next quick-entry field on the page.

To enable the barcode scanning button on a field, set the ExtendedDatatype property to Barcode. You can set ExtendedDatatype on either the field in the table or page. The property instructs the mobile client to display the barcode button when the page is opened on a supported device. For example,

tableextension 50111 ItemExt extends Item
{
    fields
    {
        field(50100; ItemBarcode; Text[50])
        {
            Caption = 'Item Barcode';
            ExtendedDatatype = Barcode;
        }
    }
}
pageextension 50111 ItemCardExt extends "Item Card"
{
    layout
    {
        addafter(Description)
        {
            field(ItemBarcode; Rec.ItemBarcode)
            {
                ApplicationArea = All;
                Caption = 'Item Barcode';
                ToolTip = 'Item Barcode';
            }
        }
    }
}

Regarding AL action and Barcode event, they were only available in the On-Pre version at that time.
For example, in page 6510 “Item Tracking Lines”:

In Business Central 2023 wave 2 (BC23):

DotNet Data Type:

The really great news is that version 24 introduced barcode scanning APIs based on control add-ins​ to replace the .NET-based APIs.

CameraBarcodeScannerProviderAddIn: The control add-in for the camera barcode scanner provider.

This means we can add scan barcode actions and multiple scanning functions to our own extensions🎆🎉🎊.

The following code example shows how to invoke barcode scanning using the CameraBarcodeScannerProviderAddIn control add-in. More details: Invoke barcode scanning using control add-in API

page 50100 "Camera Barcode Scanner"
{
    PageType = Card;
    ApplicationArea = All;
    Caption = 'Camera Barcode Scanner Sample';

    layout
    {
        area(Content)
        {
            // Declare the user control based on the CameraBarcodeScannerProviderAddIn control add-in
            usercontrol(BarcodeControl; CameraBarcodeScannerProviderAddIn) // Step 1
            {
                ApplicationArea = All;

                // The ControlAddInReady event is raised when the control add-in is ready to be used. 
                trigger ControlAddInReady(IsSupported: Boolean)
                begin
                    // Set the CameraBarcodeScannerAvailable variable to the value of the IsSupported parameter
                    CameraBarcodeScannerAvailable := IsSupported; // Step 2
                end;

                // The BarcodeAvailable event is raised when a barcode is available.    
                trigger BarcodeAvailable(Barcode: Text; Format: Text)
                begin
                    Message(Barcode);// Step 4

                    // Continious scanning                    
                    // CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 5
                end;

                // The BarcodeFailure event is raised on a failure
                trigger BarcodeFailure(Reason: Enum BarcodeFailure)
                begin
                    case Reason of
                        Reason::Cancel:
                            Message('Canceled');
                        Reason::NoBarcode:
                            Message('No Barcode');
                        Reason::Error:
                            Message('Error');
                    end;
                end;
            }
        }
    }

    actions
    {
        area(Processing)
        {
            action(ScanBarcode)
            {
                ApplicationArea = All;

                trigger OnAction()
                begin
                    // Request a barcode
                    CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 3
                end;
            }
        }
    }

    var
        CameraBarcodeScannerAvailable: Boolean;
}

Let’s do a specific test. First, let’s look at an example of a single scan (I simply added the promoted properties to the action).
The scanned results are displayed on the page as a message.

page 50100 "Camera Barcode Scanner"
{
    PageType = Card;
    ApplicationArea = All;
    Caption = 'Camera Barcode Scanner Sample';
    UsageCategory = Administration;

    layout
    {
        area(Content)
        {
            // Declare the user control based on the CameraBarcodeScannerProviderAddIn control add-in
            usercontrol(BarcodeControl; CameraBarcodeScannerProviderAddIn) // Step 1
            {
                ApplicationArea = All;

                // The ControlAddInReady event is raised when the control add-in is ready to be used. 
                trigger ControlAddInReady(IsSupported: Boolean)
                begin
                    // Set the CameraBarcodeScannerAvailable variable to the value of the IsSupported parameter
                    CameraBarcodeScannerAvailable := IsSupported; // Step 2
                end;

                // The BarcodeAvailable event is raised when a barcode is available.    
                trigger BarcodeAvailable(Barcode: Text; Format: Text)
                begin
                    Message(Barcode);// Step 4

                    // Continious scanning                    
                    //CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 5
                end;

                // The BarcodeFailure event is raised on a failure
                trigger BarcodeFailure(Reason: Enum BarcodeFailure)
                begin
                    case Reason of
                        Reason::Cancel:
                            Message('Canceled');
                        Reason::NoBarcode:
                            Message('No Barcode');
                        Reason::Error:
                            Message('Error');
                    end;
                end;
            }
        }
    }

    actions
    {
        area(Processing)
        {
            action(ScanBarcode)
            {
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                Caption = 'Scan Barcode';

                trigger OnAction()
                begin
                    // Request a barcode
                    CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 3
                end;
            }
        }
    }

    var
        CameraBarcodeScannerAvailable: Boolean;
}

Test:

Test video:

Enabling multiple scans is also very simple; just remove the comments from step 5 in the sample.

PS: There are two other overload functions available; you can refer to the standard code.

Test code:

page 50100 "Camera Barcode Scanner"
{
    PageType = Card;
    ApplicationArea = All;
    Caption = 'Camera Barcode Scanner Sample';
    UsageCategory = Administration;

    layout
    {
        area(Content)
        {
            // Declare the user control based on the CameraBarcodeScannerProviderAddIn control add-in
            usercontrol(BarcodeControl; CameraBarcodeScannerProviderAddIn) // Step 1
            {
                ApplicationArea = All;

                // The ControlAddInReady event is raised when the control add-in is ready to be used. 
                trigger ControlAddInReady(IsSupported: Boolean)
                begin
                    // Set the CameraBarcodeScannerAvailable variable to the value of the IsSupported parameter
                    CameraBarcodeScannerAvailable := IsSupported; // Step 2
                end;

                // The BarcodeAvailable event is raised when a barcode is available.    
                trigger BarcodeAvailable(Barcode: Text; Format: Text)
                begin
                    Message(Barcode);// Step 4

                    // Continious scanning                    
                    CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 5
                end;

                // The BarcodeFailure event is raised on a failure
                trigger BarcodeFailure(Reason: Enum BarcodeFailure)
                begin
                    case Reason of
                        Reason::Cancel:
                            Message('Canceled');
                        Reason::NoBarcode:
                            Message('No Barcode');
                        Reason::Error:
                            Message('Error');
                    end;
                end;
            }
        }
    }

    actions
    {
        area(Processing)
        {
            action(ScanBarcode)
            {
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                Caption = 'Scan Barcode';

                trigger OnAction()
                begin
                    // Request a barcode
                    CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 3
                end;
            }
        }
    }

    var
        CameraBarcodeScannerAvailable: Boolean;
}

Test video: Sometimes it displays the previous result, I’m not sure if it’s a bug.🤔Maybe it’s just a problem with my phone.

Let’s look at a practical example: add a scan action to the Sales Order page to scan the Lot No. and add the corresponding item number for the Lot No. to sales lines. (Since there’s no standard function to print the item number’s barcode, I used Lot No.)

Test video:

Great, this method can accomplish many things. Give it a try!!!😁

Source code: Github (Please note that the source code is for reference only, you can improve it according to your own needs)

pageextension 50128 SalesOrderExt extends "Sales Order"
{
    layout
    {
        addbefore(General)
        {
            // Declare the user control based on the CameraBarcodeScannerProviderAddIn control add-in
            usercontrol(BarcodeControl; CameraBarcodeScannerProviderAddIn) // Step 1
            {
                ApplicationArea = All;

                // The ControlAddInReady event is raised when the control add-in is ready to be used. 
                trigger ControlAddInReady(IsSupported: Boolean)
                begin
                    // Set the CameraBarcodeScannerAvailable variable to the value of the IsSupported parameter
                    CameraBarcodeScannerAvailable := IsSupported; // Step 2
                end;

                // The BarcodeAvailable event is raised when a barcode is available.    
                trigger BarcodeAvailable(Barcode: Text; Format: Text)
                var
                    SalesLine: Record "Sales Line";
                    LotNoInfo: Record "Lot No. Information";
                begin
                    //Message(Barcode);// Step 4
                    LotNoInfo.Reset();
                    LotNoInfo.SetRange("Lot No.", Barcode);
                    if LotNoInfo.FindFirst() then begin
                        SalesLine.Init();
                        SalesLine.Validate("Document Type", Rec."Document Type");
                        SalesLine.Validate("Document No.", Rec."No.");
                        SalesLine.Validate("Line No.", GetSalesLineLastLineNo(Rec) + 10000);
                        SalesLine.Validate("Type", SalesLine.Type::Item);
                        SalesLine.Validate("No.", LotNoInfo."Item No.");
                        SalesLine.Validate("Quantity", 1);
                        SalesLine.Insert(true);
                    end else
                        Message('Lot No. %1 not found.', Barcode);

                    // Continious scanning                    
                    CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 5
                end;

                // The BarcodeFailure event is raised on a failure
                trigger BarcodeFailure(Reason: Enum BarcodeFailure)
                begin
                    case Reason of
                        Reason::Cancel:
                            Message('Canceled');
                        Reason::NoBarcode:
                            Message('No Barcode');
                        Reason::Error:
                            Message('Error');
                    end;
                end;
            }
        }
    }

    actions
    {
        addbefore(Post)
        {
            action(ScanBarcode)
            {
                ApplicationArea = All;
                Promoted = true;
                PromotedCategory = Process;
                Caption = 'Scan Barcode';
                Image = ViewDocumentLine;

                trigger OnAction()
                begin
                    // Request a barcode
                    CurrPage.BarcodeControl.RequestBarcodeAsync(); // Step 3
                end;
            }
        }
    }

    var
        CameraBarcodeScannerAvailable: Boolean;

    local procedure GetSalesLineLastLineNo(var SalesHeader: Record "Sales Header"): Integer
    var
        SalesLine: Record "Sales Line";
    begin
        SalesLine.Reset();
        SalesLine.SetRange("Document Type", SalesHeader."Document Type");
        SalesLine.SetRange("Document No.", SalesHeader."No.");
        SalesLine.SetCurrentKey("Line No.");
        if SalesLine.FindLast() then
            exit(SalesLine."Line No.");
        exit(0);
    end;
}

PS: You can still use the .NET-based APIs but the control add-in APIs are the recommended way to implement barcode scanning capability going forward.

END

Hope this will help.

Thanks for reading.

ZHU

コメント