Creating MS Word Documents with Avenue using Dynamic Data Exchange
Andy Lyons, GeoPlan Center, University of Florida
September 1999

Introduction
One of the biggest weaknesses with ArcView is that is doesn't have a real report generator. Layouts are good for presentations with "frozen" formatting of data, but if you want a whole bunch of maps mixed with pages of dynamically-created text and tables which are grouped, sorted, or formatted in a certain way, you really need to go outside of ArcView.

ArcView 3.1 comes with the Crystal Reports extension, which serves as a link between ArcView and Crystal Reports, a completely separate application. Although I haven't used Crystal Reports, from the documentation it seems to be a fully featured report generator. However this approach may still have inherent limitations, because the reports created with CR are entirely based on data from ArcView tables. I don't know if you can insert Views or Layouts in Crystal reports.

Another approach is to create a report programmatically with a word processor, such as MS Word. Although this approach will require a bit of programming, you get all of the powerful formatting features of MS Word, including character formatting, paragraph formatting, tables, embedded images, etc. Reports created in MS Word can be made to look very professional, and have the added bonus (that Layouts and Crystal Reports don't), that the document can be further edited by the user. If you're a design perfectionist or looking for a very exact format in your output, this might be the best strategy.

MS Word 97 supports VBA, a flexible programming language that makes creating documents from within Word or other applications quite easy. Unfortunately however, ArcView 3.x does not support OLE (although we are told that ArcView 4 will), which means it can't get access to Word's object model. This makes our job much more difficult. Basically we have two options. First, we could create an OLE-compliant application that can be called by ArcView, such as a Visual Basic or C++ DLL, that also can create Word documents through OLE automation. Your ArcView Avenue scripts would make calls to this DLL, which would then use Word objects and methods to create the document. This approach has the disadvantage that you need C++ or Visual Basic version 6 or later (DLLs created with VB5 can not be called by ArcView), which makes development that much more complicated, and the user needs all the additional run-time files installed during setup.

A second approach, and the one that will be described in this paper, uses Dynamic Data Exchange (DDE) to create Word documents. DDE is the dinosaur of protocols for getting applications to work together, and has largely been replaced by OLE automation. However even though its clunky DDE can get the job done, and is still supported by Windows 95/98 and Microsoft Office, mostly for backwards compatibility. ArcView and Avenue supports DDE as well.

DDE Quick Overview
Although there are numerous descriptions of DDE in textbooks and on the web, the basic concepts are as follows:

  1. An application (the DDE client) opens a DDE Channel (also known as starting a conversation), with another application (the DDE server).
  2. The DDE client uses Poke and Execute commands (which perform an action in the server application) and Request methods (which retrieve information) to the DDE Server.
  3. The DDE Channel is closed.
  4. When the DDE Client opens a channel with the DDE Server, it must specify which topic it wants to discuss. All applications which serve as a DDE Server have a preset list of topics that they support. The list of valid topics varies with the application, but almost all DDE Servers support the System topic. Word also supports the name of open documents as Topics. Each time you use the Poke or Request method on an opened DDE Channel, you must also specify a valid Item.

    DDE and ArcView
    ArcView can act either as a DDE client or DDE server. Using ArcView as a DDE server, you could program another application to control ArcView. However in creating Word documents from Avenue we are only interested in using ArcView as a DDE Client. The object class in Avenue which is used to control DDE Servers is the DDEClient. Conceptually, a DDEClient object represents the conversation between the two applications. The DDE conversation between the two applications is started when you use create a new DDEClient object with the Make method, for example:

    theDDE = DDEClient.Make("WINWORD", "system")

    Note that the DDE Server Name (in this case WINWORD) is usually the name of the application's EXE file. Also note that the server application must be already running in order for a channel to be opened successfully. You can use the HasError property of the DDEClient object to see if the channel was opened successfully. If it wasn't successful, you can use Avenue's System.Execute method to start the application then and try to open the DDE channel again. Once a DDE conversation is opened, The Execute, Poke, and Request methods are used to perform actions or retrieve information from the server.

    DDE and Microsoft Word 97
    Using MS Word as a DDE server is not very well documented, unless you have the Office developers edition. About the best you can find online is an article from the MS Knowledge Base (article ID Q93657: Running Word for Windows as a DDE Server). The news group microsoft.public.word.oleinterop is also helpful (you can search the archives of this news group at www.deja.com). Below are some of the key points:

    Word supports two types of DDE topics (remember when you start a DDE conversation with another application you have to specify which topic to use). First of all, like all DDE server applications, Word supports the system topic. When you open a DDE conversation using the system topic, you can then use the Execute method to send WordBasic commands to Word (see WordBasic vs. VBA below). This is quite powerful, and will be the main tool we will use to build a new Word document. With the System topic you can also use SysItems as the parameter for the Request method, and it will returns a list of all the open documents and templates (so you can then start a new DDE conversation with one of those documents as the topic). There seems to be no straightforward way to retrieve information from Word (such as the text in the current selection, or the name of the font at the current cursor position) if you open a conversation with the System topic.

    The second type of topic that Word will accept is the name of an open document or template. If you open a DDE Conversation with Word using the name of an open document as the topic, you can then use the Request method with the name of a valid bookmark in the document as the request Item, to get the text in that bookmark. The Poke method can also be used to insert text into the Word document at the current cursor position. I don't know what you can do with the Execute method if the DDE conversation was opened with the name of an open document or template as the topic.

    WordBasic vs. VBA
    If you create a DDE conversation with MS Word using the system topic, you can then use the Execute method to send WordBasic commands to Word. WordBasic is the programming language supported by Word 6 and Word 95. In Word 97, WordBasic was replaced by VBA, which has a very different object model. However for backwards compatibility, Word 97 can interpret WordBasic commands as methods of the WordBasic object.

    Because WordBasic is no longer supported, it may be difficult to get help with the syntax. If you have the installation disks or CD for Word 6 or Word 95, you can find the WRDBASIC.HLP file (or download it from http://nature.berkeley.edu/~alyons/ftp/wrdbasic.zip). Or if you have a copy of Word 6 or Word 95 still running on an old machine, you can use the record macro command to learn the syntax of WordBasic.

    Quite often, WordBasic commands mimic keyboard behavior. For example your pseudo code might look like:

    Start a new document
    Insert the text "Hello world."
    Move the cursor to the right 6 characters
    Turn bold on
    Insert the text "beautiful "
    Move the cursor to the end of the line
    Insert a paragraph
    There would be a corresponding WordBasic command for each line of the pseudo code. There are other WordBasic commands for opening and closing files, inserting tables, formatting paragraphs, running a macro, etc.

    Avenue Syntax
    Note that if you want to use a WordBasic command using the Execute request of a DDEClient object, the command must be in [square brackets]. Also remember that WordBasic commands that insert or alter text are executed on the current Word document at the current cursor position. So as you write Avenue code to build a Word document you have to remember at all times which document is active and where the cursor is.

    Below is a sample Avenue script that will start a DDE conversation, create a new Word document, insert some text, and then close the DDE conversation. Note that the WordBasic commands are inserted in square brackets and that repeated double-quotes are used when the WordBasic command requires a literal string.

      theDDE = DDEClient.Make("WINWORD", "system")
    
      'Start Word if needed
      if (theDDE.HasError) then
        strMSW = "C:\Program Files\Microsoft\Office\Winword.exe"
        System.Execute(strMSW)
        'Try again
        theDDE = DDEClient.Make("WINWORD", "system")
        if (theDDE.HasError) then
          'Still can't connect, maybe Word isn't installed
          MsgBox.Error(theDDE.GetErrorMsg, "")
          Return Nil
        end
      end
    
      'Start a new document
      theDDE.Execute("[FileNew]")
     
      'Activate the Word window so you can see the document being built
      theDDE.Execute("[AppActivate ""Microsoft Word"", 1]")
    
      'Insert some text 
      theDDE.Execute("[Insert ""Hello world.""]")
      theDDE.Execute("[CharLeft 6]")
      theDDE.Execute("[Bold 1]")
      theDDE.Execute("[Insert ""BEAUTIFUL ""]")
      theDDE.Execute("[EndOfLine]")
      theDDE.Execute("[InsertPara]")
      theDDE.Execute("[Insert ""Goodbye!""]")
    
      'Close the conversation
      theDDE.Close
    

    Please send any feedback on this paper to: alyons@nature.berkeley.edu


    An Avenue Utility Script: wwWriteWord

    I have written a script, called wwWriteWord (download apr), which can be used to facilitate sending DDE command to MS Word to build a document. The script takes a two-element list as its argument. The first element should be a string with the name of one of the functions (see table below). The second element in the list will be an argument for the function. For example:

    av.Run("wwWriteWord", {"font_name", "Times New Roman"})

    A few other important points about using wwWriteWord:

    As stated before, wwWriteWord should be passed a list with two elements. The first element should be a string with the name of one of the functions (see table below). The second element in the list will be an argument for the function, as specified below:

    FunctionArgumentDescription
    initTrue/FalseStarts a DDE conversation with Word (launches Word if needed) and creates new document. If passed True, will make the Word application active. This function must be called before any of the other functions are called
    closeNILCloses the DDE conversation. Must be the very last function called after the document is complete.
    insert_plain_text any text (string)Inserts text at the current cursor position. Double-quotes will be converted to repeated single quotes: " => ''
    insert_html_textany text (string)Inserts text at the current cursor position, interpreting the following standard HTML tags: <b> </b> <i> </i> <u> </u> <p> <br> and applying the appropriate character formatting or line breaks. Note the script can't interpret any characters within the tag, such as <p align=center>. Double-quotes in the string will be converted to repeated single quotes.
    insert_paraN (number)inserts N new paragraphs
    insert_pictFileName (string)Inserts a picture at the cursor position. FileName should be the complete path to an image file in a format Word can import (such as BMP or WMF). You can use this function to insert an ArcView View or Layout that has been exported to a WMF file.
    insert_face1, 2, or 3 (number)Inserts a happy face symbol from the Wingdings font. 1=happy face, 2=no expression, 3=unhappy
    line_downN (number)Moves down N times. Mimics pressing the down arrow
    font_nameFontName (string)Switches the font to FontName
    font_sizeFontSize (string)Switches the font size
    alignLeftPara,
    RightPara,
    CenterPara,
    or JustifyPara
    (string)
    Sets alignment for current paragraph
    border_topNilApplies a top border to the current paragraph or table cell
    table_insertvariable list of numbers(1) Inserts a table at the current cursor position, (2) sets column widths, and (3) puts the cursor at the beginning of the first cell. First list element = NumColumns, 2nd element = NumRows, 3rd = width of 1st column in inches, 4th = width of 2nd column in inches, etc. If the column sizes are omitted from the list, the default column sizes will be used.
    table_nextcellNILMoves the cursor to the next cell
    bookmark_addBookmarkName (string)Adds a bookmark at the current cursor location. If a bookmark named BookmarkName already exists, it will be overwritten. Bookmark names can not contain spaces
    bookmark_gotoBookmarkName (string)Goes to specified bookmark
    saveFileName (string)Saves current document to FileName

    Sample Usage:

    ww = "wwWriteWord"
    av.Run(ww, {"init", True})
    
    av.Run(ww, {"align", "CenterPara"})
    av.Run(ww, {"font_name", "Arial"})
    av.Run(ww, {"font_size", 18})
    
    av.Run(ww, {"insert_html_text", "<B>Environmental Sensitivity Analysis</B>><P>"})
    
    av.Run(ww, {"font_name", "Times New Roman"})
    av.Run(ww, {"font_size", 12})
    av.Run(ww, {"align", "LeftPara"})
    
    av.Run(ww, {"table_insert", {2,1}})
    av.Run(ww, {"insert_html_text", "<B>Project:</B> SR 710 Ext., Palm Beach County"})
    av.Run(ww, {"table_nextcell", NIL})
    av.Run(ww, {"insert_html_text", "<B>Analysis Data:</B> August 22, 1999<BR><B>Version:</B> 1.0"})
    av.Run(ww, {"line_down", 1})
    av.Run(ww, {"close", NIL})
    

    Return to: Home