Monday, 19 December 2011

Word Automation Services: Asynchronous

What that Means


That has two important consequences for solutions:

  • First, it means that you don't know immediately when a conversion has completed – the Start() call for a ConversionJob returns as soon as the job is submitted into the queue. You must monitor the job's status (via the ConversionJobStatus object) or use list-level events if you want to know when the conversion is complete and/or perform actions post-conversion.

  • Second, it means that maximum throughput is defined by the frequency with which the queue is polled for work, and the amount of new work requested on each polling interval.


Dissecting those consequences a little further:

Asynchronous


The asynchronous nature of the service means you need to set up your solutions to use either list events or the job status API to find out when a conversion is complete. For example, if I wanted to delete the original file once the converted one was written, as commenter Flynn suggested, I would need to do something like this:
public void ConvertAndDelete(string[] inputFiles, string[] outputFiles)
{
    //start the conversion
    ConversionJob job = new ConversionJob("Word Automation Services");
    job.UserToken = SPContext.Site.UserToken;
    for (int i = 0; i < inputFiles.Count; i++)
        job.AddFile(inputfiles[i], outputFiles[i]);
    job.Start();

    bool done = false;
    while(!done)
    {
        Thread.Sleep(5000);
        ConversionJobStatus status = new ConversionJobStatus("Word Automation Services", jobId, null);
        
        if(status.Count == (status.Succeeded + status.Failed + status.Canceled)) //everything done
        {
            done = true;
            
            //only delete successful conversions
            ConversionItemInfo[] items = status.GetItems(ItemType.Succeeded);
            foreach(ConversionItemInfo item in items)
                SPContext.Web.Files.Delete(item);
        }
    }
}

Now, clearly using Thread.Sleep isn't something you'd want to do if this is going to happen on many threads simultaneously on the server, but you get the idea – a workflow with a Delay activity is another example of a solution to this situation.

Word Automation in C#

Here in this tutorial I have used a word document template and mail merge option of the Word to automate the word document creation.
I have use mail merge option to set the fields and make the document fill through my application. We can also set the location of the text that we want to display in the word document programmatically.
So to get started we have to first make a template which is a word document template file (.dot file).
Open a new word document. Design the template as you like and once you are done then save the document in a word 97-2009 template.



Once the document is designed, it’s now time to set the merge fields. To set the mail merge fields in word 2007navigate to Insert>Quick Parts>Fields



Select “Merge Field” from the “Field names:” and give a “Field Name:” a name which we will need in our code.



Likewise, we need to set all the fields in the document and in the end the document looks like this:



Create a new windows application in Visual Studio.
Design application so that you have all the text fields. I have created small and very simple interface to demonstrate this automation.



Coming to the code part……
As we are performing office automation we first need to add requisite libraries.
Library name can be different as it depends on the office version installed on your machine. To add reference in your application, right-click “References” in solution explorer.



You will be prompted with Add Reference box.



Here under .NET tab choose Microsoft.Office.Interop.Word. You need to check the version as office 2003 and office 2007 will get different version installed on the system. Select the appropriate version and click OK.
When you add the reference, you will see the following reference added under reference which you can see under solution explorer.



To get the word document reference in the code we need to use the below references (namespaces).










1

2


using Microsoft.Office;

using Word = Microsoft.Office.Interop.Word;




Initialize the word application with word document










1

2

3

4

5


Object oMissing = System.Reflection.Missing.Value;

Object oTrue = true;

Object oFalse = false;

Word.Application oWord = new Word.Application();

Word.Document oWordDoc = new Word.Document();




To make sure that the application preview the word document, set the Visible property to true of word application.










1


oWord.Visible = true;




Set the template path This is the same word template file which you have created.










1


Object oTemplatePath = System.Windows.Forms.Application.StartupPath+"\\Report.dot";




Then pass the template object path to the word document object.










1


oWordDoc = oWord.Documents.Add(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);




Now count all merge fields in the document so we can have the field name through which we can access their location in the document. We are not, in actual accessing the location but want their reference so we can set their text and auto fill the document. I have used foreach loop to traverse all the merge fields.










1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19


foreach (Word.Field myMergeField in oWordDoc.Fields)

{

    iTotalFields++;

    Word.Range rngFieldCode = myMergeField.Code;

    String fieldText = rngFieldCode.Text;

    

    if (fieldText.StartsWith(" MERGEFIELD"))

    {

        Int32 endMerge = fieldText.IndexOf("\\");

        Int32 fieldNameLength = fieldText.Length - endMerge;

        String fieldName = fieldText.Substring(11, endMerge - 11);

        fieldName = fieldName.Trim();

        if (fieldName == "Name")

        {

            myMergeField.Select();

            oWord.Selection.TypeText(txt_name.Text);

        }

        }

}




One thing that you should notice that the merge field starts with MERGEFIELD <Mergefield name> for e.g.: in our case all the fields are like this MERGEFIELD\\NAME. MERGEFIELD\\ADDRESS, and likewise for every mergefield.
And in the end a simple if condition which check the name of the field and update the field text.
Execute you application and fill in all the text boxes in the application and hit Generate button, after which you will see the document completed.

Upgrade .doc to .docx in-place in a doc library

using Microsoft.SharePoint.Client; 
....
using(var ctx = new ClientContext("http://yoursharepoint/web_containing_library/"))
{    
var lib = ctx.Web.Lists.GetByTitle("Title of your library");    
var files = lib.RootFolder.Files;    
ctx.Load(files);    
ctx.ExecuteQuery();    
foreach(var file in files)    
{        
if(file.Name.ToLower().EndsWith(".doc")        
{            
string newFilename = file.Name + "x"; //dirty hack :)            
file.MoveTo(newFilename, MoveOperations.Overwrite);            
ctx.ExecuteQuery();            
//not sure if you need this, try without first            
ctx.Load(file);            
ctx.ExecuteQuery();            
 //convert DOC content to DOCX            
var fi = File.OpenBinaryDirect(ctx, file.ServerRelativeUrl);            
//get content using fi.Stream and convert it            
//save it            
File.SaveBinaryDirect(ctx, file.ServerRelativeUrl, streamWithDOCX, true);        
}    
}
}

Why I Can not login via wp-admin but same time I can login from WP-login.php

I know how it feel, like no one know the solution. You like to pay everything you have, to find out why you can not login into your wordpress from wp-admin. And if you do it will redirect to same page without showing any message. but same time you can login into your wordpress from wp-login.php.  Sometime clearing the browser’s cookies might let us login again but to be honest it’s a pain, and clearing your cookies isn’t a permanent fix and do not work everyone.

Answer is easy when you know the following two solutions.

Solution1:  At   WordPress admin–> Settings–> General Settings:

“WordPress address (URL)”  should be same as “Blog address (URL)”

Solution2:  Fixing  issue with cookies.

problem is that occasionally on our various WordPress sites the wp-admin.php URL login stops working and we can only login through wp-login.php.

What we need to do is to force WordPress to follow the correct cookie path. This can be done by adding a small line of code to the wp-config.php file located in your WordPress installation root directory.

Open up wp-config.php in your web editor and add this:

/** wp-admin login fix */
@define('ADMIN_COOKIE_PATH', '/');


If you’re unsure, it can go just before the ?> at the end of the php code like so:

/** Sets up WordPress vars and included files. */
require_once(ABSPATH . 'wp-settings.php');
/** wp-admin login fix */
@define('ADMIN_COOKIE_PATH', '/');
?>


Update:  Some reported WP Security Scan case this issue. Please test and confirm in comments if you are using WP Security Scan wordpress plugin.

Alternative Cron


Use this, for example, if scheduled posts are not getting published.
"this alternate method uses a redirection approach,
which makes the users browser get a redirect when the cron needs to run,
so that they come back to the site immediately while cron
continues to run in the connection they just dropped.
This method is a bit iffy sometimes, which is why it's not the default."

define('ALTERNATE_WP_CRON', true);

define('DISABLE_WP_CRON', true);

Use Quick Parts to Display Metadata fields

We're looking at 'Quick Parts' in Word.   So, what in the world do Quick Parts have to do with SharePoint?  Well, Quick Parts allow us the opportunity to display metadata fields, that we use in our SharePoint library, into the body of the document template we are using for our Library (they probably do other things too, but this is the only thing I've messed with).  Effectively, this means that for those instances when it would be useful for users to be able to enter (or view) metadata within the body of the document, you have that option.  For example, a library has metadata fields that require significant explanation for people to know how to fill them in.   Using Quick Parts, you can add the metadata fields to your document, along with any descriptive text needed.  And since everything is displayed through a document interface, we can organize and format the fields and descriptions in whatever way is appropriate (our options for adding descriptive text for a column is much more limited).

Example:

 

