<< Click to Display Table of Contents >> Navigation: Programming > Mail Merge (replace fields with data) and data forms > Mail Merge extended - InsertTable, use custom data provider interface |
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;