Skip to Content
Technical Articles

QR code in smartforms

Most of the time in SAP the answer for many things come from a lot of sources. The QR code implementation in SAP is an example of it.

At first, I went to google and I found this excellent example (How to create a QR code and show it in a Smartform) on scn.sap.com.

But then I realized that even if I use that code I had to deal with the fact that I should be aware that the QR code had to be generated from a Google API. That could be a problem because in my case I needed to use that feature internally, so it was not ok to access the internet to get the image. On the other hand, this site could be at any moment be closed or shutdown.
So, the thing is that I had to make my own webservice to get that image, and having a background of C# I found it logical to make my own webservice. So I began to investigate how to generate the QR and when I got it then I proceeded to code it. There are a lot of open source solutions out there and in my case, I just had to grab one of them and use it. I found a couple and the ZXing.Net implementation seemed to be the best in this case.
So I had to trick it a little so I could make it to work. One requirement of this webservice was that it had to make the QR code graphic on the fly. It was not ok to save a file representing the QR code in the webserver because eventually, it would have to take some kind of job or additional work on it to delete those files. The code for the webservice is the following:
using System.Drawing.Imaging;
using System.IO;
using System.Web;
using ZXing;
using ZXing.Common;

namespace lcqrcode
{
    /// <summary>
    /// Short description of Service
    /// </summary>
    public class Service : IHttpHandler
    {
        private const string NotAvailable = "Not available";
        private const int MaxLength = 256;

