A Borland C++ Builder 5 tutorial, automating Word Mail Merge to print raffle tickets

This is a tutorial on how to put the program together, for new users in a hurry.

1) If you haven't already downloaded it, Click here to unzip the Raffle project.

Create a new folder where you wish to save your work, and copy the "data" folder from the Raffle project into your new folder. This contains the basic source file "Raffle.htm" and the Word mailmerge document "Raffle.doc". These can be edited later to your taste, but will do for now as test data.

2) When you open up C++ Builder 5 you are presented with a new project.

From the menu "File>Save Project As...", navigate to your folder created above, click "Save" to save Unit1.cpp

To save Project1 As, change the filename to "Raffle.bpr", as this will affect your program name. Click save.

3) From the "Servers" tab on the toolbar (use the arrow scroller on the right to find it), click on "WordApplication", click on your form.

4) From the "Servers" tab on the toolbar, click on "WordDocument", click on your form.

5) From the "Dialogs" tab on the toolbar, click on "OpenDialog", click on your form.

6) Select the "Standard tab on the toolbar" for the remaining components.

Click on "Label", click on your form where you want the title. In the Object Inspector>Properties tab on the left of your screen, change the Caption to "Raffle Tickets". Click on the Font value, then click on the "..." button that appears. This brings up the Font dialog where you can set the font, style and size. After clicking OK, on the form, right click on "Raffle Tickets" title just created, select "Align...", set Horizontal>Center in window, OK.

7) Click on "Label", click on your form, left hand side under the title. Change the Caption to "Lowest Number (give all digits):"

Click on "Edit", click on your form, to the right of the label just created. Change the Text to eg "05000" and the Name to "EditLowNum".

8) Repeat for EditHighNum - create a new Label, left. Change the Caption to "Highest Number:"

Create a new Edit, to the right. Change the Text to eg "05049" and the Name to "EditHighNum".

9) Create a new Label, left. change the Caption to ".htm Source File:"

Create a new Edit, to the right. Drag the right edge out 2-3 more inches. Change the Name to "EditSourceHtm", and the Text to the pathname of Raffle.htm (copy it from the address line of Explorer).

Create a new Button, to the right. Change the Caption to "Browse" and the Name to "ButSourceHtm". Double click on the button. Unit1.cpp comes to the front with the cursor in an automatically created click event subroutine for that button. Paste in the following code:-

	OpenDialog1->Title = "Select the .htm Source File";
	int i = EditSourceHtm->Text.LastDelimiter("\\");
	OpenDialog1->InitialDir = EditSourceHtm->Text.SubString(1,i);
	OpenDialog1->FileName = "";
	OpenDialog1->Filter = "HTML files (*.htm)|*.HTM";
	if (OpenDialog1->Execute())
	{
		EditSourceHtm->Text = OpenDialog1->FileName;
	}

Hit Key F12 to display the form again.

Explanation: If you click on the OpenDialog component you put on your form earlier, you will see in the Object Inspector it's Name is OpenDialog1 by default. Looking at the other properties, you will see we are setting them up at run time, rather than at design time, as this dialog will also be used for the Word file. Click on the button just created. In the Object Inspector, if you click on the Events tab, you will see the OnClick Event just created. Click on the Properties tab to continue.

10) Repeat for EditDestDoc - create a new Label, left. change the Caption to ".doc Mailmerge File:"

Create a new Edit, to the right. Drag the right edge out 2-3 more inches. Change the Name to "EditDestDoc", and the Text to the pathname of Raffle.doc.

Create a new Button, to the right. Change the Caption to "Browse" and the Name to "ButDestDoc". Double click on the button and paste the following code in it's click event subroutine:-

	OpenDialog1->Title = "Select the .doc Mailmerge File";
	int i = EditDestDoc->Text.LastDelimiter("\\");
	OpenDialog1->InitialDir = EditDestDoc->Text.SubString(1,i);
	OpenDialog1->FileName = "";
	OpenDialog1->Filter = "Word files (*.doc)|*.DOC";
	if (OpenDialog1->Execute())
	{
		EditDestDoc->Text = OpenDialog1->FileName;
	}

Hit Key F12 to display the form again.

11) Create a new ListBox anywhere. Set Visible to false.

Create a new Button, under the other buttons. Change the Caption to "Save Setup" and the Name to "ButSave". Double click on the button and paste the following code in it's click event subroutine:-

	String s = Application->ExeName;
	int i = s.LastDelimiter("\\");
	s.SetLength(i);
	s = s + "Raffle.ini";
	ListBox1->Clear();
	ListBox1->Items->Add(EditLowNum->Text);
	ListBox1->Items->Add(EditHighNum->Text);
	ListBox1->Items->Add(EditSourceHtm->Text);
	ListBox1->Items->Add(EditDestDoc->Text);
	ListBox1->Items->SaveToFile(s);

