Access Vba - format condition not saved in Excel file (unreadable content) - excel

I'm trying to create an Excel file from an Access database. I need to create some conditional formatting in the excel file. I save the file, but when I re-open the file a warning message Unreadable content is shown and, even repairing, the conditional formatting is lost.
I tried many options and read a lot of posts but none solving this issue.
This is involved part of the code:
'==========================================================================
Dim xl As Excel.Application
Dim wk3 As Workbook
Dim ws3 As Worksheet
Set xl = New Excel.Application ' Create a excel instance
Set wk3 = xl.Workbooks.Add ' Create a new workbook
Set ws3 = wk3.Worksheets(1) ' Add a worksheet to the new wrkbk
...
'
' Add conditional formatting to a range
'
ws3.Range("A1:A10").FormatConditions.Add xlCellValue, xlEqual, "=TRUE"
ws3.Range("A1:A10").FormatConditions(1).Interior.Color = vbGreen
'
' Save and close file
'
wk3.SaveAs "D:\_TOOLS\Result.xlsx" ' Save as Excel 2010 file
wk3.Close False ' Close file
'
' Close excel and free memory
'
Set wk3 = Nothing
Set ws3 = Nothing
xl.Quit
Set xl = Nothing
'==========================================================================
My office version is 2010 (ver. 14.0.6112.5000 32bit). I tried many saving formats but some of them hang Access (like xlExcel12). Some of the others give an incompatibility error with extension during saving (like xlExcel8).
Any suggestions?

You might want to try xlWorkbookNormal (value -4143) as the save format which is the Excel default (explicitly).
However, I have seen similar issues pop up when you are explicitly referencing Excel object in your code (i.e. Excel Library 14.0 is selected in the "References" of the VBA project).
The solution which has worked for me is to remove the "Excel Library" Reference and late-bound the object instead:
Dim xl As Object, wk3 As Object, ws3 As Object
' Create Excel object based on version installed on the machine (late bound).
Set xl = CreateObject("Excel.Application")
' Rest of your code can remain unchanged...
wk3.SaveAs "D:\_TOOLS\Result.xlsx", -4143 ' -4143 = xlWorkbookNormal
The downside to this during development is you will lose intellisence. So what you can do is keep the early-bound objects for development purposes, but comment them out for deployment (and vice-versa):
' Keep these for development.
' These require the Excel Library to be explicitly referenced.
'Dim xl As Excel.Application
'Dim wk3 As Workbook
'Dim ws3 As Worksheet
'Set xl = New Excel.Application ' Create a excel instance
' Use for deployment.
' Remove the Excel Library reference to use this section.
Dim xl As Object, wk3 As Workbook, ws3 As Worksheet
' Create Excel object based on version installed on the machine (late bound).
Set xl = CreateObject("Excel.Application")
' Rest of your code can remain unchanged...
wk3.SaveAs "D:\_TOOLS\Result.xlsx", -4143 ' -4143 = xlWorkbookNormal

Related

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!

Activating opened Excel workbook from Outlook

