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.
If there are any suggestions for updates or comments then please drop us a mail at malcolm.smith@dragondrop.com.