This is a simple method of saving information for the next time the program is used.
(Why do you need to use a ListBox? You don't. Just a demo of using a very useful component, especially for sorting items.)

12) Scroll to the top of Unit1.cpp and you will see the subroutine:-

__fastcall TForm1::TForm1(TComponent* Owner)
   : TForm(Owner)
{
}

This is the constructor subroutine executed when the program first starts. So paste the following in here:-

	// Reload saved setup
	String s = Application->ExeName;
	int i = s.LastDelimiter("\\");
	s.SetLength(i);
	s = s + "Raffle.ini";
	if (FileExists(s))
	{
		ListBox1->Clear();
		ListBox1->Items->LoadFromFile(s);
		EditLowNum->Text = ListBox1->Items->Strings[0];
		EditHighNum->Text = ListBox1->Items->Strings[1];
		EditSourceHtm->Text = ListBox1->Items->Strings[2];
		EditDestDoc->Text = ListBox1->Items->Strings[3];
	}
	else
	{
		// or at least ensure correct pointers to the data
		s.SetLength(i);
		EditSourceHtm->Text = s + "data\\Raffle.htm";
		EditDestDoc->Text = s + "data\\Raffle.doc";
		s = s + "Raffle.ini";
	}

13) Right click on Unit1.cpp and choose Open Source/Header File. In Unit1.h, for intended file i/o insert under the list of #include's:-

#include <fstream.h>

Still in Unit1.h, scroll down to "private: // User declarations" and insert below:-

	char buff[2000];

This is a buffer large enough to hold the first line of Raffle.htm.

Right click on Unit1.h and choose Close Page. This does not save it yet, just closes it.

Choose menu File>Save All.

(Save everything frequently as the Undo option is unpredictable. You often have to do repeated Redo's to get back to where you want.)

Hit Key F12 to display the form again (or click Toggle Form/Unit on the toolbar).

14) Create a new Button, centre bottom. Change the Caption to "Merge and Print" and the Name to "ButMergePrint". Drag the side of the button out to about 3 inches and centre it as for the title above. Double click on the button and paste the following code in it's click event subroutine:-

	Variant NoPrompt = wdDoNotSaveChanges;
	_DocumentPtr ResultName;
	int i;
	char* p1;
	char* p2;
	char* OutNum = "000000000000000000";
	int Digits = EditLowNum->Text.Length();

	if ((Digits > 9) || (EditHighNum->Text.Length() > 9))
	{
		ShowMessage("Numbers too large"); return;
	}
	if (EditHighNum->Text.ToInt() < EditLowNum->Text.ToInt())
	{
		ShowMessage("Negative number of tickets"); return;
	}
	// Start Word, visible is nice so you can see something is happening.
	try
	{
		WordApplication1->Connect();
	}
	catch(...)
	{
		ShowMessage("Unable to load Word"); return;
	}
	WordApplication1->Visible = true;
	// This if non-default printer, eg defined in an Edit component Named EditPrinter:-
	// WordApplication1->ActivePrinter = (Variant)EditPrinter->Text;
	// Keep the cursor shape to restore at the end, display a busy cursor
	TCursor oldCursor=Screen->Cursor;
	Screen->Cursor=crHourGlass;
	// Read the first line of the .htm source file
	ifstream infile(EditSourceHtm->Text.c_str());
	if(!infile)
	{
		Application->MessageBox("Cannot open file", EditSourceHtm->Text.c_str(), 
			MB_OK | MB_ICONEXCLAMATION); { Screen->Cursor=oldCursor; return; }
	}
	infile.getline(buff, sizeof(buff));
	infile.close();
	// recreate .htm source file and add 1st line containing header record
	ofstream outfile(EditSourceHtm->Text.c_str());
	if (!outfile)
	{
		Application->MessageBox("Cannot re-create file", EditSourceHtm->Text.c_str(), 
			MB_OK | MB_ICONEXCLAMATION); { Screen->Cursor=oldCursor; return; }
	}
	outfile << buff << endl;
	//  Do .htm data records
	i = EditLowNum->Text.ToInt();
	while (i <= EditHighNum->Text.ToInt())
	{
		p1 = OutNum + 9;
		sprintf(p1, "%i", i);
		while ((int)strlen(p1) < Digits) p1--;
		outfile << "<TR><TD>" << p1 << "</TD></TR>" << endl;
		i++;
	}
	// write last line
	outfile << "</TABLE></BODY></HTML>" << endl;
	outfile.close();
	// Send to Word
	if (!FileExists(EditDestDoc->Text))
	{
		Application->BringToFront();
		Application->MessageBox("File does not exist", EditDestDoc->Text.c_str(), MB_OK);
	}
	else
	{
		WordDocument1->ConnectTo(WordApplication1->Documents->Open((OleVariant)EditDestDoc->Text));
		WordDocument1->Activate();
		WordDocument1->MailMerge->OpenDataSource((Variant)EditSourceHtm->Text, EmptyParam, EmptyParam, 
			EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, 
			EmptyParam, EmptyParam, EmptyParam, EmptyParam);
		// Not reqd - is default      WordDocument1->MailMerge->MainDocumentType = wdFormLetters;
		// No - causes print dialog   WordDocument1->MailMerge->Destination = wdSendToPrinter;
		WordDocument1->MailMerge->Destination = wdSendToNewDocument;
		WordDocument1->MailMerge->SuppressBlankLines = true;
		WordDocument1->MailMerge->DataSource->FirstRecord = wdDefaultFirstRecord;
		WordDocument1->MailMerge->DataSource->LastRecord = wdDefaultLastRecord;
		i = WordApplication1->Documents->Count;
		WordDocument1->MailMerge->Execute();
		Application->ProcessMessages();
		ResultName = WordApplication1->ActiveDocument;
		if (i == WordApplication1->Documents->Count)
		{
			Application->BringToFront();
			Application->MessageBox(EditDestDoc->Text.c_str(), "!!! Merge failed.", MB_OK | MB_ICONEXCLAMATION);
			WordDocument1->Close(NoPrompt, EmptyParam, EmptyParam);
		}
		else
		{
			WordDocument1->Close(NoPrompt, EmptyParam, EmptyParam);
			ResultName->Activate();
			ResultName->PrintOut((Variant)false, EmptyParam, EmptyParam, EmptyParam, EmptyParam, 
				EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, 
				EmptyParam, EmptyParam, EmptyParam);
			ResultName->Close(NoPrompt, EmptyParam, EmptyParam);
		}
		WordDocument1->Disconnect();
		Application->BringToFront();
		Screen->Cursor=oldCursor;
		if (Application->MessageBox("All done. Exit?", EditDestDoc->Text.c_str(), 
		MB_YESNO | MB_ICONQUESTION) == IDYES) Close();
	}

