none
Creating setup for VB.NET Word Automation program RRS feed

  • Question

  • Hi,

    I am setting up a software project to perform some simple manipulations in Word templates via Interop. I am refreshing my memory on the basics of Word Automation from VB.NET (I am using VS 2012 Express and Word 2007). For this purpose, I made a simple form with a button and a textbox. I want to have all my code that manipulates Word documents stuck away in a separate module or class. That keeps my form tidy and makes code reusable. However, I am running into the problem that I was never able to solve (I am basically learning myself some VB.NET after normal job hours).

    The codes creates a new document from the standard Word template just fine (although the code to check a running instance of Word does not always function properly). However, I also want to insert text from the textbox at the current place of the cursor (=start of the empty document).

    When running the program, it gives an error upon clicking the button, referring to 

    oDoc.Range.InsertParagraphBefore()

    in the function TypeText(). The error message is "Object reference not set to an instance of an object".  I think I have to feed the document that has been opened in Word as an object to the Function TypeText in my module but I am totally lost how to do this.

    Code of the form is as follows:

    Imports Word = Microsoft.Office.Interop.Word
    
    
    Public Class Form1
    
        Private Property oWord As Object
    
        Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
            
            Dim inSert As String = txtInputWord.Text
            Dim oDoc As Word.Document
    
            NewWordDoc(inSert)
            TypeText(inSert, oDoc)
            
            
        End Sub
    End Class

    Code of the Module is as follows.

     

    Imports Word = Microsoft.Office.Interop.Word
    
    Module WordActions
    
    
    
    
        Public Function NewWordDoc(ByVal inSertText As String)
    
            Dim oWord As Word.Application
            Dim oDoc As Word.Document
    
    
            'Start Word
            Try
    
    
                oWord = GetObject(, "Word.Application")
    
            Catch ex As Exception
                oWord = CreateObject("Word.Application")
    
            End Try
    
            oWord.Visible = True
            oDoc = oWord.Documents.Add
            Return oDoc
    
        End Function
    
        Public Function TypeText(ByVal text As String, ByVal oDoc As Word.Document)
    
            oDoc.Range.InsertParagraphBefore()
            oDoc.Range.Text = text
            oDoc.Range.InsertParagraphAfter()
    
        End Function
    End Module
    I guess this is very basic, but I have not been able to find a solution for this in all these years and simply copied code over and over again just to make things work. I want to do it right this time and will write a tutorial at Code Project or someplace else once I learned how to get all the basic Word actions working through a module or class.


    Friday, October 11, 2013 9:19 PM