A note to those of you familiar with InfoPath: 'Plug your ears'.  Comparatively, the functionality of Quick Parts is very limited and will likely leave you feeling like you're trying to eat melting ice cream with a fork (in a pinch, seemed like a decent idea, but doesn't end up doing what you'd hoped).  However, though not nearly as powerful a tool as InfoPath, for those of us that don't have that application installed, or are more comfortable with Word, Quick Parts can be a handy option.

Another Note: Some knowledge of creating Content Types is needed for this.

To Use Quick Parts in your SharePoint Library: 

1.    Create your new library

2.     Build the appropriate columns (if you add out-of-the-box quick parts first, appears to be no way to promote them).

3.      Create a new document in the library.

4.   Save the Document (the 'Document Property' listing under 'Quick Parts' will be grayed out until the document has been saved).

5.   In Word 2007, access the 'Insert' ribbon and click the drop down menu for 'Quick Parts'.

6.  Go to the 'Document Property' listing, and select your new column (repeat as needed to add other columns)

7.  Arrange fields, and add descriptions, as desired.

8.  Save the updated document locally (anyplace easy to access is fine).  We'll be using this as the template for our new content type.

9.  Go to Site Actions, Site Settings and Site Content Types to 'Create' your new content type

10.  Name etc, your new Content Type and click OK.

11.    In the next  window select the 'Advanced Settings' link, choose 'Upload a new document template' and point it to your newly downloaded file.

12.   Go back to your new library, under Settings, Document Library Settings choose the Advanced Settings  link and choose 'Yes' for 'Allow management of content types'.

13.   In the Library Settings area (under Content Types), Add from existing Content Types to select your newly created Content Type (remove the other from the list if you like)

Last-   Create a doc using the new Content Type.

Adding SPListItems in SharePoint 2007

OK, so for my first real post I'd like to do go through programatically adding an item to a SharePoint List. It's pretty simple, but there are some things that it took me quite some time to find out. Please excuse me if I'm going through easy stuff, its been a hectic three months finding out all this =). I'm using the Beta 2 with a MOSS  on a VPC.

Lets look at some C# code:
___________________________________________

  SPList myList = myWeb.Lists["My First List"];
  SPListItem myNewItem = myList.Items.Add();
  myNewItem["Title"] = "New Item";
myNewItem.Update();
___________________________________________

So in the first line I got a SharePoint list from my SPWeb object. Then I used the Add() method of the Items collection which creates and returns a new SPListItem. Then, since one usually wants to initalize a new item with some values, I've set the "Title" property of my item and then used Update() to push the changes to the database.


All nice and easy. Some problems here though: what we get is an item using the default content type and maybe we have several in one list. Lets' modify our code to change the content type while creating an item:
___________________________________________

*SPContentType myCType = myList.ContentTypes["Custom content type"];
SPList myList = myWeb.Lists["My First List"];
SPListItem myNewItem = myList.Items.Add();

*myNewItem["ContentTypeId"] = myCType.Id;
*myNewItem.Update();

  myNewItem["Title"] = "New Item";
*myNewItem["CustomSiteColumn"] = 3;
  myNewItem.Update();
___________________________________________

To change the item content type, I need my list content type Id. It's best to get this from the actual SPList object, than to have it hard-wired. myList.ContentTypes returns a collection of c.types from which I can get the one I need through an indexer. It's also best to check if the content-type exist with something like an if (myCType != null) block, but for this example I'll assume it's all set up. Next thing there is changing the "ContentTypeId" field of the new item. Then an update and yey! we can access the "CustomSiteColumn" field of our new item.

For those of you wracking your brains how I knew about ContentTypeId field it can be educational to run a foreach on all the fields of your SPListItem.Fields like the following:
___________________________________________
foreach (SPField f in myItem.Fields)
{
try
{
if (myItem[f.Title] != null)
{
Label1.Text +=
f.TypeAsString + " "
+ f.Title + " "
+ f.InternalName + " "
+ thisItem[f.InternalName] + "<br />";
}
}
catch (Exception ex)
{
Label1.Text = "Non-accessable field.<br />";
}
}
___________________________________________

This little snippet of code will print all the fields inside your SPListItem for you to examine. I found out about the "Path" property this way.
Anyhow back to our example. So we've programatically created a new list item.. but where? In the root folder most likely, depending on what Item collection we were opening I think. But lists also support folders and we'll often want to stick our item in a particular folder.. for instance for security reasons, or just to keep things nice and tidy. Lets try creating an item in a folder then:
___________________________________________

SPContentType myCType = myList.ContentTypes["Custom content type"];
SPList myList = myWeb.Lists["My First List"];
*
SPFolder myFolder = dm.myList.Folders[3];
*
SPListItem myNewItem = dm.myList.Items.Add(myFolder.ServerRelativeUrl, SPFileSystemObjectType.File, null);

  myNewItem["ContentTypeId"] = myCType.Id;
  myNewItem.Update();

  myNewItem["Title"] = "New Item";
myNewItem["CustomSiteColumn"] = 3;
  myNewItem.Update();
___________________________________________

Look at the second new line: there's the overloaded .Add() method of the SPListItemColection, which now accepts a Url, an object type and a leafName. The first is most important - the url of the folder where our new item is going to be placed. When programatically accessing SharePoint its best to grab the url from some object.. like the SPFolder object we want to stick our item in. Next thing we need is the SPFileSystemObjectType.. basically either File, Folder or Web. Now it gets complicated - behind each SPListItem there is an SPFile object with the same Guid. Same with a list folder. Each folder also has an Item object.

I'll try to go into that in more detail (how I understand it) later, for now it'll do to say you can add an SPFolder to the SPList the same way as in my example, except you use
SPFileSystemObjectType.Folder. And as for the leafName - that's autogenerated so just put null.

So that just about wraps it up. Don't forget to run myWeb.Dispose() on your SPWeb objects after you've finished with them..

Friday, 16 December 2011

Word-VBA Code Samples

This code was written for Word 97 and worked fine for Word 2000.
No guarantees are given for any later versions of Word, but most of this code will likely still be valid.


« A »

Array, ReDim an Array:
ReDim arrArrayName(intNumberNames)

Array, Sort an Array:
WordBasic.SortArray (arrArrayName())

« B »

Backspace:
Selection.TypeBackspace

Bookmark, Add:
With ActiveDocument.Bookmarks
.Add Range:=Selection.Range, Name:="Name"
.DefaultSorting = wdSortByName
.ShowHidden = False
End With

Bookmark, Count # of Bookmarks in Document:
Dim intNumBookmarks as Integer
intNumBookmarks = ActiveDocument.Bookmarks.Count

Bookmark, Delete:
ActiveDocument.Bookmarks("BookmarkName").Delete

Bookmark, Exists (Does It Exist?):
If ActiveDocument.Bookmarks.Exists("BookmarkName") = True then
'Do something, i.e.,:
ActiveDocument.Bookmarks("BookmarkName").Select
Selection.TypeText Text:="Hello"
End If

Bookmark, Go to Bookmark:
(This method does not work with bookmarks in Headers/Footers)

Selection.GoTo What:=wdGoToBookmark, Name:="Name"

Bookmark, Select a Bookmark:
(This method works when using bookmarks in Headers/Footers)

ActiveDocument.Bookmarks("BookmarkName").Select

Bookmark, Insert Text Using Range (Change Content of Range):
(This method works when using bookmarks in Headers/Footers)

ActiveDocument.Bookmarks("BookmarkName").Range.Text="Text"

Bookmark, Insert Text Using Range (Add After Range):
(This method works when using bookmarks in Headers/Footers)

ActiveDocument.Bookmarks("BookmarkName").Range.InsertAfter _
"Text"

Bookmark, Go to a Bookmark, replace the text that's contained
in the Bookmark, and still have the text bookmarked:

Selection.GoTo What:=wdGoToBookmark, Name:="BookmarkName"
Selection.Delete Unit:=wdCharacter, Count:=1
Selection.InsertAfter "This is the new text"
ActiveDocument.Bookmarks.Add Range:=Selection.Range, _
Name:="BookmarkName"

Bookmark, Replace Text of a Bookmark in document2 With the
Text of a Bookmark in document1:
Note that both documents must be open when the macro is run.

Documents("c:\temp\document2.doc").Bookmarks("doc2BookmarkName").Range.Text = _
Documents("c:\temp\document1.doc").Bookmarks("doc1BookmarkName").Range.Text
Alternate Code:
Documents("c:\temp\document2.doc").Bookmarks(1).Range.Text = _
Documents("c:\temp\document1.doc").Bookmarks(4).Range.Text
where the text of the 4th bookmark in document1 is replacing the text
of the 1st bookmark in document2.


Bookmark, Turn Off View Bookmarks:
ActiveWindow.View.ShowBookmarks = False

« C »

