I have a Word document with two embedded Excel files (added using Insert -> Object -> Create From File) which I wish to modify using Word VBA. I have got to the point where I am able to open the embedded files for editing (see code below), but am unable to get a handle on the Excel workbook using which I can make the modifications and save the embedded file. Does anyone have a solution for this? Thanks in advance.
Sub TestMacro()
Dim lNumShapes As Long
Dim lShapeCnt As Long
Dim xlApp As Object
Dim wrdActDoc As Document
Set wrdActDoc = ActiveDocument
For lShapeCnt = 1 To 1 'wrdActDoc.InlineShapes.Count
If wrdActDoc.InlineShapes(lShapeCnt).Type = wdInlineShapeEmbeddedOLEObject Then
If wrdActDoc.InlineShapes(lShapeCnt).OLEFormat.ProgID = "Excel.Sheet.8" Then
'This opens the embedded Excel workbook using Excel
wrdActDoc.InlineShapes(lShapeCnt).OLEFormat.Edit
End If
End If
Next lShapeCnt
End Sub
Yikes, don't do what you're suggesting in your comment. You'll probably end up with multiple instances of Excel (check Task Manager and see how many there are after executing your code).
Firstly, add a reference to the Excel object library (Project->References & choose Microsoft Excel Object Library). Now you can declare your objects as bona-fide Excel types and use early binding rather than declaring them as "Object" and using late binding. This isn't strictly necessary, but apart from anything else it means you get Intellisense when editing your code.
You're doing the right thing right up until you do .OleFormat.Edit. (I would personally use .OleFormat.Activate but since I've never tried using .Edit I couldn't say that it makes a difference).
Having done .Activate (or, presumably, .Edit), you can then access the OleFormat.Object member. Since the embedded Object is an Excel chart, the "Object" will be the Excel Workbook, so you can do this:
Dim oOleFormat as OleFormat
Set oOleFormat = ...
oOleFormat.Activate
Dim oWorkbook As Excel.Workbook
Set oWorkbook = oOleFormat.Object
' Do stuff with the workbook
oWorkbook.Charts(1).ChartArea.Font.Bold = True
Note that you do NOT need to close Excel, and indeed you cannot - Word "owns" the instance used for an edit-in-place, and will decide when to close it. This is actually something of a problem, since there's no obvious way to force the embedded object to be de-activated, so the chart would stay open after you execute the code above.
There is a hack-y way to get the chart to close, though. If you add tell Word to activate it as something else, it'll de-activate it first. So, if you tell it to activate it as something non-sensical, you'll achieve the right result because it'll de-activate it and then fail to re-activate it. So, add the following line:
oOleFormat.ActivateAs "This.Class.Does.Not.Exist"
Note that this will raise an error, so you'll need to temporarily disable error handling using On Error Resume Next. For that reason, I normally create a Deactivate method, to avoid disrupting the error handling in my main method. As in:
Private Sub DeactivateOleObject(ByRef oOleFormat as OleFormat)
On Error Resume Next
oOleFormat.ActivateAs "This.Class.Does.Not.Exist"
End Sub
Hope this helps.
Gary
I have a solution to my own problem. Any further comments will be appreciated -
Sub TestMacro()
Dim lNumShapes As Long
Dim lShapeCnt As Long
Dim xlApp As Object
Dim wrdActDoc As Document
Set wrdActDoc = ActiveDocument
For lShapeCnt = 1 To 1 'wrdActDoc.InlineShapes.Count
If wrdActDoc.InlineShapes(lShapeCnt).Type = wdInlineShapeEmbeddedOLEObject Then
If wrdActDoc.InlineShapes(lShapeCnt).OLEFormat.ProgID = "Excel.Sheet.8" Then
wrdActDoc.InlineShapes(lShapeCnt).OLEFormat.Edit
Set xlApp = GetObject(, "Excel.Application")
xlApp.Workbooks(1).Worksheets(1).Range("A1") = "This is A modified"
xlApp.Workbooks(1).Save
xlApp.Workbooks(1).Close
xlApp.Quit
End If
End If
Next lShapeCnt
End Sub
Have another hackey way to get the chart to close: Simply use the find function to find something in the document that is not there.
EG
With Selection.Find
.ClearFormatting
.Text = "wiffleball"
.Execute Forward:=True
End With
This will take you out of the embedded file, close the instance and back to the main document, you can just code from there.
Hope this helps, this problem was driving me crazy.
When you grad the xlApp, you don't grab a specific workbook. So if you refer to a number, you may not be on the embeded file. Better use Activeworkbook.
For me workbook(1) turns out to be my personnal hidden xl file containing my personnal macros.
I don't do the tests as I only have one shape in my .docx but I think the number "Excel.Sheet.8" is rather .12 for me.
Sub TestMacro()
Dim lNumShapes As Long
Dim lShapeCnt As Long
Dim xlApp As Object
Dim wrdActDoc As Document
Set wrdActDoc = ActiveDocument
For lShapeCnt = 1 To 1 'wrdActDoc.InlineShapes.Count
If wrdActDoc.InlineShapes(lShapeCnt).Type = wdInlineShapeEmbeddedOLEObject Then
If wrdActDoc.InlineShapes(lShapeCnt).OLEFormat.ProgID = "Excel.Sheet.8" Then
wrdActDoc.InlineShapes(lShapeCnt).OLEFormat.Edit
Set xlApp = GetObject(, "Excel.Application")
xlApp.ActiveWorkbook.Worksheets(1).Range("A1") = "This is A modified"
'xlApp.ActiveWorkbook.Save
'xlApp.ActiveWorkbook.Close
xlApp.Quit
End If
End If
Next lShapeCnt
End Sub
When I quit xlApp, the focus gets out of the embeded xl. No problem with that.
Related
I have a vba code that runs in Word.
However when I control Word from excel and run the code from excel it doesn't work.
Any idea why?
Sub SetTextBoxStyle()
Dim objTextBox As Shape
Dim objDoc As Document
Set objDoc = WordApp.ActiveDocument
For Each objTextBox In objDoc.Shapes
If objTextBox.TextFrame.HasText Then
objTextBox.TextFrame.TextRange.ParagraphFormat.LineSpacingRule = wdLineSpaceSingle
objTextBox.TextFrame.TextRange.ParagraphFormat.SpaceAfter = 0
End If
Next objTextBox
End Sub
Since you have an ActiveDocument it can be safely assumed that microsoft word is running and your document is the active one. You just need to add a GetObject
Sub SetTextBoxStyle()
' You need to change word types to object as here you are working with
' late binding. If you work with early binding (i.e. by adding a reference
' to word, you can use Word.Shape and Word.Document etc
Dim objTextBox As Object ' Shape
Dim objDoc As Object ' Document
Set objDoc = GetObject(,"Word.Application").ActiveDocument
For Each objTextBox In objDoc.Shapes
If objTextBox.TextFrame.HasText Then
objTextBox.TextFrame.TextRange.ParagraphFormat.LineSpacingRule = 0
'wdLineSpaceSingle is not defined in excel vba by default
'either use its value (0), define it somewhere or add a reference to Word object model
objTextBox.TextFrame.TextRange.ParagraphFormat.SpaceAfter = 0
End If
Next objTextBox
End Sub
A lot of things can go wrong
Word might not be running and therefore, GetObject raises an error.
Your document might not be the active one.
There might be several instances of Word running and your document is not in that particular instance.
... possibly many more
To make your code robust, you need to read more about how to work with different office applications from one of them. The link provided in the comments by #braX is a good start.
I am working on a PowerPoint including charts that links to data in Excel. I would like to remove this link through a macro in VBA.
I use the following macro which executes without errors, but when I look through the PowerPoint I can still follow the links back to my Excel file. Is anyone able to see the problem?
Sub BreakAllLinks()
Dim oSld As PowerPoint.Slide
Dim oSh As PowerPoint.Shape
Dim Yes As Integer
Dim PowerPointApp As PowerPoint.Application
Set PowerPointApp = GetObject(class:="PowerPoint.Application")
'// Check if more then single powerpoint open
If PowerPointApp.Presentations.Count > 1 Then
MsgBox "Please close all other PowerPoints"
Exit Sub
End If
Yes = MsgBox("Are you sure you want to break all links in your active PowerPoint presentation?", vbYesNo + vbQuestion, "Break ALL links")
If Yes = vbYes Then
For Each oSld In PowerPointApp.ActivePresentation.Slides
For Each oSh In oSld.Shapes
If oSh.Type = msoLinkedOLEObject Then 'SOLUTION EDIT: msoChart
oSh.LinkFormat.BreakLink
End If
Next ' Shape
Next ' Slide
End If
End Sub
Thanks in advance.
Let me start by saying I'm not an expert in OLE linked charts. Someone else may explain this much better than myself. So disclaimer done ... oSh.Type = msoLinkedOLEObject doesn't correctly identify all linked charts. A linked chart maybe oSh.Type = msoChart for example. I've found that a better way of identifying a chart that is linked is by testing for the existence of the property Shape.LinkedFormat.AutoUpdate ... the property AutoUpdate only exists for linked charts ... and does not exist for non-linked charts. Referring to it for a non-linked chart will generate a runtime error. However if we're going to use its existance, then check whether a property exists in VBA (a pretty clunky process). For example:
Private Function IsLinked(myShape As Shape)
Dim AutoUpdate As Variant
On Error GoTo Err_Handler
IsLinked = False
AutoUpdate = myShape.LinkFormat.AutoUpdate
IsLinked = True
Err_Handler:
End Function
So you could test if oSh is linked using the above function. The next challenge is that BreakLink doesn't (always) work. The best method is to simply set AutoUpdate to manual update ... like:
oSh.LinkFormat.AutoUpdate = ppUpdateOptionManual
Alternatively, you could always copy the charts in without the links in the first place.
I have adapted code that checks the subject line of new Outlook emails for a keyword, opens a workbook and pastes certain information into this workbook:
Option Explicit
Private WithEvents Items As Outlook.Items
Private Sub Application_Startup()
Dim olApp As Outlook.Application
Dim objNS As Outlook.NameSpace
Set olApp = Outlook.Application
Set objNS = olApp.GetNamespace("MAPI")
' default local Inbox
Set Items = objNS.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub Items_ItemAdd(ByVal item As Object)
On Error GoTo ErrorHandler
Dim Msg As Outlook.MailItem
If TypeName(item) = "MailItem" Then
Set Msg = item
If InStr(Msg.Subject, "Re:") > 0 Then
Exit Sub
ElseIf InStr(Msg.Subject, "MDI Board") > 0 Then '// Keyword goes here
'// Declare all variables needed for excel functionality and open appropriate document
Dim oXL As Object
Dim oWS As Object
Dim lngRow As Long
Set oXL = CreateObject("Excel.Application")
oXL.Workbooks.Open FileName:="T:\Capstone Proj\TimeStampsOnly.xlsx", AddTOMRU:=False, UpdateLinks:=False
'// Change sheet name to suit
Set oWS = oXL.Sheets("TimeStamps")
lngRow = oWS.Range("A" & oXL.Rows.Count).End(-4162).Offset(1).Row '// -4162 = xlUp. not available late bound
With oWS
.cells(lngRow, 1).Value = Msg.SenderName
.cells(lngRow, 2).Value = Msg.ReceivedTime
.cells(lngRow, 3).Value = Msg.ReceivedByName
.cells(lngRow, 4).Value = Msg.Subject
.cells(lngRow, 5).Value = Msg.Body
'// And others as needed - you will have Intellisense
End With
With oXL
.activeworkbook.Save
.activeworkbook.Close SaveChanges:=2 '// 2 = xlDoNotSaveChanges but not availabe late bound
.Application.Quit
End With
Set oXL = Nothing
Set oWS = Nothing
End If
Else
Exit Sub
End If
ExitPoint:
Exit Sub
ErrorHandler:
MsgBox Err.Number & " - " & Err.Description
Resume ExitPoint
'// Debug only
Resume
End Sub
I was having issues with being able to access the workbook after the Outlook VBA code ran. It would give multiple errors such as 'the workbook is already open' even though I had no instance of Excel running on my machine or 'this file is read-only' etc.
I tried to circumvent this issue by using another workbook with an update macro that would update a dashboard using the information in the problematic workbook however I am getting a 'subscript out of range' error when I try to set a variable to the workbook with the Outlook data.
Dim wkb As Excel.Workbook
Dim wks As Excel.Worksheet
Set wkb = Excel.Workbooks("T:\Capstone Proj\TimeStampsOnly.xlsx")
Set wks = wkb.Worksheets("Timestamps")
Wagner Braga!
I have had a similar problem in the past. In my case, I was not looking for subjects containing certain characters but rather subjects equal to a string. Either way, that is irrelevant to your issue.
I found that, like yours, my code errored when trying to put info from the email into Excel. I did read the comments on your question and know that you don't want to use unneccessary computing power. My method is not the most efficient way to accomplish what you want to do, but it was the only way I could do it.
First of all, I did not edit the Excel workbook from the Outlook VBA. I tried to do it, but this is where my code errored. Instead, I set the email object as a variable's value (to make it easier to reference). Then I read the information from the email I wanted into an array by using the Split(...) function. The code created a text file and wrote the data to it so that it would be accessible by Excel. Before writing the data from the email, I also wrote the text "!NEWDATA!" on the first line. You could use any string you want, as long as there is a unique identifier at the top so that Excel recognizes that it should get data from the file. I then opened the workbook, just like I would open any other file using VBA.
Now, the Excel workboook requires some VBA code as well for my method to work. In the Workbook_Open() VBA sub in the workbook code, Excel should read the first line or first x number of characters. You can use either method, but this is should point to the part of the file that has your "!NEWDATA!" or other string. If this string is the one you wrote from Outlook, continue reading the file. If it's not, Exit Sub. From here you can have Excel read the rest of the file (which you separated by a delimeter of your choice via Outlook VBA) and put the data into the corresponding cells. Then change the "!NEWDATA!" and the rest of the file so that if you start Excel manually (and you don't want to import any data) the Workbook_Open() sub will stop and not error. You can change it to anything like a blank file, "No new data", or any other string you like. After this, use VBA to save the workbook and close it.
As you probably know, you could set the Excel window's Visible property to False if you don't want the user seeing the workbook.
If you have any questions or comments, let me know. I'll be happy to answer any questions you may have.
I have a Word 2010 document embedded inside an Excel sheet. I want to create content control boxes inside the word doc which can be populated programatically. For this I need to set tags for the content control.
I read on the MSDN website and some other sources that it is simple enough - you just have to enable Design Mode and then right click the content control box and click Properties. However, the properties option is grayed out and disabled even though I'm in Design Mode.
When I do this on a standalone Word document (not an embedded one), it works just fine. So that's a workaround I'm using right now. However it's really inconvenient to have to create the boxes in the standalone Word doc and copy them over into the one embedded in Excel.
Is it possible to edit properties of content control box in a Word doc embedded inside an Excel sheet?
It can be done programmatically, something like the code snippet that follows.
An embedded Word document is an Excel OLEObject; such an object can be named, for example: ws.Objects(1).Name = "WordDoc" - this is saved in the workbook and will remain the same, even if other objects are added later. This does not name the document, only the object on the surface of the Worksheet.
If the embedded document has never been accessed during a session, it first needs to be activated. As doing so causes the screen to jump and the selection to change, IF conditions are included to test that and first activate the OLEObject, as well as re-selecting the cell that was active previously.
Working with the .Tag property is shown in the For Each...Next loop. You can see the result if you go into Design Mode.
Note that the macro will not work if you're in Design Mode or if the selection is in the embedded Word document.
Sub Test()
Dim ws As Excel.Worksheet
Dim currCel As Excel.Range
Dim oDoc As OLEObject
Set currCel = Application.Selection
Set ws = ActiveWorkbook.Worksheets("Sheet1")
'Debug.Print ws.OLEObjects.Count
Set oDoc = ws.OLEObjects("WordDoc")
' Debug.Print oDoc.OLEType 'Type 1 = Embedded
WorkWithWordDoc oDoc, currCel
End Sub
Sub WorkWithWordDoc(oDoc As OLEObject, selRange As Excel.Range)
Dim doc As Word.Document
Dim wasActivated As Boolean
Dim cc As Word.ContentControl
'On first opening the Workbook
'the OLE interface of the OLEObject
'isn't accessible, so activate it
wasActivated = True
On Error Resume Next
Set doc = oDoc.Object
If Err.Number = 1004 Then
Excel.Application.ScreenUpdating = False
oDoc.Activate
wasActivated = False
Set doc = oDoc.Object
Excel.Application.ScreenUpdating = True
End If
On Error GoTo 0
For Each cc In doc.ContentControls
cc.Tag = "CC in embedded Doc"
Next
'Clean up
If Not wasActivated Then
'Deactivate the document
selRange.Select
End If
Set doc = Nothing
End Sub
I'm new to VBA, so I'm struggling with this for a couple of days now.
I have a combobox in Word with contacts, I also have an excel file(contacts.xls) with one column (names of the all the contacts). When I open the Word-document the Macro has to fill in the combobox with all the names from the excel file.
Is it possible to send me a working example of this for word 2007? Seems quite simple, but just can't get this to work...
Thanks in advance!
If you intend on reading from an Excel file in Word you are going to have to open it. It will use code like this:
Dim oExcel As Object
Dim oBook As Object
Dim oSheet As Object
'Start a new workbook in Excel
Set oExcel = CreateObject("Excel.Application")
Set oBook = oExcel.Workbooks.Open("FileName.xlsx")
You will also probably want to go to Tools->References in the VB project and find whatever Excel library is on your machine (and check it of course). There are ways around this if needed, but it is easier.
You then can read the values from the workbook:
oBook.Worksheets(1).cells(1,1)
I'm not totally sure what the syntax to get it into the combo box is. Look up "combobox object members" in the vbe in word. There will be a list property, or something like that.
Sorry, I'm on a linux machine right now, so I can't debug exact code for you.
I have more full code for you now:
Option Explicit
Sub TestDropDownFromExcel()
Dim counter As Long
Dim xlApp As Excel.Application
Dim xlBook As Workbook
Dim oCC As ContentControl
Set oCC = ActiveDocument.ContentControls(1)
Set xlApp = CreateObject("Excel.Application")
Set xlBook = xlApp.Workbooks.Open("C:\Path\To\MyFile.xlsx")
If xlBook Is Nothing Then
Exit Sub
End If
oCC.DropdownListEntries.Clear
For counter = 1 To 10
oCC.DropdownListEntries.Add Text:=xlBook.Worksheets(1).Cells(counter, 1), Value:=CStr(counter)
Next counter
xlApp.Visible = True
Set xlBook = Nothing
Set xlApp = Nothing
End Sub
Be aware that there is very little error checking going on here. Also, rather than the call to .Visible at the end, you can simply call .Close if you do not want the user to see it (for the final project, this is probably preferable. The deferencing (= Nothing) is good practice to have, but VBA cleans up automatically at the end of execution. Obviously I am assuming tyou want the first dropdown (ContentCOntrols(1)) but this may not be true.