Answers

  • 1. GetObject

    In VBA, GetObject will start a new instance of the application if one is not available (already running). In .NET, this is different. If no instance is already running, GetObject throws an error. Correct would be for you to first check the list of Processes for "winword.exe". If one is there, you can use GetObject, otherwise you need to start a new instance. Below is some sample code that demonstrates this. (And if you use it in a Code Project entry, please mention from whom you got it!)

            Dim WordApp As Word.Application
            Dim appName As String = "Word.Application"
            Dim wdProcesses() As System.Diagnostics.Process = _
              System.Diagnostics.Process.GetProcessesByName("winword")
            Dim wdprocess As System.Diagnostics.Process
            For Each wdprocess In wdProcesses
                System.Diagnostics.Debug.Print(wdprocess.MainWindowTitle)
                System.Diagnostics.Debug.Print(wdprocess.StartInfo.Arguments.Length.ToString())
            Next
            If wdProcesses.Length > 0 Then
                WordApp = System.Runtime.InteropServices.Marshal.GetActiveObject(appName)
            Else
                WordApp = New Word.Application
            End If
            If Not WordApp Is Nothing Then
                WordApp.Visible = True
                WordApp.Activate()
                MessageBox.Show(WordApp.ActiveWindow.Caption)
                WordApp = Nothing
            End If
    

    2. Writing text to the document. I'm surprised you weren't able to turn this up as there are examples galore dating back at least ten years...

    If you look at the Language Reference entry for Document.Range you'll see that it takes two parameters to define the start and end point of the Range in the document. Those parameters aren't strictly required in VBA, but they are in .NET.

    If what you want is the entire main body of the document, then use: Document.Content.Text = "abc"

    If what you want is the current selection, then use: WordApp.Selection.Text = "abc"


    Cindy Meister, VSTO/Word MVP, my blog

    Saturday, October 12, 2013 10:10 AM
    Moderator
  • Hi Wekkel

    <<Somehow, I need to make the computer act as if the code is working on the same oWord and oDoc object, while the code itself is scattered over the form I am working with and (reusable) code that remains in a class or module>>

    Ahh, OK :-)

    The trick would be to pass parameters/arguments to the "shared" code. For example, if the procedure you call should work with a specific document, pass it the document. That means that the procedure needs to have such a parameter in its method signature.

    Looking at the code you posted later, for example for a new document:

    Class WordActions
    
        Public Shared Function NewWordDoc(wdApp as Word.Application) as Word.Document
            Dim oDoc As Word.Document
            oDoc = wdApp.Documents.Add()
            Return oDoc
        End Function

    You pass the existing Word.Application object to the procedure NewWordDoc, which requires a parameter of that data type, and returns a Word.Document.

    In order to write text to the current selection, pass that as well as the string, but this time use a "Sub" because you don't want to return anything:

    Public Shared Sub TypeText(ByVal text As String, ByRef Sel as Word.Selection)
        Sel.Text = text
    End Sub
    


    Cindy Meister, VSTO/Word MVP, my blog

    Monday, October 14, 2013 6:42 AM
    Moderator