15) Finally, create a new Button, centre bottom. Change the Caption to "Exit" and the Name to "ButExit". Centre it under the button above. Double click on the button and paste the following code in it's click event subroutine:-

	Close();

16) Save All.

Choose menu Project>Compile Unit (with Unit1.cpp upmost). If there are errors, a window will appear at the bottom. Double click on an error and it will be highlighted in Unit1.cpp ready to be corrected.

17) Once the errors are corrected, Save All and choose menu Run>Run to test it out.

Tart it up a bit with a caption on your form, an icon (menu Project>Options>Application tab) and some colour, and it's all done.

Use the help files to understand the program - put the cursor on a word and hit key F1, you may be lucky. Look up AnsiString and study the methods.

Note the object types for interfacing with Word - Variant, OleVariant, _DocumentPtr

If you find the tooltips and code completion irritating, see menu Tools>Editor Options>Code Insight tab to turn them off.

See menu Project>Options>Compiler tab for Debug or Full Release compilation.

See menu Project>Options>Linker tab, untick "Use dynamic RTL" and Project>Options>Packages tab, untick "Build with runtime packages" (rebuild with Project>Build Raffle) to have one complete .exe without .dll's for portability.

Hopefully the above has demonstrated use of the most common components required for simple applications - but not much there on disk i/o.

C++ Disk I/O

Every offering of C++ the disk i/o methods seem to change. In Builder, although you will see several sets of disk i/o described in the help files, the supported methods are ifstream, ofstream and fstream, for example to open a binary file for random-access (filepath in Edit component EditNmbrsDat) and find the length would be something like:-

	int MaxRange;
	fstream iofile(EditNmbrsDat->Text.c_str(), ios::binary | ios::in | ios::out);
	if (!iofile)
	{
		Application->MessageBox("Cannot read/write file", EditNmbrsDat->Text.c_str(), MB_OK | MB_ICONEXCLAMATION); return;
	}
	// get filesize
	iofile.seekg(0, ifstream::end);
	MaxRange = iofile.tellg();

To position the file pointer for reading use seekg, and for writing use seekp. Say, for instance, you had created a file of unique integers and wished to shuffle a portion, RandLow to RandHigh, for randomised raffle tickets:-

	int i;
	int j;
	int ActualNum1;
	int ActualNum2;
	MaxRange = MaxRange/sizeof(ActualNum1) - 1; // to max nmbr (int = 4 bytes, start 0)
	for (i = RandLow; i <= RandHigh; i++) // Random shuffle numbers
	{
		j = random(MaxRange + 1);
		iofile.seekg(i * sizeof(ActualNum1));
		iofile.read((char*)&ActualNum1, sizeof(ActualNum1));
		iofile.seekg(j * sizeof(ActualNum1));
		iofile.read((char*)&ActualNum2, sizeof(ActualNum1));
		iofile.seekp(j * sizeof(ActualNum1));
		iofile.write((char*)&ActualNum1, sizeof(ActualNum1));
		iofile.seekp(i * sizeof(ActualNum1));
		iofile.write((char*)&ActualNum2, sizeof(ActualNum1));
	}
	iofile.close(); // close file

Return for more information