Expose Excel InlineShapes to edit - excel

I am trying to automatize filling in a third party form from an Excel Workbook VBA module. They sadly have gone with a Word document with an in-line Excel workbook embedded, which contains cells as named ranges that I want to edit.
How do I assign the InlineShapes object to an Excel.workbook object so that I can expose it to Excel methods and properties?
This is what I have tried so far:
Sub test()
Dim wdApp As Word.Application
Set wdApp = CreateObject("word.application")
wdApp.Visible = true ' for testing
Dim wdAppendixB As Word.Document
Set wdAppendixB = wdApp.Documents.Add(ThisWorkbook.Path & "\Templates\form_template.dotx")
Dim wbAppB As Excel.Workbook
wdAppendixB.InlineShapes.Item(1).OLEFormat.Edit
Set wbAppB = wdAppendixB.InlineShapes.Item(1).OLEFormat.Object
wbAppB.Sheets("Sheet1").Range("date1").Value = "2019-06-02"
Exit Sub
As soon as the script opens the OLE object for editing, the script stops with no errors. Closing the OLE object for editing does not resume the script.
If I omit editing the object and just set the workbook object to the OLEFormat.Object it errors out with Run-time error '430' "Class does not support Automation or does not support expected interface".
Any suggestion is appreciated.

Use Activate, instead of Edit (or DoVerb with an appropriate wdOleVerb constant).
Note that this will leave the object in an activated state. There's no elegant way to emulate the user clicking outside the object to de-select it. The workarounds are to either open the object in its own window (instead of in-place) and close that file window OR to try to activate the object as a specific, non-existant class. Since this will trigger an error, this has to be wrapped in `On Error Resume Next' and 'On Error GoTo 0'.
Sub test()
Dim wdApp As Word.Application
Set wdApp = CreateObject("word.application")
wdApp.Visible = True ' for testing
Dim wdAppendixB As Word.Document
Set wdAppendixB = wdApp.Documents.Add(ThisWorkbook.Path & "\Templates\form_template.dotx")
Dim wbAppB As Excel.Workbook
Dim of As Word.OLEFormat
Set of = wdAppendixB.InlineShapes.Item(1).OLEFormat
of.Activate '.Edit
Set wbAppB = of.Object
wbAppB.Sheets("Sheet1").Range("B1").Value = "2019-06-02"
On Error Resume Next
of.ActivateAs "This.Class.NotExist"
On Error GoTo 0
End Sub

Related

VBA, How to open new excel instance

When I have don't have the excel APP open, the following error is thrown :
ActiveX Component can't create object
Steps to reproduce issue :
1 Open Outlook, ALT + F11 And Insert the following sub :
Sub Test()
Dim myXL As New Excel.Application
Set myXL = GetObject(, "Excel.Application")
Set wb = myXL.Workbooks.Open("MyPath\MyXL.xlsx")
End Sub
Close ALL your excel files
Run the sub Test from outlook.
The Error will be thrown on :
Set myXL = GetObject(, "Excel.Application")
How can I avoid this error ?
A better option should be the next way, I think:
Dim objexcel As Object
On Error Resume Next 'firstly, try catching the existing open session, if any:
Set objexcel = GetObject(, "Excel.Application")
If err.Number <> 0 Then 'if no any existing session, create a new one:
err.Clear: Set objexcel = CreateObject("Excel.Application")
End If
On Error GoTo 0
Having a reference to 'Microsoft Excel ... Object library` you can declare
Dim objexcel As Excel.Application
and benefit of the intellisense suggestions...
It is also possible to find an Excel open session if you know the full name of a specific workbook open in it:
Set objExcel = GetObject(ThisWorkbook.fullName).Application
Debug.Print objExcel.hwnd
Or even for a new workbook, open by a third party application, in a new session, as "Book1":
Set objExcel = GetObject("Book1").Application
Debug.Print objExcel.hwnd
If the respective application drops new workbooks (and opens them in the same session), naming them as "Book2", "Book3" and so on, a loop building the workbook name bay concatenation of "Book" root with the incremented variable can be used to get it.

Run Excel VBA in Access