All replies

  • 1. GetObject

    In VBA, GetObject will start a new instance of the application if one is not available (already running). In .NET, this is different. If no instance is already running, GetObject throws an error. Correct would be for you to first check the list of Processes for "winword.exe". If one is there, you can use GetObject, otherwise you need to start a new instance. Below is some sample code that demonstrates this. (And if you use it in a Code Project entry, please mention from whom you got it!)

            Dim WordApp As Word.Application
            Dim appName As String = "Word.Application"
            Dim wdProcesses() As System.Diagnostics.Process = _
              System.Diagnostics.Process.GetProcessesByName("winword")
            Dim wdprocess As System.Diagnostics.Process
            For Each wdprocess In wdProcesses
                System.Diagnostics.Debug.Print(wdprocess.MainWindowTitle)
                System.Diagnostics.Debug.Print(wdprocess.StartInfo.Arguments.Length.ToString())
            Next
            If wdProcesses.Length > 0 Then
                WordApp = System.Runtime.InteropServices.Marshal.GetActiveObject(appName)
            Else
                WordApp = New Word.Application
            End If
            If Not WordApp Is Nothing Then
                WordApp.Visible = True
                WordApp.Activate()
                MessageBox.Show(WordApp.ActiveWindow.Caption)
                WordApp = Nothing
            End If
    

    2. Writing text to the document. I'm surprised you weren't able to turn this up as there are examples galore dating back at least ten years...

    If you look at the Language Reference entry for Document.Range you'll see that it takes two parameters to define the start and end point of the Range in the document. Those parameters aren't strictly required in VBA, but they are in .NET.

    If what you want is the entire main body of the document, then use: Document.Content.Text = "abc"

    If what you want is the current selection, then use: WordApp.Selection.Text = "abc"


    Cindy Meister, VSTO/Word MVP, my blog

    Saturday, October 12, 2013 10:10 AM
    Moderator
  • Thanks for your reply. I will definitely look into your sample code in respect of checking of Word is running before starting a new instance.

    On the second item, my issue is not typing text in a Word document. That is indeed rare to miss in terms of examples. My issue is something different.

    I want to move code to type text (and replacing bookmarks with text but also opening word docs on the basis of a variable filename etc) to a class or module. This way, I do not have to repeat code over and over again. However, I never achieved to find an example of common Word actions not written as code in a single flow after a button was clicked, but dispersed over code in the form that subsequently falls back to reusable code in a module or class. For most .NET programmers, this is probably kindergarten but for me it remains aca ca dabra

    Somehow, I need to make the computer act as if the code is working on the same oWord and oDoc object, while the code itself is scattered over the form I am working with and (reusable) code that remains in a class or module. For me, that would be the holy grail for my plans. I apologise if that was not clear from my first post.

    Saturday, October 12, 2013 9:41 PM
  • Ok, I got a little further in my quest. Things are working and I am able to have a lot of 'Word Actions' code in a separate file while calling the relevant procedures from the form I am working with. Switching from 'module' to a 'class' with 'shared functions' improved things much.

    I am sharing my current code for others. I am still repeating a lot of code, but at least I can keep the code in my forms limited now.

    The Code

    Form (add http:// yourselves because I cannot post images yet)

    s9.postimg.org/bgzgnx0vz/Word_Actions01.png

    Imports Word = Microsoft.Office.Interop.Word
    
    
    Public Class Form1
    
        Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
            'This procedure will open a new Word document and
            'add the text in the Texbox in the WOrd document 
            'at the cursor location
    
            'define variable and assign value
            'to it from textbox
    
            Dim inSert As String = txtInputWord.Text
    
            'call functions to make it work
            WordActions.NewWordDoc()
            WordActions.TypeText(inSert)
    
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            'This procedure will open a new Word document 
            'on the basis of a template
            'and add text to 2 bookmarks present in the template.
            '
            'The bookmarks will vanish when used so 
            'the resulting Word document is clean
            
            'setting up and assigning values to variables
            'that will be used to insert at the bookmark locations
    
            Dim docVar1a As String = "This is the 1st line of text for " _
                                   + "Document Variant 1"
            Dim docVar1b As String = "This is the 2nd line of text for " _
                                     + "Document Variant 1"
            Dim docVar2a As String = "Howdy, this is the 1st line of text for " _
                                     + "Document Variant 2"
            Dim docVar2b As String = "And this is the 2nd line of text for " _
                                     + "Document Variant 2"
    
            'logic to use either variant A or variant B
            'based on the choice made in the combobox
    
            Select Case cbDocVariant.SelectedIndex
                Case 0
                    WordActions.DocVariant(docVar1a, docVar1b)
                Case 1
                    WordActions.DocVariant(docVar2a, docVar2b)
            End Select
        End Sub
    
        
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            'making sure that the combobox is set at the
            'first Doc variant when the form loads
    
            cbDocVariant.SelectedIndex = 0
    
        End Sub
    End Class


    Class (not a module anymore)

    Imports Word = Microsoft.Office.Interop.Word
    
    Class WordActions
    
        Public Shared Function NewWordDoc()
            'Procedure NewWordDoc()
            '
            '1. check whether Word is running
            '2. if so, open a new document
            '3. if not, start word and open new document
            '
            'Code to check whether Word is running
            'courtesy of Cindy Meister, VSTO/Word MVP
    
            Dim oWord As Word.Application
            Dim oDoc As Word.Document
            Dim appName As String = "Word.Application"
            Dim wdProcesses() As System.Diagnostics.Process = _
              System.Diagnostics.Process.GetProcessesByName("winword")
            Dim wdprocess As System.Diagnostics.Process
    
            For Each wdprocess In wdProcesses
                System.Diagnostics.Debug.Print(wdprocess.MainWindowTitle)
                System.Diagnostics.Debug.Print(wdprocess.StartInfo.Arguments.Length.ToString())
            Next
    
            'If Word is running, attach oWord to that instance of Word
            If wdProcesses.Length > 0 Then
                oWord = System.Runtime.InteropServices.Marshal.GetActiveObject(appName)
    
                'if Word is not running, start new instance of Word
            Else
                oWord = New Word.Application
            End If
    
            If Not oWord Is Nothing Then
                oWord.Visible = True
                oWord.Activate()
    
            End If
    
            oWord.Visible = True
    
            'open new Word document based on the basic template
            oDoc = oWord.Documents.Add
    
            'Don't know what it does but by doing so, I 
            'avoid a warning in Visual Studio about possibly
            'no returning a value on all code paths
            Return oWord
    
            oDoc = Nothing
            oWord = Nothing
    
    
        End Function
    
        Public Shared Function TypeText(ByVal text As String)
            'Procedure TypeText(inSert)
            '1. check whether Word is running
            '2. if so, type text in the active document
            '3. if not, show message that Word is not running
            '
            'Code to check whether Word is running
            'courtesy of Cindy Meister, VSTO/Word MVP
    
            Dim oWord As Word.Application = Nothing
            Dim oDoc As Word.Document
            Dim appName As String = "Word.Application"
            Dim wdProcesses() As System.Diagnostics.Process = _
              System.Diagnostics.Process.GetProcessesByName("winword")
            Dim wdprocess As System.Diagnostics.Process
    
            For Each wdprocess In wdProcesses
                System.Diagnostics.Debug.Print(wdprocess.MainWindowTitle)
                System.Diagnostics.Debug.Print(wdprocess.StartInfo.Arguments.Length.ToString())
            Next
            'If Word is running, attach oWord to that instance of Word
            If wdProcesses.Length > 0 Then
                oWord = System.Runtime.InteropServices.Marshal.GetActiveObject(appName)
    
                'if Word is not running, show appropriate message
            Else
                MessageBox.Show("Word is not running")
    
            End If
    
            'type text from variable at current location of cursor
            'in the Active Document
            oDoc = oWord.ActiveDocument
            oDoc.Range.Text = text
    
    
            oDoc = Nothing
            oWord = Nothing
    
            'Don't know what it does but by doing so, I 
            'avoid a warning in Visual Studio about possibly
            'no returning a value on all code paths
            Return oWord
    
        End Function
    
        Public Shared Function DocVariant(docvarA As String, docvarB As String)
            'Procedure that opens new Word document based
            'on a template and inserts text provided by
            'two variables at two bookmark locations.
            'The bookmarks will be gone once used so
            'the resulting Word document is clean
            '
            'Code to check whether Word is running
            'courtesy of Cindy Meister, VSTO/Word MVP
    
            'Declare variable that will hold the path
            'to the file name of the Word document/template 
            'to be used
            Dim templatePath As String = Application.StartupPath
    
            Dim oWord As Word.Application = Nothing
            Dim oDoc As Word.Document
            Dim appName As String = "Word.Application"
            Dim wdProcesses() As System.Diagnostics.Process = _
              System.Diagnostics.Process.GetProcessesByName("winword")
            Dim wdprocess As System.Diagnostics.Process
    
            For Each wdprocess In wdProcesses
                System.Diagnostics.Debug.Print(wdprocess.MainWindowTitle)
                System.Diagnostics.Debug.Print(wdprocess.StartInfo.Arguments.Length.ToString())
            Next
            'If Word is running, attach oWord to that instance of Word
            If wdProcesses.Length > 0 Then
                oWord = System.Runtime.InteropServices.Marshal.GetActiveObject(appName)
    
                'if Word is not running, show appropriate message
            Else
                MessageBox.Show("Word is not running")
    
            End If
    
            'open new Word document on the basis of
            'the Word template provided
            oDoc = oWord.Documents.Add(templatePath + "\Templateinsertatbookmark.dotx")
    
            'place text from variable at the designated
            'bookmarks in the Word document
            oDoc.Bookmarks("bkdocvarA").Range.Text = docvarA
            oDoc.Bookmarks("bkdocvarB").Range.Text = docvarB
    
    
            oDoc = Nothing
            oWord = Nothing
    
            'Don't know what it does but by doing so, I 
            'avoid a warning in Visual Studio about possibly
            'no returning a value on all code paths
            Return oWord
    
        End Function
    End Class

    Result of the combo box item (add http:// yourselves because I cannot post images yet):

    s9.postimg.org/ltlr9zuf3/Word_Actions03.png

    If anyone has ideas to streamline this code some more, that would be very helpful. 



    • Edited by WekkelEkkel Sunday, October 13, 2013 8:41 PM typos
    Sunday, October 13, 2013 8:39 PM
  • Hi Wekkel

    <<Somehow, I need to make the computer act as if the code is working on the same oWord and oDoc object, while the code itself is scattered over the form I am working with and (reusable) code that remains in a class or module>>

    Ahh, OK :-)

    The trick would be to pass parameters/arguments to the "shared" code. For example, if the procedure you call should work with a specific document, pass it the document. That means that the procedure needs to have such a parameter in its method signature.

    Looking at the code you posted later, for example for a new document:

    Class WordActions
    
        Public Shared Function NewWordDoc(wdApp as Word.Application) as Word.Document
            Dim oDoc As Word.Document
            oDoc = wdApp.Documents.Add()
            Return oDoc
        End Function

    You pass the existing Word.Application object to the procedure NewWordDoc, which requires a parameter of that data type, and returns a Word.Document.

    In order to write text to the current selection, pass that as well as the string, but this time use a "Sub" because you don't want to return anything:

    Public Shared Sub TypeText(ByVal text As String, ByRef Sel as Word.Selection)
        Sel.Text = text
    End Sub
    


    Cindy Meister, VSTO/Word MVP, my blog

    Monday, October 14, 2013 6:42 AM
    Moderator
  • Thanks Cindy,

    I was able to implement the first part of your code examples. I had to declare oWord as Word.Application in the sub (button click) in my main form, but it worked fine. 

    I do have some trouble with the second part of your code example. I tried several ways to get a Word.Selection (or my oWord object) into this Sub but I get an error: "object reference not set to an instance of an object". The error occurs in the line of code of the Typtext2() Sub

    Main code:

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            Dim oWord As Word.Application = Nothing
            Dim text As String = txtInputWord.Text
    
            WordActions.NewWordDoc2(oWord)
    
            WordActions.TypeText2(text, oWord)
    
            oWord = Nothing
            
        End Sub

    Code in WordActions class

    Public Shared Function NewWordDoc2(oWord As Word.Application) As Word.Application
            'Procedure NewWordDoc2()
            '
            '1. check whether Word is running
            '2. if so, open a new document
            '3. if not, start word and open new document
            '
            'Code to check whether Word is running
            'courtesy of Cindy Meister, VSTO/Word MVP
    
            Dim oDoc As Word.Document
            Dim appName As String = "Word.Application"
            Dim wdProcesses() As System.Diagnostics.Process = _
              System.Diagnostics.Process.GetProcessesByName("winword")
            Dim wdprocess As System.Diagnostics.Process
    
            For Each wdprocess In wdProcesses
                System.Diagnostics.Debug.Print(wdprocess.MainWindowTitle)
                System.Diagnostics.Debug.Print(wdprocess.StartInfo.Arguments.Length.ToString())
            Next
    
            'If Word is running, attach oWord to that instance of Word
            If wdProcesses.Length > 0 Then
                oWord = System.Runtime.InteropServices.Marshal.GetActiveObject(appName)
    
                'if Word is not running, start new instance of Word
            Else
                oWord = New Word.Application
            End If
    
            If Not oWord Is Nothing Then
                oWord.Visible = True
                oWord.Activate()
    
            End If
    
            oWord.Visible = True
    
            'open new Word document based on the basic template
            oDoc = oWord.Documents.Add
    
            Return oWord
    
        End Function
    
        Public Shared Sub TypeText2(ByVal text As String, ByRef oWord As Word.Application)
            oWord.Selection.Text = text
        End Sub

    I would think that NewWordDoc2() returns oWord so I can pass in into TypeText2() ?



    • Edited by Wekkel Sunday, October 20, 2013 6:35 PM
    Sunday, October 20, 2013 6:30 PM
  • Hi Wekkel

    oWord = WordActions.NewWordDoc2(oWord)

    If you use a Function that returns the value you want to use, you do have to assign it to something in order to use the result...


    Cindy Meister, VSTO/Word MVP, my blog

    Monday, October 21, 2013 2:19 PM
    Moderator