Call, Run a Macro That's in Another Template:
Application.Run "[TemplateName].[ModuleName].[MacroName]
Example: Application.Run "Normal.NewMacros.Macro1"
Example: Application.Run "Normal.Module1.Macro2"

Call, Run a Macro That's Within the Same Template:
Application.Run MacroName:="[MacroName]"

Caption, Load an Array Element Into an Option Box Caption:
opt1.Caption = arrArrayName(0)

CHR (Character) Function:
Here are some of the more commonly used ASCII codes:
Chr(9) = tab
Chr(11) = manual line break (shift-enter)
Chr(12) = manual page break
Chr(13) = vbCrLf (return)
Chr(14) = column break
Chr(30) = non-breaking hyphen
Chr(31) = optional hyphen
Chr(32) = space
Chr(34) = quotation mark
Chr(160) = nonbreaking space
For more, look up ASCII character codes in the appendix of most
computer books. See also "Chr$" under VBA Help.
USAGE EXAMPLE: Selection.TypeText text:=Chr(11)

ComboBox, Add Array Items to Combo Box:
For i = 1 to 18   '18 elements in array
cbxComboBoxName.AddItem arrArrayName(i)
Next i

ComboBox, Set First Value to Show in Combo Box From an Array:
cbxComboBoxName.Value = arrArrayName(0)
'[(1) if Option Base 1]

Constant, Declare a Constant:
Const strIniFile as String = _
"C:\Temp\MyFile.txt"

Copy Entire Document:
Selection.HomeKey Unit:=wdStory
Selection.Extend

Copy:
Selection.Copy

« D »

Delete:
Selection.Delete Unit:=wdCharacter, Count:=1

Directory, Exists:
This particular code determines whether your computer has a C:\Windows\Temp
directory (you are running Windows) or a C:\WINNT\Temp directory (you are
running NT); of course, you can use this function to determine whether any
directory exists (for example, if SomeDir exists, great; elseif SomeDir
doesn't exist, then create it, etc.)

Dim strTempDirPath as String
Const strNTTempDirPath as String = "C:\WINNT\Temp"
Const strWindowsTempDirPath as String = "C:\Windows\Temp"
If Dir(strNTTempDirPath, vbDirectory) <> "" Then
StrTempDirPath = strNTTempDirPath
MsgBox ("The directory " + strTempDirPath + " exists.")
ElseIf Dir(strWindowsTempDirPath, vbDirectory) <> "" Then
StrTempDirPath = strWindowsTempDirPath
MsgBox ("The directory " + strTempDirPath + " exists.")
End If

Document Variable, Set (Create) a Document Variable
(NOTE: This refers to a Word Document Variable, as opposed to
a Variable used in computer programming):

Dim aVar as Variant
Dim iNum as Integer
Dim DocumentType as Variant
For Each aVar In ActiveDocument.Variables
If aVar.Name = "DocumentType" Then iNum = aVar.Index
Next aVar
If iNum = 0 Then
ActiveDocument.Variables.Add Name:="DocumentType", _
Value:="Letter"
Else
ActiveDocument.Variables("DocumentType").Value = "Letter"
End If

Document Variable, Create Draft# Doc Variable if Does Not Yet Exist
& Set Document, Draft # to 1 (NOTE: This refers to a Word Document
Variable, as opposed to a Variable used in computer programming):

Dim DraftNumber as String
Dim aVar as Variant
Dim iNum as Integer
For Each aVar In ActiveDocument.Variables
If aVar.Name = "DraftNumber" Then iNum = aVar.Index
Next aVar
If iNum = 0 Then
ActiveDocument.Variables.Add Name:="DraftNumber", Value:=1
Else
ActiveDocument.Variables(iNum).Value = 1
End If

Document Variable, What is the Current DocumentType Document Variable
Set To? (NOTE: This refers to a Word Document Variable, as opposed to
a Variable used in computer programming)

MsgBox ("The document variable is set to type: " & _
ActiveDocument.Variables("DocumentType").Value)

Document Variable, Check Document Variable Value (NOTE: This refers
to a Word Document Variable, as opposed to a Variable used in computer
programming):

Dim strDocType as String
strDocType = _
ActiveDocument.Variables("[DocumentVariableName]")

Document, Go to Start of Document:
Selection.HomeKey Unit:=wdStory

Document, Go to End of Document:
Selection.EndKey Unit:=wdStory

Document, New, Create a New Document from Another
Document (Template, Form Document, etc.):

Documents.Add Template:="C:\Forms\FormDoc.doc", _
NewTemplate:=False

Document, Protect Document:
ActiveDocument.Protect Password:="[password]", _
NoReset:=False, Type:=wdAllowOnlyFormFields

Document, Save Document:
ActiveDocument.Save

Document, SaveAs
ActiveDocument.SaveAs ("C:\Temp\MyFile.doc")

Document, SaveAs (with all the junk):
ActiveDocument.SaveAs FileName:="C:\Temp\MyFile.doc",
FileFormat:=wdFormatDocument, LockComments:=False, _
Password:="", AddToRecentFiles:=True, WritePassword:="", _
ReadOnlyRecommended:=False, EmbedTrueTypeFonts:=False, _
SaveNativePictureFormat:=False, SaveFormsData:=False, _
SaveAsAOCELetter:=False

Document, Unprotect Document:
If ActiveDocument.ProtectionType := wdNoProtection Then
ActiveDocument.Unprotect Password:="readonly"
End If

« E »

Extend, Turn Off Extend Mode:
Selection.ExtendMode = False

« F »

Field Code, Lock Field Code:
Selection.Fields.Locked = True

Field Code, Insert SEQ Field Code:
Selection.Fields.Add Range:=Selection.Range, _
Type:=wdFieldEmpty, Text:="SEQ name \n", _
PreserveFormatting:=True

