Dragon Drop - A Visual Basic Software Consultancy

Word Tips

A Glossary Builder

A fun little assignment. Someone had, via the Record Macro, functionality had written some code which would copy the highlighted text from one document and into another. The idea was that the second document would be some form of Glossary Document. However, after a few days the recorded macro started to fail and so the call went out to see if the code could be improved.

One of the great problems about using the code from the Record Macro function in a production environment is that things in Real Life rarely mimic what the conditions which were present when the macro was recorded. This is why we always say that anything that comes from Record Macro has to be rewritten entirely after the general idea has be taken.

The first thing that must be understood is that the Record Macro does just what it says; it records what you do. The code it produces should be considered to be more of a log of what has happened rather than real useable code. Certainly it comes nowhere close to what we would call good VBA standard code.

The second thing is to realise is that in recorded code there is no error handling and there is no form of defensive programming at all. Neither do we expect there to be. In this scenario, we have a requirement to copy the selected text from one document to another. What happens if there are no documents open? Or just one document? What happens if the glossary document is closed, or will opening another document cause problems and, also, what happens if one tries to copy text from the glossary document to itself?

All of these are Real World issues and have to be addresed by the code. Clearly the Record Macro function cannot cater for these problems and this is why we stress that by all means use the Record Macro function but then be prepared to work on the code until it does what is really required.

In this case we were told of another requirement; if the highlighted text is only a point and not a range then the remainder of the word to the selected text must be included. Now we come to conditional requirements which, of course, no Record Macro would ever cope with. So, this is simple, if the selection is a point and is on the left hand side of a word then the word gets expanded. Of course, if the selection point is at the end of the word then it has to move to the right to the start of the next word and then select that. The specification is getting a little more involved.

There are a number of ways in which this problem can be solved and this is the way which we provided a solution. There are perhaps dozens of other methods, no doubt more elegant than this one, but this seemed to work and the user went away happy.

From the specification it is clear that we are going to have to identify the Glossary document. This variable would then have to be held globally. This, therefore, means that a specific start-up template would have to be created. Within this template would have placed the pointer to the Glossary Template as a Public variable but we decided to hold this within a Class.

Below is the code for clsGlossary:


Option Explicit

  Private m_oGlossaryDocument As Document
  
Sub DefineGlossaryDocument()

  If Documents.Count < 2 Then
    MsgBox "Not enough documents open.  Please ensure that at least two documents are open.", _
      vbOKOnly, "Error - Set Glossary Document"
    Exit Sub
  End If
    

  If Not m_oGlossaryDocument Is Nothing Then
    If MsgBox("Do you wish to change the Glossay Document from " & _
        m_oGlossaryDocument.Name & "?", vbYesNo, "Set Glossary Document") = vbNo Then
      Exit Sub
    End If
  End If
  
  Set m_oGlossaryDocument = ActiveDocument
  MsgBox "Glossary Document is now defined to be " & m_oGlossaryDocument.Name, _
    vbOKOnly, "Set Glossary Document"

End Sub


Property Get Name() As String

  Name = m_oGlossaryDocument.Name

End Property


Property Get IsOpen() As Boolean

  Dim oDoc As Document
  Dim bRetVal As Boolean
  
  
  bRetVal = False
  If Not m_oGlossaryDocument Is Nothing Then
  For Each oDoc In Documents
    If oDoc.Name = m_oGlossaryDocument.Name Then
      bRetVal = True
      Exit For
    End If
  Next
  End If


  IsOpen = bRetVal

End Property


Public Sub CopySelection(oRange As Range)

  Dim oActiveDocument As Document
  

  If Not m_oGlossaryDocument Is Nothing Then
    Set oActiveDocument = ActiveDocument
    m_oGlossaryDocument.Activate
    Application.Selection.Range = oRange
    oActiveDocument.Activate
  End If

End Sub