I have been researching this a great deal and I am not finding any leads to how this would work.
I have written code in Excel that I want to run in MS Access. I have pasted the code I wish to run in Access.
All the examples or information I have found is from 2003 Access. I am using 2016 Access.
The Excel code
Public Function getworkbook()
' Get workbook...
Dim ws As Worksheet
Dim Filter As String
Dim targetWorkbook As Workbook, wb As Workbook
Dim Ret As Variant
Application.DisplayAlerts = False
Sheets("DATA").Delete
' Sheets("DATA").Cells.Clear
Set targetWorkbook = Application.ActiveWorkbook
' get the customer workbook
Filter = "Text files (*.xlsx;*.xlsb),*.xlsx;*.xlsb"
Caption = "Please Select an input file "
Ret = Application.GetOpenFilename(Filter, , Caption)
If Ret = False Then Exit Function
Set wb = Workbooks.Open(Ret)
wb.Sheets(1).Move After:=targetWorkbook.Sheets(targetWorkbook.Sheets.Count)
' ActiveSheet.Paste = "DATA"
ActiveSheet.Name = "DATA"
ThisWorkbook.RefreshAll
' Application.Quit
Application.DisplayAlerts = True
End Function
Code I found and tried to use in Access.
Public Function runExcelMacro(wkbookPath)
Dim XL As Object
Set XL = CreateObject("Excel.Application")
With XL
.Visible = False
.displayalerts = False
.Workbooks.Open wkbookPath
'Write your Excel formatting, the line below is an example
.Range("C2").value = "=1+2"
.ActiveWorkbook.Close (True)
.Quit
End With
Set XL = Nothing
End Function
There are few concepts you need to deal with first.
Library references and scope
Your original code was written in Excel. Therefore, in that VBA project, it has Excel object referenced. In your Access VBA project, that is not referenced. You can compare this by looking at Tools -> References.
That brings us to the concept of "early-binding" and "late-binding". When you type in things like Range., you get VBA's intellisense to tell you what you can do with a Range or whatever. But in Access, you don't have Excel object library referenced by default. Therefore, Range. will not yield intellisense and you can't run the code because Access does not have Range in its object model and your VBA project mostly likely don't have a reference that has it.
Therefore, your code need to be adjusted to run late-bound if you do not want to add reference to Excel object model, and you most likely do want that anyway.
Unqualified Reference
Your original Excel code contains unqualified references to various global objects that are available in Excel's object model.
Application.DisplayAlerts = False
...
Sheets("DATA").Delete
...
Set wb = Workbooks.Open(Ret)
...
Those won't necessarily work consistently in VBA projects hosted by other hosts other than Excel and most certainly won't work in late-bound code. Furthermore, if you elect to add a reference to Excel's object model, you still end up leaking Excel instance which can cause ghost instances because unqualified references to the global objects will implicitly create an Excel instance that you can't interact and that can also cause other runtime error down the path. To make your code more late-bindable, you need something like:
Set ExcelApp = CreateObject("Excel.Application")
ExcelApp.DisplayAlerts = False
...
Set MyBook = ExcelApp.Workbooks("Whatever")
MyBook.Sheets("DATA").Delete
...
Set wb = ExcelApp.Workbooks.Open(Ret)
...
Note how all global objects that you could have accessed in a Excel-hosted context now have to be a variable on its own. Furthermore, you won't have access to ThisWorkbook or even Sheet1 in other VBA projects because Excel is no longer the host. You must adjust accordingly.
Switching between early-binding & late-binding
Early-bound code makes it much easier for you to develop since you get full intelisense and object browser helping you write the code. However, when referencing other object models, you might want to distribute your VBA code using late-binding to avoid versioning problems and broken references. But you can have best from both worlds:
#Const EarlyBind = 1
#If EarlyBind Then
Dim ExcelApp As Excel.Application
#Else
Dim ExcelApp As Object
#End If
Set ExcelApp = CreateObject("Excel.Application")
This illustrates the use of conditional compilation argument to allow you to have ExcelApp variable that can be either Excel.Application (aka early-bound) vs. Object (aka late-bound). To change, you simply change the Const LateBind line between 0 or 1.
First, to clear up terminology:
VBA is a separate language and not tied to any MS Office application. Under Tools\References, you will see Visual Basic for Applications is usually the first checked object. What differs between running VBA inside Excel, Access, Word, Outlook, etc. is the default access to their object library. Specifically:
Only Excel sees Workbook, Worksheet, etc. without defining its source
Only Access sees Forms, Reports, etc. without defining its source
Only Word sees Documents, Paragraphs, etc. without defining its source
When running a foreign object library inside an application, such as MS Access accessing Excel objects, you must define and initialize the foreign objects via reference either with early or late binding:
' EARLY BINDING, REQUIRES EXCEL OFFICE LIBRARY UNDER REFERENCES
Dim xlApp As Excel.Application
Dim wb As Excel.Workbook
Dim ws As Excel.Worksheet
Set xlApp = New Excel.Application
Set wb = xlApp.Workbooks.Open(...)
Set ws = wb.Worksheets(1)
' LATE BINDING, DOES NOT REQUIRE EXCEL OFFICE LIBRARY UNDER REFERENCES
Dim xlApp As Object, wb As Object, ws As Object
Set xlApp = CreateObject("Excel.Application")
Set wb = xlApp.Workbooks.Open(...)
Set ws = wb.Worksheets(1)
With that said, simply keep original code nearly intact but change definitions and initializations. Notably, all Application calls now point to Excel.Application object and not to be confused with Access' application. Plus, best practices of avoiding .Select/ .Activate/ Selection/ ActiveCell/ ActiveSheet/ ActiveWorkbook.
Public Function getworkbook()
' Get workbook...
Dim xlApp As Object, targetWorkbook As Object, wb As Object, ws As Object
Dim Filter As String, Caption As String
Dim Ret As Variant
Set xlApp = CreateObject("Excel.Application")
Set targetWorkbook = xlApp.Workbooks.Open("C:\Path\To\Workbook.xlsx")
xlApp.DisplayAlerts = False
targetWorkbook.Sheets("DATA").Delete
' get the customer workbook
Filter = "Text files (*.xlsx;*.xlsb),*.xlsx;*.xlsb"
Caption = "Please Select an input file "
Ret = xlApp.GetOpenFilename(Filter, , Caption)
If Ret = False Then Exit Function
Set wb = xlApp.Workbooks.Open(Ret)
wb.Sheets(1).Move After:=targetWorkbook.Sheets(targetWorkbook.Sheets.Count)
Set ws = targetWorkbook.Worksheets(targetWorkbook.Sheets.Count)
ws.Name = "DATA"
targetWorkbook.RefreshAll
xlApp.DisplayAlerts = True
xlApp.Visible = True ' LAUNCH EXCEL APP TO SCREEN
' xlApp.Quit
' RELEASE RESOURCEES
Set ws = Nothing: Set wb = Nothing: Set targetWorkbook = Nothing: Set xlApp = Nothing
End Function
By the way, above can be run in any MS Office application as no object of the parent application (here being MS Access) is used!

