I'm trying to loop through all the worksheets in an Excel file using Access VBA.
The subroutine needs to select the first row and set the RowHeight in each worksheet.
I'm using a string variable to call the subroutine and passing the worksheet name to it. It works the first time but the next time I get
"Select method of Range Class Failed"
I tried moving the variable declarations around, changing where I open Excel (it opens twice if I put it in the subroutine) and doing it as function instead of a subroutine.
'My object and worksheet variables are declared at the top of the object:
Dim objExcel As Object
Dim wks As Worksheet
Dim wkb As Workbook
'I'm opening Excel and setting the workbook object in a subroutine:
Set objExcel = CreateObject("Excel.Application") 'Excel is invoked 01
Set wkb = objExcel.Workbooks.Open(strOutputPathAndFileName)
objExcel.Application.Visible = True
'Then calling the subroutine to set the row height and wrap text property
FirstRowHeightAndWrap ("ChangeTracking")
FirstRowHeightAndWrap ("FivePCalcsThisPPE")
'Here's the function
Function FirstRowHeightAndWrap(strSheetName As String)
Set wks = wkb.Sheets(strSheetName)
With wks
.Rows(1).Select
.Rows(1).RowHeight = 28
.Rows(1).WrapText = True
End With
End Function
It's something about setting the wks variable in a different place than the objExcel and wkb variables, clearly, 'cuz it works if I do it all in one place.
Why does it not work?
It's something about setting the wks variable in a different place than the objExcel and wkb variables, clearly, 'cuz it works if I do it all in one place.
The main reason why your function call does not work is because you are trying to use a variable that is outside it's scope. The variables that you declare and set inside a sub/function are local to that sub/function.
In your case, the wkb variable can only be referenced within the sub where it was declared and set. Once you call the FirstRowHeightAndWrap, you will get an Object required error because access does not know what the variable wkb is.
You can see this process by using the View > Locals Window and executing your code line by line, and you will notice that your existing local variables show up as Empty once it reaches a new sub/function call.
How can you fix it?
1. A fix would be to pass the wkb object as a reference in
your function. In other words you would need to adjust the lines as
follow :
In your sub ...
FirstRowHeightAndWrap wkb, "ChangeTracking"
FirstRowHeightAndWrap wkb, "FivePCalcsThisPPE"
You will need to also change the function header and function to:
If you want to loop for all worksheets ...
Function FirstRowHeightAndWrap(ByRef wkb As Object)
For Each wks In wkb.Worksheets
wks.Rows(1).RowHeight = 28
wks.Rows(1).WrapText = True
Next
End Function
If you want to keep your original function with the specific sheetname parameter ...
Function FirstRowHeightAndWrap(ByRef wkb As Object, strSheetName As String)
Set wks = wkb.Sheets(strSheetName)
With wks
.Rows(1).RowHeight = 28
.Rows(1).WrapText = True
End With
End Function
2. The easier solution, especially in cases where you are using simple functions, is to dump everything in the same sub so you don't have to pass a reference with each function call.
Also as mentioned in the comments, you do not need the .Select line in your function. I would also recommend using late binding for your variables in order to prevent any possible issues with references if you have other users using your application.
Good luck! :)
All you need is to loop the worksheets like this:
Public Function SetRowHeight()
Dim objExcel As Object
Dim wkb As Workbook
Dim wks As Worksheet
Set objExcel = CreateObject("Excel.Application")
Set wkb = objExcel.Workbooks.Open("c:\test\test.xlsx")
For Each wks In wkb.Worksheets
wks.Rows(1).RowHeight = 28
wks.Rows(1).WrapText = True
Next
wkb.Close True
Set wkb = Nothing
objExcel.Quit
Set objExcel = Nothing
End Function
Related
I created a userform sub that will allow the user to choose from the open workbook to use as the reference workbook using a combo box. My current script is returning an error that workbooks(wb) is not defined - I assume this is because the variable is defined in two different modules and the combo box is not in the module being called. Ideally would like to use the userform below
Private Sub Go_Click()
If ComboBox1.ListIndex = -1 Then
MsgBox "Please select a workbook name and try again"
Exit Sub
End If
Dim wb As Variant
wb = ComboBox1.List(ComboBox1.ListIndex)
Call GenerateReportUserForm
End Sub
To call this sub:
Sub newMacro()
Dim copyNames As Range, pasteNames As Range, copyAmounts As Range, pasteAmounts As Range, copyDates As Range, pasteDates As Range, _
copyPayment As Range, pastePayment As Range
' For cheques only
Set copyNames = Workbooks(wb).Worksheets(2).Columns("F")
Set copyAmounts = Workbooks(wb).Worksheets(2).Columns("AR")
Set copyDates = Workbooks(wb).Worksheets(2).Columns("AI")
Set copyPayment = Workbooks(wb).Worksheets(2).Columns("AJ")
Set pasteNames = Workbooks("VBA Workbook.xlsm").Worksheets(1).Columns("A")
Set pasteAmounts = Workbooks("VBA Workbook.xlsm").Worksheets(1).Columns("C")
Set pasteDates = Workbooks("VBA Workbook.xlsm").Worksheets(1).Columns("D")
Set pastePayment = Workbooks("VBA Workbook.xlsm").Worksheets(1).Columns("E")
copyNames.Copy Destination:=pasteNames
copyAmounts.Copy Destination:=pasteAmounts
copyDates.Copy Destination:=pasteDates
copyPayment.Copy Destination:=pastePayment
End sub
Thanks!
wb clearly wants to be a String representing the name of a workbook. Declare it as such.
Dim wbName As String
wbName = ComboBox1.List(ComboBox1.ListIndex)
Side note, name your controls. AvailableFilesBox tells so much more than ComboBox1.
Now, what you want is to pass this variable as an argument; do not use a global variable unless you absolutely MUST.
Call GenerateReportUserForm
Not sure what this is supposed to be doing, but it's not calling newMacro. If you want to make it call newMacro, then change it to this:
NewMacro wbName
Or if you really really want to keep that redundant and distracting Call keyword:
Call NewMacro(wbName)
Note: give that macro a meaningful name that describes what the macro does. "new macro" might be clear now, but not so much once there are 4-5 newer macros in that project - and newMacro2 is NOT an option!
Now, in order to pass wbName as an argument, the procedure needs to declare that it takes a parameter - like this:
Public Sub NewMacro(ByVal wbName As String)
Inside that procedure scope, you don't need to constantly dereference the Workbook object. Do it once, store the object reference into a local variable, then use that variable:
Dim wb As Workbook
Set wb = Workbooks(wbName)
Turns out, that macro doesn't really care for the workbook's name; what it really actually wants is a Workbook object. So, let's make it the caller's responsibility to provide a Workbook.
First we change the signature to take a Workbook parameter:
Public Sub NewMacro(ByVal wb As Workbook)
Then we change the form code to supply it:
Dim wb As Workbook
Set wb = Workbooks(ComboBox1.List(ComboBox1.ListIndex))
NewMacro wb ' or: Call NewMacro(wb)
Remember to always put Option Explicit at the top of every module; Rubberduck can help you find & fix this, and other issues in your code.
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!
In this project I'm developing, I created many Subs that all use the same three workbooks. But is there a better way to use these workbooks' sheets without having to write them down everytime I create a new Sub? I tried returning it in a function but it does not work.
Function defineWorksheet() As Worksheet
Dim wk_Base_18 As Excel.Workbook
Dim ws_18 As Excel.Worksheet
Set wk_Base_18 = Excel.Workbooks("2019.01.03.xlsb")
Set ws_18 = wk_Base_18.Worksheets("Planilha1")
ws_18
End Function
error 91
Yes, you can declare them as a global variable.
Public ws1 As Worksheet
Then instantiate the global variable during the application load event of the excel application
Private Sub Workbook_Open()
Set ws1 = ThisWorkbook.Sheets("YourSheetName")
End Sub
And now, you can refer to it via the variable, eg.
Dim x as Integer: x = ws1.Range("B5")
The general idea is sound, you just need to create one function per object:
Function wk_Base_18() as workbook
Set wk_Base_18 = Excel.Workbooks("2019.01.03.xlsb")
End Function
Function ws_18() as worksheet
Set ws_18 = wk_Base_18.Worksheets("Planilha1")
End Function
Then whenever you go to use the variable ws_18 or wk_Base_18, you will be calling these same functions.
Is it possible to store a PageSetup object that will be used to set the printing options of a worksheet? I tried with this code but I'm getting an error that says: Object variable or With block variable not set. This is how I'm doing it since I need to setup the settings first from a form and loop through some sheets using the printing settings stored in this object.
Dim curPageSetup As PageSetup
curPageSetup.paperSize = xlPaperA3
If all that you want to do is change the PaperSize for all of the sheets in your book, you can do it like this
Sub SetPaperSize()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
ws.PageSetup.PaperSize = xlPaperA3
Next ws
End Sub
Not necessary to store PageSetup to change its attributes
I'm having a hard time with VBA errors on Excel, can someone help me understanding what this error means?
"VBA Object variable or With block variable not set error"
My function is supposed to check if a sheet exists, if not create the new sheet, rename it and return to the main function.
The code works, but the error is always thrown..
Function GetWorksheetFromName(Name As String) As Worksheet
Dim WS As Worksheet
For Each WS In ThisWorkbook.Worksheets
If StrComp(WS.Name, Name, vbTextCompare) = 0 Then
Set GetWorksheetFromName = WS
Exit Function
End If
Next WS
With ThisWorkbook
Set WS = Worksheets.Add(After:=Sheets(.Sheets.Count))
WS.Name = Name
End With
Set GetWorksheetFromName = WS
End Function
P.S.: this might help, but I still haven't fixed my code
Set WS = .Worksheets.Add(After:=.Sheets(.Sheets.Count))
note the added period before Sheets and Worksheets - your current code addresses the Sheets collection of the Active workbook, not ThisWorkbook.
The error was actually listed on the MSDN link
You attempted to use an object variable that has been set to Nothing.
Set MyObject = Nothing ' Release the object.
MyCount = MyObject.Count ' Make a reference to a released object.
Respecify a reference for the object variable. For example, use a new Set statement to set a new reference to the object.
Dim WS As Worksheet
Sheets("Config").Select
WS = GetWorksheetFromName(Range("B8").Value)
My error was on the last line, I was missing the SET
Set WS = GetWorksheetFromName(Range("B8").Value)
Thank you Tim Williams and Scott Craner for your quick help