I have a macro in my Outlook that whenever I receive an e-mail with a certain subject, it automatically opens an Excel workbook and pastes a portion of the e-mail subject in a specific cell in one of the worksheets. It works perfectly. Now I need to do this exact same process but pasting this information in an already opened workbook, instead of opening a closed file.
I've tried different solutions from my limited Excel VBA knowledge (ActiveWorkbook, worbooks(filename).activate, etc.) but none of that worked and I have not found anything similar online, as most macros are written as being run from an Excel file and not Outlook.
This is part of our current code, that opens the file and pastes the e-mail subject (which is the "ticker" value) in a specific cell on the "Lista Empresas" worksheet. What I need is a code that does the same, but in an workbook that is already opened (let's call it "test1").
Dim exapp As Excel.Application
Dim ExWbk As Workbook
Dim ExWbk2 As Workbook
Set exapp = New Excel.Application
Set ExWbk2 = exapp.Workbooks.Open("\\XXX\ListaEmpresas_ajustado.xlsm", UpdateLinks:=0)
exapp.Visible = True
ExWbk2.Sheets("Lista Empresas").Range("P2").Value = ticker
ExWbk2.Sheets("Lista Empresas").Range("P3").Calculate
There are a few scenarios to handle here. First, is Excel running? If no, then do what you are doing already. If yes - is the correct workbook open? If yes - return it, otherwise open it.
Dim ExWbk2 As Workbook
Dim wb As Workbook
Dim exapp As Excel.Application
On Error Resume Next
Set exapp = GetObject(, "Excel.Application")
If Err.Number <> 0 Then
Set exapp = Nothing
End If
On Error GoTo 0
If exapp Is Nothing Then
' Excel is not running
Set exapp = New Excel.Application
Set ExWbk2 = exapp.Workbooks.Open("\\XXX\ListaEmpresas_ajustado.xlsm", UpdateLinks:=0)
ExWbk2.Visible = True
Else
' Excel is running, but is the right book open?
For Each wb In exapp.Workbooks
Debug.Print wb.Name ' <-- This will help you find what to look for
If wb.Name = "ListaEmpresas_ajustado" Then
' Yes, it is!
Set ExWbk2 = wb
End If
Next
If ExWbk2 Is Nothing Then
' No, it wasn't
Set ExWbk2 = exapp.Workbooks.Open("\\XXX\ListaEmpresas_ajustado.xlsm", UpdateLinks:=0)
End If
End If
The trick to find out if Excel is running is GetObject. It will fail if it can't find it.
The for loop is there to allow for finding the correct workbook, based on the name. Adjust as needed.
The following code gets the object if you know the name of the sheet currently active in Excel instance. I guess this could be got from the application title using the first bit of code.
Dim exapp As Excel.Application
Dim ExWbk As Workbook
Dim ExWbk2 As Workbook
Set exapp = GetObject("ListaEmpresas_ajustado.xlsm").Application
exapp.Visible = True
ExWbk2.Sheets("Lista Empresas").Range("P2").Value = ticker
ExWbk2.Sheets("Lista Empresas").Range("P3").Calculate

Read Excel file sheet names

I have an export process that transfers data from my Access tables to an Excel File. A couple times I have had issues where the process didn't generate one or more of the sheets (1 sheet = 1 table) in Excel. So when the transfers are complete I want Access to check if all the sheets are located in the Excel file. I have most of the Check process worked out all I need now is a way to "read" the sheet names from the Excel File in to a table. How can I read the Sheet name (not the data)?
From Access you can automate Excel, open the workbook file, and read the sheet names from the Worksheets collection.
This sample uses late binding. If you prefer early binding, add a reference for Microsoft Excel [version] Object Library and enable the "early" lines instead of the "late" lines.
Give the procedure the full path to your workbook file as its pWorkBook parameter.
Public Sub List_worksheets(ByVal pWorkBook As String)
'Dim objExc As Excel.Application ' early
'Dim objWbk As Excel.Workbook ' early
'Dim objWsh As Excel.Worksheet ' early
Dim objExc As Object ' late
Dim objWbk As Object ' late
Dim objWsh As Object ' late
'Set objExc = New Excel.Application ' early
Set objExc = CreateObject("Excel.Application") ' late
Set objWbk = objExc.Workbooks.Open(pWorkBook)
For Each objWsh In objWbk.Worksheets
Debug.Print objWsh.Name
Next
Set objWsh = Nothing
objWbk.Close
Set objWbk = Nothing
objExc.Quit
Set objExc = Nothing
End Sub
In Access 2007, You can use OpenDatabase method to do this:
Private Sub Command1_Click()
Set db = OpenDatabase("c:/123.xls", True, False, "Excel 5.0")
For Each tbl In db.TableDefs
MsgBox tbl.Name
Next
End Sub

Create a blank workbook which is not saved

I am trying to open a workbook in separate instance. Currently, this workbook is saved on the desktop. I would want to open a new workbook which is not saved or located anywhere on my system. Below is the current code which I have. Please advise.
Sub New_Excel()
'Create a Microsoft Excel instance via code
'using late binding. (No references required)
Dim xlApp As Object
Dim wbExcel As Object
'Create a new instance of Excel
Set xlApp = CreateObject("Excel.Application")
'Open workbook, or you may place here the
'complete name and path of the file you want
'to open upon the creation of the new instance
Set wbExcel = xlApp.Workbooks.Open("C:\Users\PRASHPRA\Desktop\Book.xls")
'Set the instance of Excel visible. (It's been hiding until now)
xlApp.Visible = True
'Release the workbook and application objects to free up memory
Set wbExcel = Nothing
Set xlApp = Nothing
End Sub
If you want to create a new blank workbook, stop trying to open an existing one. Just change the line
Set wbExcel = xlApp.Workbooks.Open("C:\Users\PRASHPRA\Desktop\Book.xls")
to
'Add a new, empty workbook
Set wbExcel = xlApp.Workbooks.Add
For more information, see Creating a New Workbook (the link is for Excel 2003, because it's the first one I found via Google, but it still applies, and if you want a more recent link you can probably find it the same way I did).

Excel VBA Run-time error '424': Object Required when trying to copy TextBox

I'm attempting to copy the contents of a text box from one workbook to another. I have no problem copying cell values from the first workbook to the 2nd, but I get an object required error when I attempt to copy the text box. This macro is being run from the workbook containing the data I want copied. Using Excel 2007 Code:
Sub UploadData()
Dim xlo As New Excel.Application
Dim xlw As New Excel.Workbook
Set xlw = xlo.Workbooks.Open("c:\myworkbook.xlsx")
xlo.Worksheets(1).Cells(2, 1) = Range("d4").Value 'Copy cell content (this works fine)
xlo.Worksheets(1).Cells(2, 2) = TextBox1.Text 'This gives me the object required error
xlw.Save
xlw.Close
Set xlo = Nothing
Set xlw = Nothing
End Sub
Thanks for any help.
The problem with your macro is that once you have opened your destination Workbook (xlw in your code sample), it is set as the ActiveWorkbook object and you get an error because TextBox1 doesn't exist in that specific Workbook. To resolve this issue, you could define a reference object to your actual Workbook before opening the other one.
Sub UploadData()
Dim xlo As New Excel.Application
Dim xlw As New Excel.Workbook
Dim myWb as Excel.Workbook
Set myWb = ActiveWorkbook
Set xlw = xlo.Workbooks.Open("c:\myworkbook.xlsx")
xlo.Worksheets(1).Cells(2, 1) = myWb.ActiveSheet.Range("d4").Value
xlo.Worksheets(1).Cells(2, 2) = myWb.ActiveSheet.TextBox1.Text
xlw.Save
xlw.Close
Set xlo = Nothing
Set xlw = Nothing
End Sub
If you prefer, you could also use myWb.Activate to put back your main Workbook as active. It will also work if you do it with a Worksheet object. Using one or another mostly depends on what you want to do (if there are multiple sheets, etc.).
I think the reason that this is happening could be because TextBox1 is scoping to the VBA module and its associated sheet, while Range is scoping to the "Active Sheet".
EDIT
It looks like you may be able to use the GetObject function to pull the textbox from the workbook.
The issue is with this line
xlo.Worksheets(1).Cells(2, 2) = TextBox1.Text
You have the textbox defined at some other location which you are not using here. Excel is unable to find the textbox object in the current sheet while this textbox was defined in xlw.
Hence replace this with
xlo.Worksheets(1).Cells(2, 2) = worksheets("xlw").TextBox1.Text

Resources