Create Copy Of Excel Worksheet using Word VBA

With an already-opened workbook, I want to create a copy of a worksheet ("Template") and re-name it with the value I have in an array. When I debug the code the copy is created when I execute the line but I still get an error 424: object required.
I've tried On Error Resume Next but since I already used On Error GoTo in my sub it isn't read.
Dim oXL As Excel.Application 'Requires loading "Microsoft Excel 16.0 Object Library" from Tools -> References
Dim oWB As Excel.Workbook
Set oXL = New Excel.Application
Set oWB = oXL.Workbooks.Open(FileName:=WorkbookToWorkOn) 'Opens Excel
oXL.Visible = True 'Shows Excel window while running code. Set to false after finished editing.
Dim ws As Worksheet
For i = LBound(seller_names) To UBound(seller_names)
'On Error Resume Next 'Doesn't work
Set ws = oXL.ActiveWorkbook.Sheets("Template").Copy(After:= _
oXL.ActiveWorkbook.Sheets(oXL.ActiveWorkbook.Sheets.Count))
ws.name = seller_names(i)
Next i
Sheets.Copy doesn't return a reference to the copied sheet, so you need to first copy the sheet, then get a reference to it:
With oXL.ActiveWorkbook
.Sheets("Template").Copy After:= .Sheets(.Sheets.Count)
Set ws = .Sheets(.Sheets.Count)) '<< get the copy
ws.name = seller_names(i)
End With
On Error Resume Next should always work - but is not always a good solution - unless you have "Break on all errors" enabled in your VBA Options.