The glossary document pointer is held privately within this class. Before any action can be taken again the Glossary document it has to be defined. If the Glossary document is already defined then the user is prompted to see if he wants to define a new glossary document. Of course, there will have to be at least documents open before this can be done as there is no point doing anything until the required number of documents are present.

One important property and procedure is the IsOpen() property. This checks to see if the Glossary Document is still open and the other is the CopySelection() routine which, of course, copies the text from the active document to the glossary document.

All of these routines are called from the module, modGlossary:

Option Explicit

  Public oGlossary As clsGlossary

  
  
Public Sub DefineGlossaryDocument()

  If oGlossary Is Nothing Then
    Set oGlossary = New clsGlossary
  End If

  oGlossary.DefineGlossaryDocument

End Sub


Public Sub CopySelectedText()

  Dim oRange As Range
  

  If Documents.Count < 2 Then
    MsgBox "There needs to be at least documents open to contine this operation.", _
        vbOKOnly + vbExclamation, "Error: Glossary"
    Exit Sub
  End If

  If oGlossary Is Nothing Then
    MsgBox "Please define the Glossary Document before continuing.", _
        vbOKOnly + vbExclamation, "Error: Glossary"
  Else
    If ActiveDocument.Name = oGlossary.Name Then
      MsgBox "This operation cannot work if the Glossary Document is selected.", _
          vbOKOnly + vbExclamation, "Error: Glossary"
    Else
      If Not oGlossary.IsOpen() Then
        MsgBox "The Glossay Document seems to have vanished.", _
            vbOKOnly + vbExclamation, "Error: Glossary"
      Else
        ' All of the obvious pitfalls are avoided let's move the data from one document to another
        
        Set oRange = Application.Selection.Range
        If oRange.Start = oRange.End Then
          Application.Selection.MoveRight unit:=wdWord, Count:=1, Extend:=wdExtend
          Set oRange = Application.Selection.Range
          If Trim$(oRange.Text) = "" Then
            Application.Selection.MoveRight unit:=wdWord, Count:=1, Extend:=wdExtend
            Set oRange = Application.Selection.Range
          End If
          oGlossary.CopySelection oRange
        Else
          oGlossary.CopySelection oRange
        End If
        
  
      End If
    End If
  End If

End Sub


Public Sub About()

  Dim oAbout As frmAbout
  
  
  Set oAbout = New frmAbout
  oAbout.Show
  Unload oAbout
  Set oAbout = Nothing
  
End Sub

There are three public procedures in this module. Each is linked to a button in the toolbar. The first, DefineGlossaryDocument() defines or redefines the Glossary Document. The second, CopySelectedText(), copies the text (subject to a few criteria) from the active document to the Glossary document and then last, About(), displays a simple form which shows the author's details.

The interesting part of the code is in the CopySelectedText() routine. The first two checks are to see if there are enough documents open (there has to be at least two) and that the Glossary Document is defined. If not then the user is given a warning. Then given that there is a Glossary Document defined and that there is enough documents open the check has to be made that the current document isn't the Glossary Document and that the Glossary Document is open still.

Once these tests have been performed, and passed, then the actual copying of the text can go ahead. If the selection range is zero length then the selection is moved to the right until the selection covers the remainder of the current or the whole of the next word. Once that is done then the CopySelection() method is called from the clsGlossary object.

Note that this works by passing the Range object of the selected text and not by putting the text through the clipboard. This is because the user may have something of importance within the clipboard and it is the responsibility of the programmer to ensure that the contents of the clipboard are not disturbed unless absolutely necessary.

So, in summary, we can see that the code has been expanded to take up a fair few lines. However, please take note that the core of the code which does the actual required functionality contains just a few lines and the rest of the code within the module and class is mostly concerned with defensive programming.

This code can be downloaded from here.

Updates or Comments

If there are any suggestions for updates or comments then please drop us a mail at malcolm.smith@dragondrop.com.