VISUAL COBOL, .NET and Microsoft Office combined is a great opportunity for a little CRM

[Migrated content. Thread originally posted on 04 January 2011]

Considering that most COBOL apps store customer contact information one way or another, there should be nice opportunities for some CRM tooling, especially when we know how easy it is to interact with Microsoft Office using dot net in VISUAL COBOL.

How about using Microsoft Word in conjunction with your customer database to mail a promo? All automatic of course :-)

To use the following source example;

* Make sure you have a C drive, and write access to the root of it. Otherwise change all filenames in the source to your preferred destination.

* Make sure you have Microsoft Office installed.

* Start up Visual COBOL.

* Select File | New | Project.

* Choose COBOL, Managed, Console Application.

* Enter name, Location and Solution name as desired or use the suggested values.

* Click Ok.

* You should now have gotten the source file (Program1.cbl) editor.

* To the right, you should have the Solution Explorer. If not, open it from the menu (View | Solution Explorer)

* Make sure Solution, and project is expanded, there should be an item in the tree named References.

* Right-click References in the Solution Explorer.

* Click Add Reference in the pop-up menu.

* Depending on your computer, it might take some time to populate the .NET tab. Here, scroll for Microsoft.Interop.Word, note that there might be multiple versions installed. This worked with version for me.

* Click Microsoft.Interop.Word to select this and OK.

* Right-Click References again, press Add Reference again, this time look for System-Windows.Forms, select it and click OK. We use this for the message boxes, if you remove the message boxes, you can skip this.

* Select all content in the editor window (Program1.cbl), delete it and replace with the code below.

* Build.

* Run.

You may of course as well step through in the debugger.
When the program is finished, you will find two new files; "c:\merged.doc" and "c:\vcdatasource.doc".

The merged.doc file is the final document to print. The vcdatasource.doc is the datasource we created to merge recepients from.

