Mail Merge extended - InsertTable, use custom data provider interface

Top  Previous  Next

Please first read the chapter about mail merge.

 

Here we describe how mail merge can be used more effectively. The coide can be found in project 

   "I) MailMerge\ModifyTextInMailM"

 

 

The demo was built, so not only merge fields can be updated, but also bookmarked text. There is a checkbox on the form which selects the "bookmark" method.

 

1) Code to insert a field:

 

procedure TForm1.btnInsFieldClick(Sender: TObject);

begin

if chkUseBookmarks.Checked then

begin

    // Create a bookmark

    WPRichText1.BookmarkInput( FieldName.Text, true );

    // Insert Text widthin

    WPRichText1.InputString( FieldName.Text );

    // Leave it

    WPRichText1.CPMoveNext;

end

// or create a standard merge field

else WPRichText1.InputMergeField( FieldName.Text, FieldName.Text  );

end;

 

 

2) Code to start the merge process

 

procedure TForm1.btnMergeTextClick(Sender: TObject);

begin

dataprovider := myDataProvider_nextrow;

try

if chkUseBookmarks.Checked then

    WPRichText1.MergeTextEx( '', '', wpobjBookmark, [wpmergeAllTexts] )

else

    WPRichText1.MergeTextEx( '', '', wpobjMergeField, [wpmergeAllTexts] );

finally

   dataprovider := nil;

end;

end;

 

We are initializing a "dataprovider : IWPDataProviderInterface" here. This is an interface which was designed to provide the data for the field. Using interfaces makes it possible to create the actual logic completely independently from the merging procedure. So the interface can be exchanged or updated without modifying (especially not recompiling) the actual merge procedure!

 

For this simple example we we designed the interface like this:

 

type

 IWPDataProviderInterface = interface

   ['{7C355C22-9B43-464F-A790-7EFB99D359CC}']

  // Get data type for this field

  function GetDataType( fieldname : String ) : Integer;

  // Get attributes for this field. Returns CSS

  function GetDataCSS( fieldname : String ) : String;

  // Get the actual data - for Datatype_String and Datatype_RTFString

  function GetData(  fieldname : String ) : String;

  // Get Table Information. The result value is an object which needs to be returened to the

  // the interface using

  function ReadTable( fieldname : String; var rows, cols : Integer ) : TObject;

  // Get Table data

  function GetTableData( TableObj : TObject; row, col : Integer ) : String;

  // End table reading

  procedure ReadTableFinish( TableObj : TObject );

end;

 

The possible data types are defined as integer constants:

 

const

  Datatype_None = 0;

  Datatype_String = 1;

  Datatype_RTFString = 2;

  Datatype_Table = 3;

 

There is also a demo implementation of this interface in unit MailExDataProvider, but it actually does not do much. It was only meant to provide some test data and fill the methods of the interface with life. 

 

An instance to the interface is provided by 

  

  function myDataProvider_nextrow : IWPDataProviderInterface;

 

This function is called above, before the merge process starts.

 

3) Code which inserts the data 

 

As usual the event OnMailMergeGetText is used. It uses the form variable dataprovider : IWPDataProviderInterface and tableobj : TObject. 

 

uses ... WPRTEDefsConsts, WPRTEEdit, WPIOCSS, ...

 

procedure TForm1.WPRichText1MailMergeGetText(Sender: TObject;

const inspname: string; Contents: TWPMMInsertTextContents);

var t,i,m : Integer;

   s : String;

   css : TWPCSSParserStyleWP;

   txtstyle : TWPTextStyle;

   editor : TWPCustomRtfEdit;

 

   r,c : Integer;

begin

 editor := (Sender as TWPCustomRtfEdit);

 txtstyle := nil;

try

    // Apply the attributes we received as a CSS string, i.e. "color:red"

     s := dataprovider.GetDataCSS(inspname);

    if s<>'' then

    begin

       css := TWPCSSParserStyleWP.Create(nil);

       txtstyle := TWPTextStyle.Create(editor.RTFData.RTFProps);

      try

         css.IsCharStyle := true;

         css.AsString := s;

        // nassign to a TWPTextStyle (that could also be a TParagraph!)

         css.ApplyToStyle(txtstyle);

        // Read all attributes

         editor.SelectedTextAttr.BeginUpdate;

        if txtstyle.AGet( WPAT_CharColor , i) then

                  editor.SelectedTextAttr.SetColorNr( i);

        // This shortcut code which has the disadvantage that it resets all the

        // attributes

        if txtstyle.AGet( WPAT_CharStyleMask , m) and

            txtstyle.AGet( WPAT_CharStyleON , i) then

              editor.SelectedTextAttr.SetCharStyles(m,i);

 

         editor.SelectedTextAttr.EndUpdate;

 

         Contents.MergeAttr.Assign(editor.SelectedTextAttr);

 

      finally

         css.Free;

      end;

    end;

    // Read the data

     t := dataprovider.GetDataType(inspname);

 

    if t=Datatype_String then

         Contents.StringValue := dataprovider.GetData(inspname)

    else if t=Datatype_RTFString then

    begin

         Contents.StringValue := dataprovider.GetData(inspname);

         Contents.Options := Contents.Options + [mmMergeAsRTF];

    end

    else if t=Datatype_Table then

    begin

       tableobj := dataprovider.ReadTable(inspname, r, c);

      if tableobj<>nil then

      try

          editor.ClearSelection(true);

          editor.InputString(#13+ #13); // Move after bookmark

          editor.CPMoveBack;

 

          editor.TableAdd(c,r,[wptblActivateBorders],txtstyle, TableAddCellEvent);

 

      finally

         dataprovider.ReadTableFinish(tableobj);

         tableobj := nil;

      end;

    end;

 

 

finally

   txtstyle.Free;

end;

 

end;

 

 

The code above uses

  if txtstyle.AGet( WPAT_CharStyleMask , m) and

            txtstyle.AGet( WPAT_CharStyleON , i) then

              editor.SelectedTextAttr.SetCharStyles(m,i);

to assign character styles, such as "bold" or "italic". Thew code will reset the current styles so you can alternatively use the following code which will only add certain styles and not remove any.

 

if txtstyle.AGet( WPAT_CharStyleON , i) then

begin

            if (i and WPSTY_BOLD)<>0 then

                  editor.SelectedTextAttr.IncludeStyle( afsBold );

            if (i and WPSTY_ITALIC)<>0 then

                  editor.SelectedTextAttr.IncludeStyle( afsItalic );

            if (i and WPSTY_UNDERLINE)<>0 then

                  editor.SelectedTextAttr.IncludeStyle( afsUnderline );

end;

 

 

The method below is called from TableAdd(). It uses the variable tableobj and dataprovider. Tableobj is the reference to the current table, it is used by the dataprovider to read and cache the data for the table. Internally it can contain a TQuery or similar to access a subset of the data. 

 

procedure TForm1.TableAddCellEvent(RowNr, ColNr: Integer; par: TParagraph);

begin

if tableobj<>nil then

  par.SetText( dataprovider.GetTableData(tableobj, RowNr-1, ColNr-1)); // 0 based!

end;