Field Code, Reset SEQ Field Code to Zero (Restart #ing):
Selection.Fields.Add Range:=Selection.Range, _
Type:=wdFieldEmpty, Text:="SEQ name \r0 \h ", _
PreserveFormatting:=True

Field Code, Sequence Numbering Field Codes With Sub-Levels:
Level 1:
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
Text:="SEQ \L1 \*arabic \c \* MERGEFORMAT", _
PreserveFormatting:=True
Level 2:
Selection.Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
Text:="SEQ \L2 \*alphabetic \c \* MERGEFORMAT", _
PreserveFormatting:=True
(etc.)

Field Code, SEQ#, Reset #s to 0:
Selection.Fields.Add _
Range:=Selection.Range, Type:=wdFieldEmpty, _
Text:="SEQ L1 \r0 \h", PreserveFormatting:=True
Selection.Fields.Add _
Range:=Selection.Range, Type:=wdFieldEmpty, _
Text:="SEQ L2 \r0 \h", PreserveFormatting:=True

Field Code, Unlock Field Code:
Selection.Fields.Locked = False

Field Code, Update Field Code:
Selection.Fields.Update

Field Code, View Field Codes:
ActiveWindow.View.ShowFieldCodes = True

Field Code, View Field Codes (with all the junk):
ActiveWindow.View.ShowFieldCodes = _
Not ActiveWindow.View.ShowFieldCodes
With ActiveWindow
With .View
.ShowFieldCodes = True
End With
End With

Find:
Selection.Find.ClearFormatting
With Selection.Find
.Text = "xxx"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute

Find, Was It Found? (version 1)
If Selection.Find.Found = True Then
'blah blah blah
End If

Find, Was It Found? (version 2, thanks to Shawn Wilson)
If Selection.Find.Execute Then
'blah blah blah
End If

Find, Field Code:
Selection.Find.ClearFormatting
With Selection.Find
.Text = "^d"
... [all the other junk, i.e., direction, etc.]
End With

Find, Paragraph Mark (Real Paragraph, Not the Symbol):
Selection.Find.ClearFormatting
With Selection.Find
.Text = "^p"
.Forward = True
.Wrap = wdFindStop
End With
Selection.Find.Execute

Find, Replace:
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "xxx"
.Replacement.Text = "yyy"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll

Find, Replace Hard Returns With Manual Line Breaks
Within Selected Text:

Selection.Extend
Selection.Find.ClearFormatting
With Selection.Find
.Text = "^l"          'L not 1
.Forward = False
.Wrap = wdFindStop
End With
Selection.Find.Execute
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend
Selection.Find.ClearFormatting
With Selection.Find
.Text = "^p"
.Replacement.Text = "^l"       'L not 1
.Forward = True
.Wrap = wdFindStop
End With
Selection.Find.Execute Replace:=wdReplaceAll
Selection.MoveRight Unit:=wdCharacter, Count:=1

Font, Set Font Size:
Selection.Font.Size = 12

Font:
With Selection.Font
.Hidden = True
.ColorIndex = wdRed [or] wdAuto
End With

Footer, View Footer:
ActiveWindow.ActivePane.View.SeekView = _
wdSeekCurrentPageFooter

Form, Hide a Form:
frmFormName.Hide

Form, Load & Show a Form:
Load frmFormName
frmFormName.Show

« G »

GoTo, Go to Bookmark:
(This method not suggested for use with bookmarks in Headers/Footers;
see "Bookmarks" entries under "B")

Selection.GoTo What:=wdGoToBookmark, Name:="Name"

GoTo, Go to Page 1
Selection.GoTo What:=wdGoToPage, Which:=wdGoToNext, Name:="1"

« H »

Header, View Current Page Header:
ActiveWindow.ActivePane.View.SeekView = _
wdSeekCurrentPageHeader

Header, View Header (with all the junk):
If ActiveWindow.View.SplitSpecial := wdPaneNone Then
ActiveWindow.Panes(2).Close
End If
If ActiveWindow.ActivePane.View.Type = wdNormalView Or _
ActiveWindow.ActivePane.View.Type = wdOutlineView Or _
ActiveWindow.ActivePane.View.Type = wdMasterView Then _
ActiveWindow.ActivePane.View.Type = wdPageView
End If
ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader

« I »

IF Test:
If [condition] Then
[Do Something]
ElseIf [another condition] Then
[Do Something Else]
Else [another condition] Then
[Do Something Else]
End If

Indent, Set Left Indent:
Selection.ParagraphFormat.LeftIndent = InchesToPoints(3.75)

Indent, Set Right Indent:
Selection.ParagraphFormat.RightIndent = InchesToPoints(1)

InputBox, Get & Use Data From an Input Box:
Dim strData as String
strData = InputBox("What is the data?")
MsgBox (strData)

Insert After:
Selection.InsertAfter "xxx"

Insert an Underlined Tab:
Selection.Font.Underline = wdUnderlineSingle
Selection.TypeText Text:=vbTab
Selection.Font.Underline = wdUnderlineNone

Insert AutoText:
Selection.TypeText Text:="a3"
Selection.Range.InsertAutoText

Insert Date Code (Month Only):
Selection.Fields.Add Range:=Selection.Range, _
Type:=wdFieldEmpty, Text:="DATE \@ ""MMMM""", _
PreserveFormatting:=True

Insert Date Code (Year Only):
Selection.Fields.Add Range:=Selection.Range, _
Type:=wdFieldEmpty, Text:="DATE \@ ""yyyy""", _
PreserveFormatting:=True

Insert File:
Selection.InsertFile ("C:\Docs\Something.doc")

Insert Page Break:
Selection.InsertBreak Type:=wdPageBreak

Insert Paragraph Symbol:
Selection.TypeText Text:=Chr$(182)

Insert Section Symbol:
Selection.TypeText Text:=Chr$(167)

Insert SEQ# Field Code:
Selection.Fields.Add Range:=Selection.Range, _
Type:=wdFieldEmpty, Text:="SEQ name \n", _
PreserveFormatting:=True

Insert Text in Upper Case:
Selection.TypeText Text:=UCase(strStuff)   OR
Selection.TypeText Text:=UCase(cbxSigBlockAnotherName.Value)

Insert Symbol:
Selection.InsertSymbol CharacterNumber:=8212, _
Unicode:=True, Bias:=0
(This happens to be the symbol for an "M-dash")

Insert Tab:
Selection.TypeText Text:=vbTab

Insert Text (replaces selection if anything is selected):
Selection.TypeText Text:=txtStuff.text [or] "Hello" [or]
strText

Insert Text After Position of Cursor (does not replace
selection; appends text to end of selection:

Selection.InsertAfter txtStuff.text [or] "Hello" [or] strText

Insert Various Characters:
Selection.TypeText Text:=vbTab   'Tab
Selection.TypeText Text:=vbCrLf  'Para Return

Insert, Type Paragraph:
Selection.TypeParagraph

« J »

« K »

« L »

Line, Beginning of Line:
Selection.HomeKey Unit:=wdLine

Line, End of Line:
Selection.EndKey Unit:=wdLine

Line Spacing, Set Line Spacing to Exactly:
Selection.ParagraphFormat.LineSpacingRule = wdLineSpaceExactly
Selection.ParagraphFormat.LineSpacing = 12
OR
With Selection.ParagraphFormat
.LineSpacingRule = wdLineSpaceExactly
.LineSpacing = 12
End With

Loop: Do...Loop:
Do While intCounter < 10
intCounter = intCounter + 1
Selection.TypeText Text:="Howdy"
Loop

Loop: Do Until End of Document
Do Until ActiveDocument.Bookmarks("\Sel") = _
ActiveDocument.Bookmarks("\EndOfDoc")
'(Do something)
Loop

Loop: Do a Search, Then Execute Some Other Commands
Inside a "Do Until End of Document" Loop (version 1):

Do Until ActiveDocument.Bookmarks("\Sel") = _
ActiveDocument.Bookmarks("\EndOfDoc")
Selection.Find.ClearFormatting
With Selection.Find
.Text = "Howdy!"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute

If Selection.Find.Found = True Then
'Do something within the found text
Else
Exit Do
End If
Loop

Loop: Do a Search, Then Execute Some Other Commands Inside
a "Do Until End of Document" Loop (version 2, thanks to Shawn Wilson):

Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "Something"
.ReplacementText = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While Selection.Find.Execute
'Do something within the found text
Loop

Loop: Do a Search, Then Execute Some Other Commands Inside a
"Do Until End of Document" Loop (version 3, thanks to Shawn Wilson):

Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "Something"
.ReplacementText = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
While Selection.Find.Execute
'Do something within the found text
Wend

« M »

Macro, Run a Macro That's in Another Template:
Application.Run "[TemplateName].[ModuleName].[MacroName]
Example: Application.Run "Normal.NewMacros.Macro1"
Example: Application.Run "Normal.Module1.Macro2"

Macro, Run a Macro That's Within the Same Template:
Application.Run MacroName:="[MacroName]"

Move Right, 1 Cell in a Table:
Selection.MoveRight Unit:=wdCell

Move Right, a Few Cells in a Table:
Selection.MoveRight Unit:=wdCell, Count:=3

Move Right, With Extend On:
Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend

Move Right:
Selection.MoveRight Unit:=wdCharacter, Count:=1

Move Up One Paragraph:
Selection.MoveUp Unit:=wdParagraph, Count:=1

MsgBox Result:
Dim intMsgBoxResult as Integer
intMsgBoxResult = MsgBox("Are you alive", vbYesNo + _
vbQuestion, "Current Status")

MsgBox, Use the MsgBox Result:
Dim intMsgBoxResult as Integer
If intMsgBoxResult = vbYes Then
'Do something
End If

« N »

Number, Is Selected Text a Number? (IsNumeric function)
Dim strSelText As String
strSelText = Selection.Text
If IsNumeric(strSelText) = True Then
MsgBox ("It's a number!")
Else
MsgBox ("It's not a number!")
End If

Number of Pages, Determine # Pages in Document:
Dim varNumberPages as Variant
varNumberPages = _
ActiveDocument.Content.Information(wdActiveEndAdjustedPageNumber)

« O »

« P »

Paragraph, Justify Paragraph:
Selection.ParagraphFormat.Alignment = wdAlignParagraphLeft

Paragraph, KeepLinesTogether:
Selection.ParagraphFormat.KeepTogether = True

Paragraph, KeepWithNext:
Selection.ParagraphFormat.KeepWithNext = True

Paragraph, Space After:
Selection.ParagraphFormat.SpaceAfter = 12

Paragraph, Space Before:
Selection.ParagraphFormat.SpaceBefore = 0

Paragraph, WidowOn:
Selection.ParagraphFormat.WidowControl = True

Paste:
Selection.Paste

Properties, Set Properties On the Fly:
cmdOK.Visible = False
cmdOK.Enabled = False
optOther.Value = False
txtOther.Text = ""

« Q »

« R »

Run a Macro That's in Another Template:
Application.Run "[TemplateName].[ModuleName].[MacroName]
Example: Application.Run "Normal.NewMacros.Macro1"
Example: Application.Run "Normal.Module1.Macro2"

Run a Macro That's Within the Same Template:
Application.Run MacroName:="[MacroName]"

« S »

Select, All (Entire Document):
Selection.WholeStory

Select, Entire Line:
Selection.EndKey Unit:=wdLine, Extend:=wdExtend

Select, Entire Line (Except Paragraph Mark):
Selection.EndKey Unit:=wdLine, Extend:=wdExtend
Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend

Select, Text, Using Extend:
Selection.EndKey Unit:=wdLine, Extend:=wdExtend
Selection.HomeKey Unit:=wdLine
Selection.MoveRight Unit:=wdCharacter, Count:=2, _
Extend:=wdExtend

Smart Cut & Paste Off:
Options.SmartCutPaste = False

Smart Quotes, Turn On "Smart Quotes As-You-Type":
With Options
.AutoFormatAsYouTypeReplaceQuotes = True
End With

Start of Line:
Selection.HomeKey Unit:=wdLine

Style, Copy Style Using Organizer:
Dim strThisDocument as String
strThisDocument = ActiveDocument.FullName
Application.OrganizerCopy Source:= _
"C:\Program Files\Microsoft Office\Templates\Normal.dot", _
Destination:=strThisDocument, Name:="[StyleName]", _
Object:=wdOrganizerObjectStyles

Style, Set a Style:
Selection.Style = ActiveDocument.Styles("[StyleName]")

« T »

Table of Contents, Update Page Numbers Only:
ActiveDocument.TablesOfContents(1).UpdatePageNumbers

Table, Go to 1st Table in Document:
Selection.GoTo What:=wdGoToTable, Which:=wdGoToFirst, _
Count:=1, Name:=""

Table, Show Table Gridlines:
ActiveWindow.View.TableGridlines = True

Table, Take Borders Off Table:
With Selection.Cells
.Borders(wdBorderLeft).LineStyle = wdLineStyleNone
With .Borders(wdBorderRight)
.LineStyle = wdLineStyleSingle
.LineWidth = wdLineWidth075pt
.ColorIndex = wdAuto
End With
With .Borders(wdBorderTop)
.LineStyle = wdLineStyleSingle
.LineWidth = wdLineWidth075pt
.ColorIndex = wdAuto
End With
With .Borders(wdBorderBottom)
.LineStyle = wdLineStyleSingle
.LineWidth = wdLineWidth075pt
.ColorIndex = wdAuto
End With
.Borders.Shadow = False
End With
With Options
.DefaultBorderLineStyle = wdLineStyleSingle
.DefaultBorderLineWidth = wdLineWidth050pt
.DefaultBorderColorIndex = wdAuto
End With

Table, Total the Numbers
Selection.InsertFormula Formula:="=SUM(ABOVE)", _
NumberFormat:="#,##0.00"

Tabs, Clear All:
Selection.ParagraphFormat.TabStops.ClearAll
ActiveDocument.DefaultTabStop = InchesToPoints(0.5)

Tabs, Tab Stop, Add:
Selection.ParagraphFormat.TabStops.Add _
Position:=InchesToPoints(6.63), _
Alignment:=wdAlignTabRight, Leader:=wdTabLeaderSpaces

TextBox, Is There Something in a Text Box:
If txtStuff.Text := "" Then
MsgBox("There is nothing in the text box")
End If

Text File, Open, Write to & Close:
Open "C:\Temp\MyFile.txt" For Output As #1
Write #1, "This is some text."
Close #1

Text File, Open, Read Data Into Variables & Close:
This example assumes that "C:\Temp\MyFile.txt" is a text file with a few
lines of text in it, each line containing a string in quotations and a
number separated by a comma. For example:
"Howdy Doodie", 12345
"Good Morning", 67890

Dim MyString, MyNumber
Open "C:\Temp\MyFile.txt" For Input As #1
Do While Not EOF(1)                'Loop until end of file
Input #1, MyString, MyNumber       'Read data into two variables
MsgBox (MyString & " " & MyNumber) 'Show variable contents in message box
Loop
Close #1

« U »

Underline, Turn On Single Underline:
Selection.Font.Underline = wdUnderlineSingle

Underline, Turn Off Single Underline:
Selection.Font.Underline = wdUnderlineNone

Unload Forms - Unload All of Them (i.e., at End of Program):
Dim frm as Userform
For Each frm in Userforms
Unload frm
Next frm

User Info, Set User Initials in Tools, User Info:
Application.UserInitials = "[initials]"   OR
(If getting user initials from an .ini file:)
Application.UserInitials = System.PrivateProfileString(strIniFile, _
"Initials", strUserName)      OR
Application.UserInitials = strUserInitials

User Info, Set User Name in Tools, User Info:
Application.UserName = "[UserName]"      OR
Application.UserName = cbxUserName.Value   OR
Application.UserName = strUserName

« V »

Value, Get Value of a Number From a String:
intNumber = Val(txtNumber.Text)

Variable, Declare:
Note: "Dim" stands for "Dimension"
Dim [VariableName] as [TypeOfVariable]
Example: Dim strName as String
There are many kinds of Variables and ways to declare them.
Look under VBA "Help" for a listing and explanation.

View, Bookmarks:
ActiveWindow.View.ShowBookmarks = True

View, Current Page Header:
ActiveWindow.ActivePane.View.SeekView = _
wdSeekCurrentPageHeader

View, Field Codes (with all the junk):
ActiveWindow.View.ShowFieldCodes = _
Not ActiveWindow.View.ShowFieldCodes
With ActiveWindow
With .View
.ShowFieldCodes = True
End With
End With

View, Field Codes:
ActiveWindow.View.ShowFieldCodes = True

View, Footer:
ActiveWindow.ActivePane.View.SeekView = _
wdSeekCurrentPageFooter

View, Header:
ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader

View, Header (with all the junk):
If ActiveWindow.View.SplitSpecial := wdPaneNone Then
ActiveWindow.Panes(2).Close
End If
If ActiveWindow.ActivePane.View.Type = wdNormalView Or _
ActiveWindow.ActivePane.View.Type = wdOutlineView Or _
ActiveWindow.ActivePane.View.Type = wdMasterView Then _
ActiveWindow.ActivePane.View.Type = wdPageView
End If
ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader

View, Main View (Close Header or Footer:)
ActiveWindow.ActivePane.View.SeekView = wdSeekMainDocument

View, Options:
Application.DisplayStatusBar = True
With ActiveWindow
.DisplayHorizontalScrollBar = True
.DisplayVerticalScrollBar = True
.DisplayVerticalRuler = True
.DisplayScreenTips = True
With .View
.ShowAnimation = True
.ShowPicturePlaceHolders = False
.ShowFieldCodes = False
.ShowBookmarks = False
.FieldShading = wdFieldShadingWhenSelected
.ShowTabs = False
.ShowSpaces = False
.ShowParagraphs = False
.ShowHyphens = False
.ShowHiddenText = False
.ShowAll = True
.ShowDrawings = True
.ShowObjectAnchors = False
.ShowTextBoundaries = False
.ShowHighlight = True
End With
End With

View, Turn Off View Bookmarks:
ActiveWindow.View.ShowBookmarks = False

« W »

Window, Maximize Application Window:
Application.WindowState = wdWindowStateMaximize

« X »

« Y »

« Z »

Getting Started with VBA in Word 2010

Summary:  Introduces new programmers to using Visual Basic for Applications (VBA) programming to extend Microsoft Word 2010. This topic summarizes the VBA language, instructions about how to access VBA in Word 2010, a detailed solution to a real-world Word VBA programming problem, and tips about how to program and debugging.

Applies to:  Microsoft Word 2010 | Microsoft Office Word 2007 | Microsoft Office Word 2003

 

What is a Macro, Why Would I Want One, and How Can I Create One?




A macro enables you to put Word on autopilot.

It can be frustrating to perform a frequent task in Word if you have to use a long sequence of mouse clicks or key strokes every time that you perform the task. When you write a macro, you can bundle up a collection of commands and instruct Word to start them with only one click or one keystroke.

This article assumes that you are a skilled user of Word. It also does not assume that you know anything about computer programming or software development.

This article is in two parts:

  1. The first part of the article describes an end-to-end example: how to write a simple macro, where you can save it, how to create a button on the Quick Access Toolbar or create a keyboard shortcut to run the macro, and how to copy that macro to another computer (so you can use it at home and at work, or so you can share it with a colleague). That will give you a simple example.

  2. The second part of the article examines macros in detail: how to write more complex macros and how to ensure that your macro actually does what you want and does not create errors or problems.


Part 1: An End-to-End Macro Example




One purpose of macros is to enable you to perform a task with one click or one keystroke.

I often create documents that contain hyperlinks to Web sites. By default, Word requires me to follow, or open, a hyperlink, by pressing CTRL while clicking the hyperlink. The following image shows the default option for following a hyperlink.
Figure 1. Press CTRL and click the hyperlink to follow it

Default option for following a hyperlinkThe following image shows the alternative option for following a hyperlink by clicking it.
Figure 2. Click to follow hyperlink

Alternative option for following a hyperlinkSometimes that is useful, but sometimes I prefer to follow a hyperlink by clicking it (Figure 2). I may configure that setting many times a day. To do that, click the File button, and under Help, I click Options. In the Word Options dialog, I click Advanced, and then under Editing options, I select (or clear) Use CTRL+Click to follow hyperlink and then click OK. Doing that sequence of mouse clicks repeatedly is frustrating. I want to be able to click one time to configure this setting.

This section describes how to create a simple macro to toggle between using CTRL+Click and only clicking a hyperlink. To create this macro, you will learn how to:

  • Create a Word Macro-Enabled Template file (.dotm) to store your macro and save it in the Word Startup folder.

  • Open and use the Visual Basic Editor, which is part of Word.

  • Write a macro in the Visual Basic Editor.

  • Test your macro.

  • Create a button on the Quick Access Toolbar to run your macro.

  • Create a keyboard shortcut to run your macro.

  • Save your file, load it as an add-in and, if you need to, share it with a colleague, or use it on another computer.


This section describes each step.

Creating a File to Hold Your Macro




Before you start to write a macro, think about how and where you want to save it.

Word lets you save macros in two Word file types: a Word Macro-Enabled Document file (.docm) and a Word Macro-Enabled Template file (.dotm). It is not a recommended practice to store macros in a document. Macros are usually stored in a Word Macro-Enabled Template file (.dotm).

It is possible to save macros in the Normal Template (Normal.dotm), but that makes it difficult to organize or share your macros. It is usually better to create your own file and store your macros there.

Where to save your template file depends on how you want to use the macros. You must decide whether you want to use your macros with all the documents that you might work on or only with some documents.

For example, you may create a template for monthly sales reports, and you may have macros to format those reports. In this case, save the template file in the User Templates folder, and use that template as the basis for new documents. (To verify the User Templates folder, click the File tab, and then click Options. In the Word Options dialog, click Advanced, and then click the File Locations button.) If a macro is stored in a template, then only documents attached to that template can access the macro. Other documents will be unable to “see” the macro.

In this article, you will create macros that can be used by any document. To do that, you will create a Macro-Enabled Template file. First, close any Word files that you have open. To create the new file to hold your macros, click the File button, click New, and then click My Templates. In the New dialog box, select the Template option button and then click OK. Save the file as a Macro-Enabled Template file that is named MyWordTools.dotm in the Word Startup folder. (To verify the Word Startup folder, click the File tab, and then click Options. In the Word Options dialog, click Advanced, and then click the File Locations button.)


Using the Visual Basic Editor




Word macros are written in a programming language called Visual Basic for Applications (VBA).

You write macros in a part of Word that most users never see: the Visual Basic Editor (the VBE). Open the VBE by using any one of the following methods:

  • Press the keyboard shortcut, Alt+F11

  • Click the Visual Basic button on the Developer tab. To do that, click the File tab, and then click Options. On the Word Options dialog box, click Customize Ribbon. In the right side of the dialog box, select the Developer tab. Click OK to return to your document, and then on the Developer tab, click the Visual Basic button.

  • Add the Visual Basic command to the Quick Access Toolbar.


Before you start to use the VBE, on the Tools menu, click Options. In the Options dialog box, on the Editor tab, ensure that all the check boxes are selected.

In the upper-left side of the VBE you will see the Project Explorer. This shows all files that are currently open in Word. Expect to see Normal (which refers to Normal.dotm) and MyWordTools (the new file that you just created).

The following image shows the MyWordTools project in the Visual Basic Editor Project Explorer.

Figure 3. Visual Basic Editor Project Explorer

Visual Basic Editor Project ExplorerWithin your file, macros are stored in Modules. To add a module to your file, in the Project Explorer, select the MyWordTools file (Figure 3). On the Insert menu, click Module. When you add a module to your file, you will see the module added in the Project Explorer and in the Properties Window under it (Figure 4). You can rename a module using the Properties Window. Leave the name as Module1.

The following image shows Module1 in the Visual Basic Editor Project Explorer.

Figure 4. Visual Basic Editor Project Explorer

Visual Basic Editor properties windowFinally, you will see that the entry for your file is named TemplateProject. Although it is not necessary, it is recommended that you give it a more descriptive name. To do that, right-click the entry for the MyWordTools file, and then click TemplateProject Properties on the shortcut menu (Figure 5).

The following image shows the TemplateProject Properties… menu item

Figure 5. Properties menu item

Selecting project properties pop-up menu itemIn the Template Project-Project Properties dialog, change the Project name to MyWordTools.


Writing Your Macro




The large white area on the right in the VBE is where you write the code of your macro. If you cannot see it, on the View menu, click Code to see the code window. Word automatically inserts the statement Option Explicit at the top of the code window. Do not delete this.

You want a macro to toggle between the two possible settings, in Word, for following a hyperlink. It will work like other buttons that toggle a setting in Word. The Bold button on the Home tab, for example, will text bold if it is not bold and make text not bold text if it is currently bold.

Copy and paste the following macro code example into the code window.






Sub ToggleHyperlinkCtrlClick()
Options.CtrlClickHyperlinkToOpen = Not Options.CtrlClickHyperlinkToOpen
End Sub





This is a short macro named ToggleHyperlinkCtrlClick, and it only has one line of code. The one line of code in our macro means "Change the Word option that controls whether I have to use CTRL+Click to open a hyperlink to the opposite of its current setting, that is, to Not its current setting" (Figure 6).

The following image shows a line-by-line explanation of the ToggleHyperLinkCtrlClick method.

Figure 6. Explanation of the ToggleHyperLinkCtrlClick method

Code in Visual Basic Editor code window

Testing Your Macro




To test your macro, use the following procedure.

To test the macro




  1. Arrange the Word and VBE windows so you can see them side by side.

  2. Click the main Word window. Type several paragraphs of text into the MyWordTools.dotm document. Include some hyperlinks in your text (Figure 7).

    The following image shows the MyWordTools document and the ToggleHyperLinkCtrlClick code in the VBE side by side.

    Figure 7. Document and Visual Basic Editor side by side

    Split screen of document and Visual Basic Editor

  3. In the VBE, click anywhere within your macro. To run your macro, on the Run menu, click Run Sub/User Form or press F5.

  4. The setting to follow a hyperlink will be changed. Mouse over a hyperlink in the main Word window to see that the tooltip has changed.

  5. Re-run the macro to toggle the setting.


You can also run your macro within Word itself. On the View tab, in the Macros group, click the Macros button. Your ToggleHyperlinkCtrlClick macro will be listed in the Macros dialog. To run your macro, click the name of the macro, then click the Run button.



Creating a Button on the Quick Access Toolbar to Run Your Macro




To get one-click access to your macro, you can add a button to the Quick Access Toolbar. To do that, use the following procedure.

To create a button on the Quick Access Toolbar




  1. Right-click the Quick Access Toolbar and then click Customize Quick Access Toolbar on the shortcut menu.

  2. Under Customize the Quick Access Toolbar, in the Choose commands from list, select Macros.

  3. In the Customize Quick Access Toolbar list, select MyWordTools.dotm. (You must select MyWordTools.dotm so that Word will save the button on the Quick Access Toolbar in the MyWordTools.dotm file. Only then will the button be available when you copy that file to another computer.)

  4. Select the ToggleHyperlinkCtrlClick macro and then click Add.

  5. Click the Modify button to select a symbol and change the name to ToggleHyperlinkCtrlClick.


You can now run your macro at any time by clicking the new button on the Quick Access Toolbar.




Creating a Keyboard Shortcut to Run Your Macro




You can also create a keyboard shortcut to run your macro. To do that, use the following procedure.

To create a keyboard shortcut to run the macro




  1. Right-click the Quick Access Toolbar, and then click Customize the Ribbon on the shortcut menu. Next to Keyboard shortcuts, click the Customize button.

    The following image shows the Customize Keyboard dialog box

    Figure 8. Customize Keyboard dialog box

    Customize Keyboard dialog box

  2. In the Customize Keyboard dialog box (Figure 8), you must:

    1. In the Categories list, select Macros.

    2. In the Macros list, click your macro name.

    3. Click in the Press new shortcut key box, and type the keyboard shortcut that you want to use. I used Alt+H, which I can remember because this toggles the setting for Hyperlinks. The dialog box also tells me that this shortcut is currently unassigned, so I will not interfere with an existing keyboard shortcut.

    4. In the Save changes in list, select MyWordTools.dotm. (You must select MyWordTools.dotm so that Word will save the keyboard shortcut in the MyWordTools.dotm file. Only then will the button be available when you copy that file to another computer.)

    5. Click Assign.




To run your macro, press Alt+H.



Finishing Up




You have now created a new file to hold your macro (MyWordTools.dotm), added a Module (Module1), created your macro (known as ToggleHyperlinkCtrlClick), created a button on the Quick Access Toolbar to start the macro, and created a keyboard shortcut to start the macro. Now, save the MyWordTools.dotm file (you can do this in the VBE or from the main Word window).

Because you have finished with the VBE, close it and return to Word. To do that, in the VBE, click the File menu and then click Close and Return to Microsoft Word.

To text your macro, click your button on the Quick Access Toolbar. When you confirm that it is working, save and close the file.


Managing and Loading Add-ins




The plan was that this macro should be available regardless of what document that you are working on. However, if you create a new document (use CTRL+N) you will be unable to see your button on the Quick Access Toolbar. There is no way to start your macro.

To make the macro stored in MyWordTools.dotm available to any document that you have open, you must load MyWordTools.dotm as an add-in. (When a .dotm file is used as an add-in it is also known as a ‘global template’.) You can load a .dotm file as an add-in either manually or automatically:

  • To load a .dotm file as an add-in manually, close the.dotm file if it is currently open. On the Developer tab, click the AddIns button. In the Templates and Add-ins dialog, click Add, locate your .dotm file and then click Open.

  • To load a .dotm file as an add-in automatically, the .dotm file must be saved in the Word Startup folder. Quit and restart Word. Word will load your add-in automatically.


You can see what add-ins are currently loaded in Word. On the Developer tab, click the Add-Ins button.

Because you saved MyWordTools.dotm in the Word Startup folder, close and restart Word. Word will automatically load your MyWordTools.dotm as an add-in. You should see the button on the Quick Access Toolbar, and you can use your macro.

When MyWordTools.dotm is loaded as an add-in, you will not see any of the text you may have left in the main Word window in MyWordTools.dotm. Word makes no use of any content on the face of the document itself. Delete any text in the main Word window before you save a .dotm file that is used as an add-in.

It is important to differentiate between opening an add-in file (for example, by clicking File and then clicking Open) and loading an add-in (for example, by using the Add-Ins button on the Developer tab). Open a file when you want to edit it and test it. Load the file as an add-in when you want to use it.










Caution Caution:
Never load a file as an add-in if that file is currently open in Word. Never open and edit an add-in file if it is currently loaded as an add-in. This may result in unpredictable behavior.

 



Using Your Macro on Another Computer




To use your macro, you must have:

  • The macro code.

  • A button on the Quick Access toolbar that runs the macro.

  • The keyboard shortcut that runs the macro.


You saved all three in MyWordTools.dotm. So, to use your macro on another computer, or to share your macro, you just have to to copy MyWordTools.dotm to the Word Startup folder on another computer. When you start Word, Word will load MyWordTools.dotm as an add-in and your macro, the button and the keyboard shortcut will all be available.

 



Part 2: Writing More Complex Macros




The end-to-end example in Part 1 includes a simple one-line macro. Part 2 describes how to extend that basic end-to-end process to create more complex macros.

Adding Additional Macros to the MyWordTools.dotm File




The file that you created in Part 1, MyWordTools.dotm, now contains one module (Module1), and that module contains one macro (ToggleHyperlinkCtrlClick).

Do not edit a file while it is loaded as an add-in. To add additional macros to your file, you must:

  1. Unload the file as an add-in. To do that, on the Developer tab, click Add-Ins, and then click to clear the check box for your add-in and then click OK.

  2. Open the file for editing. To do that, click the File tab. Under Info, click Open. Locate the file, click the file, and then click Open.


When your file is open for editing, you can:

  • Add a macro to the existing module, Module1, by typing in the code window, or

  • Add a new module to the file, and type a new macro in the new module.


Which method that you use depends on how complex your macros are. If you only have several short macros, then it is common to put them all in one module. If you have many long macros, you may prefer to store them in separate modules and rename each module to indicate what macros the module contains.

When you have added a new macro to your file, you can test it, create a button for it on the Quick Access Toolbar, and create a keyboard shortcut for it, exactly as you did for your first macro.

 


Comments




Professional developers generally include comments in their code to explain what the code is intended to do.

You can add comments to your macros by preceding the text of the comment with a single apostrophe. By default, the VBE displays comments in green.


Writing Robust Code: A Macro to Sort Text




There are two things to consider if you share a macro with a colleague. First, the other person’s computer is almost definitely not set up identical to yours. Second, the other person is likely to be less forgiving of poor code than you are. So your code must be robust.

If a macro is robust then the user will not see inexplicable error messages and the macro will do what it is intended to do—no more and no less.

To demonstrate some issues in writing robust code, consider the following macro that is used to sort text.

It takes several mouse clicks to sort several paragraphs of text. If you must sort text frequently, it may be useful to have a single button to do a simple sort (and when you must do a more complex sort, the builtin button will still be available to you on the Home tab). This macro action is shown in the following code example.






Sub SortText1()
' A macro to sort the selected text
Selection.Sort
End Sub





You can test this macro as you tested the previous one. Tile the Word and VBE windows side by side. In the main Word window, type several paragraphs of text and select them. In the VBE, run the macro by clicking in the macro code and pressing F5. Word will sort the paragraphs alphabetically.

Our SortText1 macro seems to work well. However, it is not robust. To see what can go wrong, insert a picture into your document. Make it a floating picture. To do that, select the picture. On the Format tab, under Picture Tools, in the Arrange group, click Position. Select any one of the With Text Wrapping page position options.

Select your picture, and then run the SortText1 macro. Because it does not make sense for Word to sort one floating picture, you will see an error message from Visual Basic. That is not a robust macro!

Our one line of code, Selection.Sort, only works correctly if you have selected ordinary text. Before you find a way to solve that problem, consider another problem.










Caution Caution:
If you click in your document without selecting any text and run the SortText1 macro, the whole document is sorted. At best, that is unexpected; at worst, it might corrupt data.

 


The following code example shows how to restrict the macro to sorting only when there are two or more paragraphs of selected text.






Sub SortText2()
' A macro to sort the Selection if the user has selected more than one
' paragraph of text.
If Selection.Paragraphs.Count > 1 Then
Selection.Sort
End If
End Sub





Experiment running the SortText2 macro. Unlike SortText1, this macro will not display an error message if you select a picture and then run the macro. Also, it will not let you accidentally sort your whole document. To see what happens, use the following procedure to step through the code one line at a time.

To step through the VBA code




  1. Click in the main Word window, and select your picture.

  2. Click anywhere within the SortText2 macro.

  3. Press F8. Word will highlight the first line in the macro (Sub SortText2()).

  4. Press F8 repeatedly to step line by line through the code.



Using F8 to step through code is a common way to see what the code is doing, and to troubleshoot problem code. In this case, you see that, when a picture is selected, Word processes the If statement, but skips the Selection.Sort statement.

There are several important things to see in the new SortText2 macro:

  • You can use If and End If to control whether Word processes code or skips it.

  • The If line ends with the keyword Then.

  • Each If must have a corresponding End If.

  • This macro uses Selection two times. Selection.Paragraphs.Count provides information about what the user has selected. Selection.Sort sorts the selected text.


The following code example show how to extend this macro to provide information to the user by using the Else and MsgBox keywords.






Sub SortText3()
If Selection.Paragraphs.Count > 1 Then
' The user has selected more than one paragraph of text.
' Sort the selection.
Selection.Sort
Else
' Tell the user what to do.
MsgBox "Please select two or more paragraphs and try again."
End If
End Sub





From this macro, you can see that:

  • You can use IfThen, Else and End If to control whether Word processes code or skips it.

  • Using an Else statement is optional. Each If statement may have zero or one Else statements.

  • If you use Else, you still need an End If.

  • The command displays a message to the user on the screen. Type the text of the message after the keyword MsgBox, and enclose it in double quotation marks. If you provide any text in a macro (such as the text of this message), you must enclose it in quotation marks ("). Without quotation marks, Word would try to find "Please" in the Word object model. Because Word does not know what "Please" means, the macro would cause an error message.


To see the problem, use the following procedure.

To test the SortText3 macro




  1. Create a button on the Quick Access Toolbar to start the SortText3 macro. Instruct Word to save the button in MyWordTools.dotm.

  2. Test your button to ensure that it runs the SortText3 macro.

  3. Save the MyWordTools.dotm file

  4. Quit and restart Word

  5. If a document is open, close it so that there is no document open.

  6. Test your button on the Quick Access Toolbar again.



You should see an error message. The error is occurring because the first line of the macro refers to the Selection. The add-in is loaded. If no document is open in the main Word window, then there is no Selection. Without a Selection, Word cannot run the code in your macro.

Make one more change to the macro to make it robust. Add a comment to the top of the macro. That is a good way to document your macro so anyone will know what the macro is intended to do.

Unload the add-in, and then open the file for editing. Replace your existing macro with the following code example.






Sub SortText()
' A macro to sort the selected text, if the user has selected
' more than one paragraph

If Documents.Count > 0 Then
' The user has at least one document open.

If Selection.Paragraphs.Count > 1 Then
' The user has selected more than one paragraph
' of text, so sort it.
Selection.Sort
Else
' Tell the user what to do.
MsgBox "Please select two or more paragraphs and try again."
End If
End If
End Sub






In this final version of SortText, this example used nested If…End If blocks. It is important to indent lines of code. Each If is matched to the correct End If.

A macro that is not robust can result in:

  • Error messages (for example, when the Selection object was used, but there was no document open, and therefore no Selection), or

  • Unwanted behavior (for example, when the Selection.Sort command unintentionally sorted the whole document).


Here are tips for writing robust macros.

  • If your macro refers to the Selection, create a test document and test your macro by running it after you select different parts of the document: just click so you have selected no text, move the curso to the top of the document or the bottom of the document, or select a single word, several paragraphs, a picture, part or all of a table, text in the header or footer, text in a footnote or endnote or comment, and so on.

  • Close your file, load it as an add-in and test your macro to ensure that the macro works if there is no document open. Use If Documents.Count > 0 to verify if a document is open.



Learning About the Word Object Model




The macros created to this point have used three important Word elements:

  • Options. This refers to the Options used to set up Word.

  • Documents. This refers to all the Documents currently open in Word. You can use the keyword ActiveDocument to refer to the currently-active document.

  • Selection. This refers to whatever the user has selected in the ActiveDocument. It might be one word, several paragraphs, a picture, or any other kind of content.


For each of these macros, the following lower-level elements were used:

  • CtrlClickHyperlinkToOpen

  • Count

  • Selection.Sort


These elements (Options, Documents, and Selection) are known as Objects, and together they are part of the Word object model.

An object model is a hierarchy. The hierarchy includes all the parts of Word that you might need a macro to control. For example, the ActiveDocument object refers to the currently-active document.

If your current document that is contained a table with at least 3 rows and at least 2 columns, then the following would display the text in the second cell in the third row of the first table in the active document.






MsgBox ActiveDocument.Tables(1).Rows(3).Cells(2).Range.Text





A macro can manipulate an object (such as the Selection object or the ActiveDocument object). Actually, objects have two ways to manipulate them: methods and properties.

Methods are like verbs, they describe action. For example:

  • ActiveDocument.PrintPreview

  • ActiveDocument.AcceptAllRevisionsShown


Properties are like adjectives, they describe objects. For example:

  • MsgBox ActiveDocument.Paragraphs(1).Range.Text

  • ActiveDocument.Paragraphs(1).Range.Text = “New text for Paragraph 1”


In the first example here the macro is “reading” the Text property and displaying the text in a message box. In the second example, the macro is “writing” (or setting) the Text property. Running that line of code will change the existing text in the first paragraph to “New text for Paragraph 1”.

In general, your macro can “read” a property or it can “write” a property. However, some properties are readonly. For example, the Documents object has a property named .Count. Consider the following:

  • MsgBox Documents.Count ' Reports the number of currentlyopen documents.

  • Documents.Count = 4 ' Does not work: .Count is a readonly property.



Writing Shortcut Code: A Macro to Toggle the Display of Text Boundaries




When I am editing a big document, I like to see the text boundaries in my document. When I am proofreading, I prefer to turn off text boundaries. I may turn the display of text boundaries on and off many times in a day. To do that, I click the File button, and under Help, I click Options. In the Word Options dialog, I click Advanced, and then under Show Document Content, I select (or clear) Show Text Boundaries and then click OK. Doing that sequence of mouse clicks repeatedly is frustrating. I would prefer to click one time to turn text boundaries on or off.

The following code example is a macro that is used to toggle the display of text boundaries.






Sub ToggleTextBoundaries1()
ActiveDocument.ActiveWindow.View.ShowTextBoundaries = Not ActiveDocument.ActiveWindow.View.ShowTextBoundaries
End Sub





Word does not mind that the single line of code is so long, but it is much to type, and can be difficult for humans to read. To shorten the macro and make it easier to read, use the With keyword as shown in the following code example.






Sub ToggleTextBoundaries2()
With ActiveDocument.ActiveWindow.View
.ShowTextBoundaries = Not .ShowTextBoundaries
End With
End Sub





Within the object model, you can “drill down” from one object to another using a period (“.”) between each item of the hierarchy. Because the period marks off a child object from its parent, you cannot start a line of code with a period—except when using the With keyword.

Each With must be paired to an End With statement. Between the With and End With, you can use a period to begin a shortcut.






With ActiveDocument.ActiveWindow.View
' Between With and End With you can use
' a period to begin shortcut code.
' The shortcut will refer to ActiveDocument.ActiveWindow.View.
End With





For example, between this With and End With, the shortcut .ShowTextBoundaries refers to ActiveDocument.ActiveWindow.View.ShowTextBoundaries.

When you have code that refers to the same object several times, it will be less to type, and easier to read, if you use With and End With.

This is not yet a robust macro, but you can make a final solid version as shown in the following code example.






Sub ToggleTextBoundaries()
If Documents.Count > 0 Then
With ActiveDocument.ActiveWindow.View
.ShowTextBoundaries = Not .ShowTextBoundaries
End With
End If
End Sub





In this final version of ToggleTextBoundaries, there is a With…End With block nested in an If…End If block. By indenting the lines, you ensure that each With is matched to its End With, and each If statement is matched to its End If statement.


Using With and the Selection Object: A Macro to Insert a Landscape Section




Here is a more complete example using the With keyword: a macro to insert a landscape section at the cursor.

This macro uses the With keyword with the Selection object (that is, With Selection). As you become more skilled in writing macros, you will find that you use the Selection object less and less, because it is often more effective to use a Range object. Studying this macro is a good way to learn how Word manages the Selection. With Word and the VBE tiled side by side, click within the macro and press F8 repeatedly to step through the macro line by line, to see what happens in the main Word window.

This macro also shows that, when you have many lines of text it is recommended that you break them up into logical ‘paragraphs,’ with comments to describe what the code is intended to do.






Public Sub InsertLandscapeSectionHere()
' Purpose: Insert a landscape section at the insertion point,
' and insert text to tell the user where the landscape section is.
If Documents.Count > 0 Then
' The user has a document open, so insert a
' landscape section.
With Selection
' Do not accidentally over-write selected text
.Collapse Direction:=wdCollapseStart

' Insert section breaks with blank paragraphs
' in the new section.
.TypeParagraph
.Style = ActiveDocument.Styles(wdStyleNormal)
.InsertBreak Type:=wdSectionBreakNextPage
.TypeParagraph
.TypeParagraph
.TypeParagraph
.InsertBreak Type:=wdSectionBreakNextPage
.MoveUp Unit:=wdLine, Count:=3

' Set the orientation of new section to landscape.
.PageSetup.Orientation = wdOrientLandscape

' Provide guidance to the user.
.TypeText Text:="Your landscape section starts here."
End With
Else
' Tell the user what to do.
MsgBox "Please open a document and try again."
End If
End Sub





You will see two kinds of syntax in this macro:

  1. The first syntax is uses an equal sign (=):Selection.PageSetup.Orientation = wdOrientLandscape

  2. The second syntax uses a colon and an equal sign (=):Selection.InsertBreak Type:=wdSectionBreakNextPage.


For more information about the differences between these syntax types, see the article “Understanding Visual Basic Syntax” in the VBE Help.


Learning More About Word Macros and the Visual Basic Editor




The Visual Basic Editor (VBE) includes tools to help you write your own macros. The following is a list of things to consider:

  • You can customize the menu and toolbars in the VBE. One useful customization is to create keyboard shortcuts for comments. To do this, right-click in open space near the menu or toolbars and select Customize. In the Customize dialog, on the Toolbars tab, select the Edit toolbar. Leave the Customize dialog box open. On the Edit toolbar, right-click the Comment block button, change the name to &Comment, then click Image and Text. Do the same to the Uncomment block button, renaming it as &Uncomment. You can now select several lines of code and use the keyboard shortcuts Alt+C and Alt+U to comment, or uncomment, the selected code.



  • The VBE includes a large amount of Help information. This will be your main source of information about the Word object model:

    • For general information, in the VBE, on the Help menu, click Microsoft Visual Basic for Applications Help. Be sure that you display the Table of Contents by clicking the little book icon in the toolbar of the client help viewer. It makes browsing the developer help much more intuitive. In particular, under Concepts, read the topics at Frequently Asked Visual Basic Questions.

    • For specific help about a keyword or an object from the Word object model (such as ActiveDocument), click in the word and press F1 to read help about that keyword or object.






Conclusion




Word macros can be long, complex, and powerful. You could write a macro to talk to an external database, perform calculations and return the result to Word. You can also write a macro in Word to control Excel, PowerPoint, or Outlook. Sometimes simple macros can be useful and save you time.

If your macro is to format text, it may be better to create a Style instead of writing a macro. If the macro is to control the layout of a document, it may be better to create a new template. Also, there may be a built-in command that you can add to the Quick Access Toolbar (when customizing the Quick Access Toolbar, in the Choose Commands From: list, click All Commands).

Have fun creating macros, but do not re-invent the wheel by writing a macro to do what Word can already do on its own.