You may print, fax or even email the documents from your applocation, provided you have the setup for this.

       program-id. Program1 as "VCMailMerge.Program1".

       working-storage section.   
      *Word is not native .net, but encapsulated COM. In COM you have the opportunity to
      *omitt optional parameters, this is not supported in most .net native languages. Hence,
      *we have to provide "dummy" data for these. For this purpose we use the
      * "System.Reflection.NullObject::Value
       01 NullObject       object value type System.Reflection.Missing::Value.
       01 my-exception     type "System.Exception".
       01 orgbold          binary-long.
       01 dummystr         string.
       01 listsep          string.
       01 headerstr        string.

       01 WrdApp type Microsoft.Office.Interop.Word._Application.
       01 WrdDoc type Microsoft.Office.Interop.Word._Document.
       01 WrdDDc type Microsoft.Office.Interop.Word._Document.       
       01 WrdSel type Microsoft.Office.Interop.Word.Selection.
       01 WrdMMg type Microsoft.Office.Interop.Word.MailMerge.
       01 WrdMMF type Microsoft.Office.Interop.Word.MailMergeFields.
       01 WrdTbl type Microsoft.Office.Interop.Word.Table.         
       01 WrdNoSave object value type Microsoft.Office.Interop.Word.WdSaveOptions::wdDoNotSaveChanges.
       01 wdBlank object value type Microsoft.Office.Interop.Word.WdNewDocumentType::wdNewBlankDocument.
       01 wdGoToLine       object.
       01 wdGoToLast       object.

       procedure division.
      *First get the correct list separator character for your culture.
           set listsep to type System.Globalization.CultureInfo::CurrentCulture::TextInfo::ListSeparator.
           *> launch Word, get an instance to it.
           set WrdApp to new Microsoft.Office.Interop.Word.Application()
           *> Show Word, so we can see things happen. For production comment out or remove the next line
           set WrdApp::Visible to true
               *> Create master document
               invoke WrdApp::Documents::Add(NullObject, NullObject, wdBlank, NullObject) giving WrdDoc               
               *> Select the document as current.
               invoke WrdDoc::Select()
               *> Get a reference to the current selection                     
               set WrdSel to WrdApp::Selection
               *> Get a reference to the MailMerge object of the master document
               set WrdMMg to WrdDoc::MailMerge
               *> Create the Data Source file, for the header we use the list separator character we
               *> obtained from the current culture on the first line.
               set headerstr to string::Concat("FirstName" listsep " LastName" listsep
                   " City" listsep " Country")
               invoke WrdMMg::CreateDataSource(
                   "c:\vcdatasource.doc" as type System.Object, NullObject, NullObject,
                   headerstr as type System.Object,
                   NullObject, NullObject, NullObject, NullObject, NullObject)
               *> Open the Data Source file   
               invoke WrdApp::Documents::Open(
                   "c:\vcdatasource.doc" as type System.Object,
                   NullObject, NullObject, NullObject, NullObject, NullObject, NullObject, NullObject, NullObject,
                   NullObject, NullObject, NullObject, NullObject, NullObject, NullObject, NullObject)
                   giving WrdDDc
               *> Get a reference to the table in the Data Source file
               set WrdTbl to WrdDDc::Tables::Item(1)   
               *> The CreateDataSource method insert a table with 2 rows by default, we expand this
               *> with another three rows just for having more data. Giving us a total of 5 rows.
               *> 1 header row (1) and 4 data rows (2..5)
               perform 3 times
                   Invoke WrdTbl::Rows::Add(NullObject)
               *> This following sequence could easily be replaced by you reading your data from a COBOL file.
               *> Insert Gisle, Forseth, Oslo, Norway in table row 2.
               invoke WrdTbl::Cell(2, 1)::Range::InsertAfter("Gisle")
               invoke WrdTbl::Cell(2, 2)::Range::InsertAfter("Forseth")
               invoke WrdTbl::Cell(2, 3)::Range::InsertAfter("Oslo")
               invoke WrdTbl::Cell(2, 4)::Range::InsertAfter("Norway")
               *> Insert Mark, Warren, Newbury, England in table row 3.
               invoke WrdTbl::Cell(3, 1)::Range::InsertAfter("Mark")
               invoke WrdTbl::Cell(3, 2)::Range::InsertAfter("Warren")
               invoke WrdTbl::Cell(3, 3)::Range::InsertAfter("Newbury")
               invoke WrdTbl::Cell(3, 4)::Range::InsertAfter("England")
               *> Insert Piet, Henskens, Amsterdam, Holland in table row 4
               invoke WrdTbl::Cell(4, 1)::Range::InsertAfter("Piet")
               invoke WrdTbl::Cell(4, 2)::Range::InsertAfter("Henskens")
               invoke WrdTbl::Cell(4, 3)::Range::InsertAfter("Amsterdam")
               invoke WrdTbl::Cell(4, 4)::Range::InsertAfter("Holland")
               *> Insert Nabeen, Jamal, Newbury, England in table row 5
               invoke WrdTbl::Cell(5, 1)::Range::InsertAfter("Nabeen")
               invoke WrdTbl::Cell(5, 2)::Range::InsertAfter("Jamal")
               invoke WrdTbl::Cell(5, 3)::Range::InsertAfter("Newbury")
               invoke WrdTbl::Cell(5, 4)::Range::InsertAfter("England")
               *> Save the Data Source document
               invoke WrdDDc::Save()
               *> Close the Data Source document
               invoke WrdDDc::Close(WrdNoSave, NullObject, NullObject)
               *> Let us start typing the template letter, first we make the header,
               *> let us align the header text center.
               set WrdSel::ParagraphFormat::Alignment to type
               *> Make a copy of the current font bold state and name
               set orgbold to WrdSel::Font::Bold
               set dummystr to WrdSel::Font::Name
               *> Set the font to bold and the font type to Tahoma, that looks good :-)
               set WrdSel::Font::Bold to 1
               set WrdSel::Font::Name to "Tahoma"
               *> Type in Micro Focus University
               invoke WrdSel::TypeText("Micro Focus University")
               *> Let us get a new line
               invoke WrdSel::TypeParagraph()
               *> Department
               invoke WrdSel::TypeText("Knowledge department")
               *> Restore font to original
               set WrdSel::Font::Bold to orgbold
               set WrdSel::Font::Name to dummystr
               *> Two blank lines
               perform 2 times
                   invoke WrdSel::TypeParagraph()
               *> Set left alignment
               set WrdSel::ParagraphFormat::Alignment to type
               *> Initiate the MailMergeField object   
               set WrdMMF to WrdMMg::Fields
               *> With the MailMergeField object, using the template document (WrdSel) we type in
               *> the mailmerge fields.
               *> FirstName field
               invoke WrdMMF::Add(WrdSel::Range, "FirstName")
               *> Add a blank for nice formatting
               invoke WrdSel::TypeText(" ")
               *> LastName field
               invoke WrdMMF::Add(WrdSel::Range, "LastName")
               *> New line
               invoke WrdSel::TypeParagraph()
               *> City field
               invoke WrdMMF::Add(WrdSel::Range, "City")
               *> New line
               invoke WrdSel::TypeParagraph()
               *> Country field
               invoke WrdMMF::Add(WrdSel::Range, "Country")
               *> New line
               invoke WrdSel::TypeParagraph()               
               *> Align right for date
               set WrdSel::ParagraphFormat::Alignment to type
               *> Insert date object. Behold! Do note that the format here may differ depending
               *> on your nationality. Make sure you choose a format appropriate for your target
               *> audience :-)   
               invoke WrdSel::InsertDateTime("dddd, MMMM dd, yyyy" as type System.Object,
                   false as type "System.Object", NullObject, NullObject, NullObject)
               *> New line   
               invoke WrdSel::TypeParagraph()               
               *> Restore left alignment for body text
               set WrdSel::ParagraphFormat::Alignment to type
               *> Be polite, say Dear ;-)   
               invoke WrdSel::TypeText("Dear ")
               *> Insert FirstName, be personal to make sure you have their attention
               invoke WrdMMF::Add(WrdSel::Range, "FirstName")
               *> Just a comma
               invoke WrdSel::TypeText(",")
               *> New line
               invoke WrdSel::TypeParagraph()
               *> Insert the body text. Note that the concatenation is not just for
               *> having the source code look good, but also because the compiler has a
               *> limitation of 255 bytes on a single line of source code.
               invoke WrdSel::TypeText( "Thank you for your recent r" &
                   "equest for next semesters' class schedule for the" &
                   " Visual COBOL Programming Department. Enclosed wi" &
                   "th this letter is a booklet containing all the cl" &
                   "asses offered next semester at Micro Focus Univer" &
                   "sity. Several new classes will be offered in the " &
                   "dot net Programming Department next semester. The" &
                   "se classes are listed below.")
               *> New line   
               invoke WrdSel::TypeParagraph()
               *> Create a table for entry of the courses, apply table behavior, 9 rows and 4 columns
               *> Note the cast to "System.Object", this is due to the type specification of the method Add's
               *> parameters being pointer to variant
               invoke WrdDoc::Tables::Add(WrdSel::Range, 9, 4,
                   type Microsoft.Office.Interop.Word.WdDefaultTableBehavior::wdWord8TableBehavior as type System.Object,
                   type Microsoft.Office.Interop.Word.WdAutoFitBehavior::wdAutoFitFixed as type System.Object)
               *> To save typing, get a reference to the table item in the object variable WrdTbl   
               set WrdTbl to WrdDoc::Tables::Item(1)
               *> Set width and alignment for table item 1
               invoke WrdTbl::Columns::Item(1)::SetWidth(55,
                   type Microsoft.Office.Interop.Word.WdRulerStyle::wdAdjustNone)
               *> Set width and alignment for table item 2
               invoke WrdTbl::Columns::Item(2)::SetWidth(165,
                   type Microsoft.Office.Interop.Word.WdRulerStyle::wdAdjustNone)
               *> Set width and alignment for table item 3
               invoke WrdTbl::Columns::Item(3)::SetWidth(100,
                   type Microsoft.Office.Interop.Word.WdRulerStyle::wdAdjustNone)
               *> Set width and alignment for table item 4
               invoke WrdTbl::Columns::Item(4)::SetWidth(111,
                   type Microsoft.Office.Interop.Word.WdRulerStyle::wdAdjustNone)
               *> Let us apply a shade to the title row
               set WrdTbl::Rows::Item(1)::Cells::Shading::BackgroundPatternColorIndex to
                   type Microsoft.Office.Interop.Word.WdColorIndex::wdGray25
               *> Since we shade the title row, it makes sense to have the text here appear in bold
               *> to ease reading   
               set WrdTbl::Rows::Item(1)::Range::Bold to 1
               *> Center the first cell, leave the others left aligned
               set WrdTbl::Cell(1, 1)::Range::Paragraphs::Alignment to
                   type Microsoft.Office.Interop.Word.WdParagraphAlignment::wdAlignParagraphCenter
               *> Insert the header row text   
               invoke WrdTbl::Cell(1, 1)::Range::InsertAfter("Class Number")
               invoke WrdTbl::Cell(1, 2)::Range::InsertAfter("Class Name")
               invoke WrdTbl::Cell(1, 3)::Range::InsertAfter("Class Time")
               invoke WrdTbl::Cell(1, 4)::Range::InsertAfter("Instructor")
               *> Insert row for course 1, cell by cell
               invoke WrdTbl::Cell(2, 1)::Range::InsertAfter("EE220")
               invoke WrdTbl::Cell(2, 2)::Range::InsertAfter("Introduction to VISUAL COBOL programming")
               invoke WrdTbl::Cell(2, 3)::Range::InsertAfter("1:00-2:00 M,W,F")
               invoke WrdTbl::Cell(2, 4)::Range::InsertAfter("Dr. Bits")
               *> Insert row for course 2, cell by cell
               invoke WrdTbl::Cell(3, 1)::Range::InsertAfter("EE230")
               invoke WrdTbl::Cell(3, 2)::Range::InsertAfter("VISUAL COBOL programming theory")
               invoke WrdTbl::Cell(3, 3)::Range::InsertAfter("10:00-11:30 T,T")
               invoke WrdTbl::Cell(3, 4)::Range::InsertAfter("Dr. Bytes")
               *> Insert row for course 3, cell by cell
               invoke WrdTbl::Cell(4, 1)::Range::InsertAfter("EE300")
               invoke WrdTbl::Cell(4, 2)::Range::InsertAfter("Modernization theory")
               invoke WrdTbl::Cell(4, 3)::Range::InsertAfter("9:00-10:00 M,W,F")
               invoke WrdTbl::Cell(4, 4)::Range::InsertAfter("Dr. Integer")
               *> Insert row for course 4, cell by cell
               invoke WrdTbl::Cell(5, 1)::Range::InsertAfter("EE325")
               invoke WrdTbl::Cell(5, 2)::Range::InsertAfter("UI Modernization with PAM")
               invoke WrdTbl::Cell(5, 3)::Range::InsertAfter("9:00-10:30 T,T")
               invoke WrdTbl::Cell(5, 4)::Range::InsertAfter("Dr. Dword")
               *> Insert row for course 5, cell by cell
               invoke WrdTbl::Cell(6, 1)::Range::InsertAfter("EE350")
               invoke WrdTbl::Cell(6, 2)::Range::InsertAfter("Advanced VISUAL COBOL topics")
               invoke WrdTbl::Cell(6, 3)::Range::InsertAfter("9:00-10:30 T,T")
               invoke WrdTbl::Cell(6, 4)::Range::InsertAfter("Dr. Gpf")
               *> Insert row for course 6, cell by cell
               invoke WrdTbl::Cell(7, 1)::Range::InsertAfter("EE400")
               invoke WrdTbl::Cell(7, 2)::Range::InsertAfter("To the sky with VISUAL COBOL")
               invoke WrdTbl::Cell(7, 3)::Range::InsertAfter("1:00 -2:30 M,W,F")
               invoke WrdTbl::Cell(7, 4)::Range::InsertAfter("Dr. Gui")
               *> Insert row for course 7, cell by cell
               invoke WrdTbl::Cell(8, 1)::Range::InsertAfter("EE450")
               invoke WrdTbl::Cell(8, 2)::Range::InsertAfter("UI programming with PAM")
               invoke WrdTbl::Cell(8, 3)::Range::InsertAfter("1:00 -2:00 M,W,F")
               invoke WrdTbl::Cell(8, 4)::Range::InsertAfter("Dr. Netguru")
               *> Insert row for course 8, cell by cell
               invoke WrdTbl::Cell(9, 1)::Range::InsertAfter("EE500")
               invoke WrdTbl::Cell(9, 2)::Range::InsertAfter("Deploying to multiple targets")
               invoke WrdTbl::Cell(9, 3)::Range::InsertAfter("3:00 -4:00 M,W,F")
               invoke WrdTbl::Cell(9, 4)::Range::InsertAfter("Dr. Redist")
               *> Set value for wdGoToItem to wdGoToLine
               set wdGotoLine to type Microsoft.Office.Interop.Word.WdGoToItem::wdGoToLine
               *> Set value for wdGoToDirection to wdGoToLast
               set wdGoToLast to type Microsoft.Office.Interop.Word.WdGoToDirection::wdGoToLast
               *> Let us move to the last line in the document
               invoke WrdSel::GoTo(wdGoToLine, wdGoToLast, NullObject, NullObject)
               *> Add some more text
               invoke WrdSel::TypeText("For additional information r" &
                   "egarding the Micro Focus University, you can visit our website at ")
               *> Today people like links, let us give them the hyperlink to our web site to click on   
               invoke WrdSel::Hyperlinks::Add(WrdSel::Range, "", NullObject,
                   NullObject, NullObject, NullObject)
               *> Conclude the letter, providing a phone number
               invoke WrdSel::TypeText(". Thank you for your interest in the classes offered at the Micro Focus Univer" &
                   "sity. If you have any other questions, please feel free to give us a call at 800 328 4967 (US).")
               *> New line   
               invoke WrdSel::TypeParagraph()
               *> Have a polite end of the letter
               invoke WrdSel::TypeText("Sincerely, ")
               invoke WrdSel::TypeParagraph()
               invoke WrdSel::TypeText("John Doe")
               invoke WrdSel::TypeParagraph()
               invoke WrdSel::TypeText("Micro Focus University")
               invoke WrdSel::TypeParagraph()
               *> Set the destination of the merge. Here we tell we want it to go to a new document
               *> but it might as well be fax or email. These two of course will depend on us providing
               *> respectively fax number or email address
               set WrdMMg::Destination to type Microsoft.Office.Interop.Word.WdMailMergeDestination::wdSendToNewDocument
               *> Now run the merge
               invoke WrdMMg::Execute(1 as object)
               *> We don't care for the template document, so we tell Word it is already saved to avoid a dialog
               *> poping up
               set WrdDoc::Saved to true
               *> Now close the template document, no dialogs please (WrdNoSave)
               invoke WrdDoc::Close(WrdNoSave, NullObject, NullObject)
               *> Set WrdDoc object to the new merged document
               set WrdDoc to WrdApp::Documents::Item(1)
               *> Show a message box, telling we store the merged document in C:\merged.doc
               invoke type System.Windows.Forms.MessageBox::Show("Saved mailmerge to c:\merged.doc",
               *> Save the merged document   
               invoke WrdDoc::SaveAs("c:\merged.doc" as type System.Object,
                   NullObject, NullObject, NullObject, NullObject, NullObject, NullObject, NullObject,
                   NullObject, NullObject, NullObject, NullObject, NullObject, NullObject, NullObject, NullObject)
               *> For some odd reason, we have to tell Word it is saved (!)   
               set WrdDoc::Saved to true
               *> Close the merged document
               invoke WrdDoc::Close(WrdNoSave, NullObject, NullObject)                   
           catch my-exception
               *> Uh oh, we were not supposed to get here :-(
               invoke type System.Windows.Forms.MessageBox::Show(
               my-exception::Message::ToString(), "Try Catch")
           *> Close Word, we are done, no save dialogs (WrdNoSave) please
           invoke WrdApp::Quit(WrdNoSave, NullObject, NullObject)
           *> Clear pointers to indicate we are all set and done
           set WrdTbl to NULL
           set WrdDDc to NULL
           set WrdDoc to NULL
           set WrdSel to NULL
           set WrdApp to NULL
           *> Wow, you just did your first MailMerge from Visual COBOL using dot net and Office Interop!
       end program Program1.