How to open Excel if not already open

I'm trying to find a way to open Excel using Outlook VBA, but only if it's not already open. I managed to find some code on the internet that opens Excel, does changes and then closes it, but it doesn't behave nicely if the Excel workbook is already open(it does apply the changes, but it no longer closes the Excel workbook, and it simply leaves it with a grey interior; also, sometimes it doesn't even show in the explorer anymore and I have to close it from the task manager).
I would also greatly appreciate if someone could explain what most of the code does.
Public xlApp As Object
Public xlWB As Object
Public xlSheet As Object
Sub ExportToExcel()
Dim enviro As String
Dim strPath As String
'Get Excel set up
enviro = CStr(Environ("USERPROFILE"))
'the path of the workbook
strPath = enviro & "\Documents\test2.xlsx"
On Error Resume Next
Set xlApp = GetObject(, "Excel.Application")
If Err <> 0 Then
Application.StatusBar = "Please wait while Excel source is opened ... "
Set xlApp = CreateObject("Excel.Application")
bXStarted = True
End If
On Error GoTo 0
'Open the workbook to input the data
Set xlWB = xlApp.Workbooks.Open(strPath)
Set xlSheet = xlWB.Sheets("Sheet1")
' Process the message record
On Error Resume Next
xlWB.Close 1
If bXStarted Then
xlApp.Quit
End If
End Sub
I know what the xlWb and xlSheet objects do, and how they're declared, and I also understand what the environ function and strPath string do, but I don't undestand why we need the bXStarted boolean, what Set xlApp = GetObject does, why the Application.StatusBar message doesn't get displayed, the difference between GetObject and CreateObjectand why so many error tests are needed.
Thanks in advance.
The difference between get object and create object is in the title, one will get open excel.application, if there is an error err<>0 then it creates an excel.application. I think you'll be getting a saveas message, as the file may not be saving, but open, and you're instructing it to save, the error resume next is skipping it . Try saving before just a .close If you remove the on error resume next the error wont be skipped and will be shown.
Sub explaination()
Dim blnDidICreateExcel As Boolean ' Please read MSDN on boolean
Dim objToHoldExcelCreatedOrNot As Object ' Please read MSDN on objects create/get
' Does the user have Excel open, if i try to get it, then there will be an error logically if not
Set objToHoldExcelCreatedOrNot = GetObject(, "Excel.Application")
' Was there an error
If Err <> 0 Then
' There was, so i need to create one
Set objToHoldExcelCreatedOrNot = CreateObject("Excel.Application")
blnDidICreateExcel = True ' Yes, i created it
End If
' Do the neccessary
' CLose the workbook
' Did i create this Excel, if so tidy up
If blnDidICreateExcel Then objToHoldExcelCreatedOrNot.Quit
End Sub

How to refer to workbooks opened before from within word vba?

i've got a subtle problem using VBA on MS Word. I try to refer to some workbooks that were opened before word was started up.
From within a short test-macro in word a simple
MsgBox Workbooks.Count
delievers a value of 0 although 3 (empty) workbooks are opened. When the 3 Workbooks are opened after Word was started, i get the correct value of 3.
How to fix this ?
jm2p Zeph
it's because you must get the running instance of Excel instead of creating a new one
the following code set an Excel application object trying to get any running instance first and then, should no excel session be already running, open a new one:
Option Explicit
Sub LateBindingExcel()
Dim xlApp As Object
Set xlApp = GetExcelObject
MsgBox xlApp.Workbooks.count
End Sub
Function GetExcelObject() As Object
Dim excelApp As Object
On Error Resume Next
Set excelApp = GetObject(, "Excel.Application") '<--| try getting a running Excel application
On Error GoTo 0
If excelApp Is Nothing Then Set excelApp = CreateObject("Excel.Application") '<--| if no running instance of Excel has been found then open a new one
Set GetExcelObject = excelApp '<--| return the set Excel application
End Function
Check this out:
'Option Explicit
'Option Explicit
Sub check()
Set objExcel = GetObject(, "Excel.Application")
Set wbs = objExcel.Workbooks
Debug.Print wbs.Count
Set objExcel = Nothing
Set wbs = Nothing
End Sub

Resources