        public void ProcessRequest(HttpContext context)
        {
            if (context.Request.HttpMethod != "GET") return;

            var qrValue = context.Request.QueryString["text"] ?? NotAvailable;

            qrValue = qrValue.Trim();

            if (qrValue.Length > MaxLength)
                qrValue = qrValue.Substring(0, MaxLength - 1); // Limit to 'MaxLength' characters

            var barcodeWriter = new BarcodeWriter
                                    {
                                        Format = BarcodeFormat.QR_CODE,
                                        Options = new EncodingOptions
                                                      {
                                                          Height = 200,
                                                          Width = 200,
                                                          PureBarcode = true
                                                      }
                                    };

            using (var bitmap = barcodeWriter.Write(qrValue))

            using (var mem = new MemoryStream())
            {
                bitmap.SetResolution(50, 50);

                bitmap.Save(mem, ImageFormat.Png);
                mem.Seek(0, SeekOrigin.Begin);

                context.Response.ContentType = "image/png";

                mem.CopyTo(context.Response.OutputStream, 4096);
                context.Response.Flush();
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

Create a web project and insert a generic handler file (ASHX extension) in Visual Studio, name it ‘Service’, and paste the above code. Don’t forget to include de ZXING reference in the project. I had to make use of this kind of file because I had to make the webservice restful.

The other step that I took was to make the ABAP code to easily use it in any smartform and the best way to do that was to make a BAPI. This approach would help to make use of it anytime anywhere.
So I took the code from above and encapsulated it in a BAPI. Here is the code (Taken and adapted from How to create a QR code and show it in a Smartform):
function zqrcode .
*"----------------------------------------------------------------------
*"*"Interfase local
*"  IMPORTING
*"     REFERENCE(I_TEXT) TYPE  CHAR255
*"     REFERENCE(I_NAME) TYPE  CHAR20
*"     REFERENCE(I_WIDHT) TYPE  I DEFAULT 200
*"     REFERENCE(I_HEIGHT) TYPE  I DEFAULT 200
*"  EXPORTING
*"     REFERENCE(E_CHECK) TYPE  CHAR01
*"----------------------------------------------------------------------
  perform get_qrcode using i_text changing e_check.
  check e_check is not initial.

  perform convert_image using i_name i_widht i_height changing e_check.
endfunction.
And here is the code for the Include of the BAPI (Taken and adapted from How to create a QR code and show it in a Smartform):
*&---------------------------------------------------------------------*
*&      Form  GET_QRCODE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_TEXT  text
*      -->P_CHECK text
*----------------------------------------------------------------------*
form get_qrcode using p_text type char255 changing p_check type c.
  data oref type ref to cx_root.
  data wa_source type zgen_qrsource.
  select single * from zgen_qrsource into wa_source. " Get webservice address for QR code generation

  check sy-subrc eq 0.

  concatenate wa_source-address p_text into url.

  call method cl_http_client=>create_by_url
    exporting
      url                = url
    importing
      client             = http_client
    exceptions
      argument_not_found = 1
      plugin_not_active  = 2
      internal_error     = 3
      others             = 4.

  if sy-subrc = 0.

*    http_client->send( ).
    call method http_client->send
    exceptions
      http_communication_failure = 1
      http_invalid_state = 2
      http_processing_failed = 3
      http_invalid_timeout = 4.

*    http_client->receive( ).
    call method http_client->receive
    exceptions
      http_communication_failure = 1
      http_invalid_state         = 2
      http_processing_failed     = 3
      others                     = 4.

    check sy-subrc eq 0.

    content = http_client->response->get_data( ).

    http_client->close( ).

    l_str_length = xstrlen( content ).

    call function 'RSFO_XSTRING_TO_MIME'
      exporting
        c_xstring = content
        i_length  = l_str_length
      tables
        c_t_mime  = mime.

    p_check = 'X'.
  endif.
endform.                    " download_qrcode

*&---------------------------------------------------------------------*
*&      Form  convert_image
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_NAME     text
*      -->P_HEIGHT   text
*      -->P_WIDTH    text
*      -->P_CHECK    text
*----------------------------------------------------------------------*
form convert_image using p_name type char20 p_height type i p_width type i changing p_check type c.
  create object i_igs_image_converter .

  i_igs_image_converter->input = 'image/png'.
  i_igs_image_converter->output = 'image/bmp'.
  i_igs_image_converter->width = p_width.
  i_igs_image_converter->height = p_height.

  call method i_igs_image_converter->set_image
    exporting
      blob      = mime
      blob_size = l_content_length.

  call method i_igs_image_converter->execute
    exceptions
      communication_error = 1
      internal_error      = 2
      external_error      = 3
      others              = 4.

  if sy-subrc = 0.
    call method i_igs_image_converter->get_image
      importing
        blob      = blob
        blob_size = blob_size
        blob_type = blob_type.

    p_check = 'X'.
  else.
    clear p_check.
  endif.

  if sy-subrc = 0.
    perform generate_picture using p_name changing p_check.
  endif.
endform.                    " CONVERT_IMAGE

*&---------------------------------------------------------------------*
*&      Form  generate_picture
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_NAME     text
*      -->P_CHECK    text
*----------------------------------------------------------------------*
form generate_picture using p_name type char20 changing p_check type c.
  gi_name = p_name.
  gi_object = 'GRAPHICS'.
  gi_id = 'BMAP'.
  gi_btype = 'BCOL'. " If u want black and white pass BMON
  gi_resident = ' '.
  gi_autoheight =  'X'.
  gi_bmcomp = 'X'.
  l_extension = 'BMP'.

  "importing the image into se78 before displaying it in the smartform.
  perform import_bitmap_bds    using blob
                                   gi_name
                                   gi_object
                                   gi_id
                                   gi_btype
                                   l_extension
                                   ' '
                                   gi_resident
                                   gi_autoheight
                                   gi_bmcomp
                          changing l_docid
                                   gi_resolution.

  if sy-subrc = 0.
    p_check = 'X'.
  else.
    clear p_check.
  endif.
endform.                    " SHOW_SMART_FORM

*&---------------------------------------------------------------------*
*&      Form  import_bitmap_bds
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_BLOB        text
*      -->P_NAME        text
*      -->P_OBJECT      text
*      -->P_ID          text
*      -->P_BTYPE       text
*      -->P_FORMAT      text
*      -->P_TITLE       text
*      -->P_RESIDENT    text
*      -->P_AUTOHEIGHT  text
*      -->P_BMCOMP      text
*      -->P_DOCID       text
*      -->P_RESOLUTION  text
*----------------------------------------------------------------------*
form import_bitmap_bds
        using    p_blob       type w3mimetabtype
                 p_name           type stxbitmaps-tdname
                 p_object         type stxbitmaps-tdobject
                 p_id             type stxbitmaps-tdid
                 p_btype          type stxbitmaps-tdbtype
                 p_format         type c
                 p_title          like bds_description
                 p_resident       type stxbitmaps-resident
                 p_autoheight     type stxbitmaps-autoheight
                 p_bmcomp         type stxbitmaps-bmcomp
        changing p_docid          type stxbitmaps-docid
                 p_resolution     type stxbitmaps-resolution.


  data: l_object_key type sbdst_object_key.
  data: l_tab        type ddobjname.

  data: begin of l_bitmap occurs 0,
          l(64) type x,
        end of l_bitmap.

  data: l_filename        type string,
        l_bytecount       type i,
        l_bds_bytecount   type i.
  data: l_color(1)        type c,

        l_width_tw        type stxbitmaps-widthtw,
        l_height_tw       type stxbitmaps-heighttw,
        l_width_pix       type stxbitmaps-widthpix,
        l_height_pix      type stxbitmaps-heightpix.
  data: l_bds_object      type ref to cl_bds_document_set,
        l_bds_content     type sbdst_content,
        l_bds_components  type sbdst_components,
        wa_bds_components type line of sbdst_components,
        l_bds_signature   type sbdst_signature,
        wa_bds_signature  type line of sbdst_signature,
        l_bds_properties  type sbdst_properties,
        wa_bds_properties type line of sbdst_properties.

  data  wa_stxbitmaps type stxbitmaps.

* Enqueue
  perform enqueue_graphic using p_object
                                p_name
                                p_id
                                p_btype.

* Bitmap conversion
  call function 'SAPSCRIPT_CONVERT_BITMAP_BDS'
    exporting
      color                    = 'X'
      format                   = p_format
      resident                 = p_resident
      bitmap_bytecount         = l_bytecount
      compress_bitmap          = p_bmcomp
    importing
      width_tw                 = l_width_tw
      height_tw                = l_height_tw
      width_pix                = l_width_pix
      height_pix               = l_height_pix
      dpi                      = p_resolution
      bds_bytecount            = l_bds_bytecount
    tables
      bitmap_file              = p_blob
      bitmap_file_bds          = l_bds_content
    exceptions
      format_not_supported     = 1
      no_bmp_file              = 2
      bmperr_invalid_format    = 3
      bmperr_no_colortable     = 4
      bmperr_unsup_compression = 5
      bmperr_corrupt_rle_data  = 6
      others                   = 7.

  if sy-subrc <> 0.

    perform dequeue_graphic using p_object
                                  p_name
                                  p_id
                                  p_btype.
    message id sy-msgid type sy-msgty number sy-msgno
            with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
    raising conversion_failed.

  endif.

* Save bitmap in BDS
  create object l_bds_object.

  wa_bds_components-doc_count  = '1'.
  wa_bds_components-comp_count = '1'.
  wa_bds_components-mimetype   = c_bds_mimetype.
  wa_bds_components-comp_size  = l_bds_bytecount.
  append wa_bds_components to l_bds_components.

  if p_docid is initial.          " graphic is new

    wa_bds_signature-doc_count = '1'.
    append wa_bds_signature to l_bds_signature.


    call method l_bds_object->create_with_table
      exporting
        classname  = c_bds_classname
        classtype  = c_bds_classtype
        components = l_bds_components
        content    = l_bds_content
      changing
        signature  = l_bds_signature
        object_key = l_object_key
      exceptions
        others     = 1.

    if sy-subrc <> 0.

      perform dequeue_graphic using p_object
                                    p_name
                                    p_id
                                    p_btype.
*      message e285 with p_name  'BDS'.

    endif.

    read table l_bds_signature index 1 into wa_bds_signature
    transporting doc_id.

    if sy-subrc = 0.

      p_docid = wa_bds_signature-doc_id.

    else.

      perform dequeue_graphic using p_object
                                    p_name
                                    p_id
                                    p_btype.
*      message e285 with p_name 'BDS'.

    endif.

  else.                " graphic already exists

********* read object_key for faster access *****
    clear l_object_key.
    select single * from stxbitmaps into wa_stxbitmaps
        where tdobject = p_object
          and tdid     = p_id
          and tdname   = p_name
          and tdbtype  = p_btype.

    select single tabname from bds_locl into l_tab
       where classname = c_bds_classname
          and classtype = c_bds_classtype.

    if sy-subrc = 0.
      select single object_key from (l_tab) into l_object_key
        where loio_id = wa_stxbitmaps-docid+10(32)
          and classname = c_bds_classname
            and classtype = c_bds_classtype.
    endif.

******** read object_key end ********************
    call method l_bds_object->update_with_table
      exporting
        classname     = c_bds_classname
        classtype     = c_bds_classtype
        object_key    = l_object_key
        doc_id        = p_docid
        doc_ver_no    = '1'
        doc_var_id    = '1'
      changing
        components    = l_bds_components
        content       = l_bds_content
      exceptions
        nothing_found = 1
        others        = 2.

    if sy-subrc = 1.   " inconsistency STXBITMAPS - BDS; repeat check in

      wa_bds_signature-doc_count = '1'.
      append wa_bds_signature to l_bds_signature.

      call method l_bds_object->create_with_table
        exporting
          classname  = c_bds_classname
          classtype  = c_bds_classtype
          components = l_bds_components
          content    = l_bds_content
        changing
          signature  = l_bds_signature
          object_key = l_object_key
        exceptions
          others     = 1.

      if sy-subrc <> 0.
        perform dequeue_graphic using p_object
                                      p_name
                                      p_id
                                      p_btype.
*        message e285 with p_name 'BDS'.

      endif.

      read table l_bds_signature index 1 into wa_bds_signature
      transporting doc_id.
      if sy-subrc = 0.
        p_docid = wa_bds_signature-doc_id.
      else.

        perform dequeue_graphic using p_object
                                      p_name
                                      p_id
                                      p_btype.

*        message e285 with p_name 'BDS'.

      endif.

    elseif sy-subrc = 2.

      perform dequeue_graphic using p_object
                                    p_name
                                    p_id
                                    p_btype.

*      message e285 with p_name 'BDS'.

    endif.

  endif.

* Save bitmap header in STXBITPMAPS
  wa_stxbitmaps-tdname     = p_name.
  wa_stxbitmaps-tdobject   = p_object.
  wa_stxbitmaps-tdid       = p_id.
  wa_stxbitmaps-tdbtype    = p_btype.
  wa_stxbitmaps-docid      = p_docid.
  wa_stxbitmaps-widthpix   = l_width_pix.
  wa_stxbitmaps-heightpix  = l_height_pix.
  wa_stxbitmaps-widthtw    = l_width_tw.
  wa_stxbitmaps-heighttw   = l_height_tw.
  wa_stxbitmaps-resolution = p_resolution.
  wa_stxbitmaps-resident   = p_resident.
  wa_stxbitmaps-autoheight = p_autoheight.
  wa_stxbitmaps-bmcomp     = p_bmcomp.
  insert into stxbitmaps values wa_stxbitmaps.

  if sy-subrc <> 0.

    update stxbitmaps from wa_stxbitmaps.

    if sy-subrc <> 0.
*       message e285 with p_name 'STXBITMAPS'.
    endif.
  endif.

* Set description in BDS attributes
  wa_bds_properties-prop_name  = 'DESCRIPTION'.
  wa_bds_properties-prop_value = p_title.
  append wa_bds_properties to l_bds_properties.
  call method l_bds_object->change_properties
    exporting
      classname  = c_bds_classname
      classtype  = c_bds_classtype
      object_key = l_object_key
      doc_id     = p_docid
      doc_ver_no = '1'
      doc_var_id = '1'
    changing
      properties = l_bds_properties
    exceptions
      others     = 1.

  perform dequeue_graphic using p_object
                                p_name
                                p_id
                                p_btype.
endform.                    "import_bitmap_bds

*&---------------------------------------------------------------------*
*&      Form  ENQUEUE_GRAPHIC
*&---------------------------------------------------------------------*
* Enqueue of graphics stored in BDS
*----------------------------------------------------------------------*
form enqueue_graphic using p_object
                           p_name
                           p_id
                           p_btype.
  call function 'ENQUEUE_ESSGRABDS'
       exporting
*           MODE_STXBITMAPS = 'E'
            tdobject        = p_object
            tdname          = p_name
            tdid            = p_id
            tdbtype         = p_btype
*           X_TDOBJECT      = ' '
*           X_TDNAME        = ' '
*           X_TDID          = ' '
*           X_TDBTYPE       = ' '
*           _SCOPE          = '2'
*           _WAIT           = ' '
*           _COLLECT        = ' '
       exceptions
            foreign_lock    = 1
            others          = 2.

  if sy-subrc <> 0.
    message id sy-msgid type sy-msgty number sy-msgno
          with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
    raising enqueue_failed.
  endif.

endform.                    " ENQUEUE_GRAPHIC

*&---------------------------------------------------------------------*
*&      Form  DEQUEUE_GRAPHIC
*&---------------------------------------------------------------------*
* Dequeue of graphics stored in BDS
*----------------------------------------------------------------------*
form dequeue_graphic using p_object
                           p_name
                           p_id
                           p_btype.

  call function 'DEQUEUE_ESSGRABDS'
       exporting
*           MODE_STXBITMAPS = 'E'
*           X_TDOBJECT      = ' '
*           X_TDNAME        = ' '
*           X_TDID          = ' '
*           X_TDBTYPE       = ' '
*           _SCOPE          = '3'
*           _SYNCHRON       = ' '
*           _COLLECT        = ' '
            tdobject        = p_object
            tdname          = p_name
            tdid            = p_id
            tdbtype         = p_btype.

endform.                    " DEQUEUE_GRAPHIC
Next, we have to make the output on a smartform and it’s very simple. Just take the BAPI and use it inside the smartform and to avoid that file names collide, use the ‘GUID_CREATE’ function module to generate different names.
This function module has 3 importing parameters which output 3 kinds of GUIDs. In my case, I used the ‘EV_GUID_16’ one. The base of this strategy is that there will be no chance to have the same file name per request.
data: lv_text type char255, " Variable for text to convert in QR code
      lv_check type c,      " Flag to check if QR code was generated
      lv_name type char20.  " Variable for file name

data lv_guid type guid_16. " Variable for GUID
call function 'GUID_CREATE'
 importing
   ev_guid_16       = lv_guid.
move lv_guid to lv_name.

lv_text = 'Demo for QR code'.

call function 'ZQRCODE'
  exporting
    i_text        = lv_text " Text to convert
    i_name        = lv_name " Graphic name
  importing
    e_check       = lv_check. " 'X' = success in conversion

if lv_check is not initial.
  g_name = lv_name.
  display = 'X'. " Yes, display the image
endif.

The steps in SAP for the smartform are:

1. Execute transaction ‘smartforms’.

2. Create a smartform

3. Define global variables
4. Add ‘Program Lines’
5. Paste the above code into ‘%CODE1’
6. Insert a graphic object and in ‘General Attributes’ table type ‘&G_NAME& in Name, ‘GRAPHICS’ in Object and ‘BMAP’ in ID properties.
7. Finally in ‘Conditions’ tab add the variable ‘DISPLAY’ with comparison value = ‘X’. This variable is defined in step 3.

Just consider that in this case the images are stored in SAP. You can check those files in transaction ‘SE78’.

The only step left was to make the webservice configurable, which meant that I had to keep in a table in SAP the webservice address. This strategy is good because it provides a flexible way to maintain the webservice in any server.

You can check that in the form ‘DOWNLOAD_QRCODE’ in the include file of the BAPI:

form download_qrcode using p_text type char255 changing p_check type c.
  data oref type ref to cx_root.
  data wa_source type zyour_table.
  select single * from zyour_table into wa_source. " Get webservice address for QR code generation


  check sy-subrc eq 0.

  concatenate wa_source-address p_text into url.

  call method cl_http_client=>create_by_url
    EXPORTING
      url                = url
    IMPORTING
      client             = http_client
    EXCEPTIONS
      argument_not_found = 1
      plugin_not_active  = 2
      internal_error     = 3
      others             = 4.

  if sy-subrc = 0.

This code will allow us to indicate the web address of the webservice and change it anytime I want.

Check the output of the smartform pressing 3 times the F8 key and then clicking on ‘Print preview’ button in the Print dialog:

Smartform can be very tricky but they are very handy and powerful. A lot of transactions in SAP ECC use these reports and you can define your own in transaction NACE, they come with all the information and information that are needed to adapt them according to the client requirements.
And the other hand you can make your own smartforms and use them in your own programs with the same tools in SAP.
See you in the next post.
1 Comment
You must be Logged on to comment or reply to a post.
  • Nice blog post and pretty well explained what to do. Can you tell me, why you aren't moving to AdobeForms? It has a lot more advantages and the qr-code is fully included.

    and new templates delivered by SAP will be AdobeForms only.. so in 2020 it should be not the decision how to include things which aren't delivered by SAP in Sapscript or Smartforms (by the way, your solution would also work with a sapscript 😉

    it's more the question why are the responsible persons do not invest into AdobeForms, which is available since 2006.. so it's not something brand new and you don't know if SAP will stay there.

    ~Florian