Perform Function for Every Open Workbook - excel

I am creating a blank workbook with a command button that upon clicking, I want it to perform actions on EVERY open workbook currently open (as I will have other workbooks that aren't blank that I want it to carry out operations on).
I get a Subscript out of range error when I run:
Sub Button1_Click()
'
' Button1_Click Macro
' Fire Ext. Comments
'
'
Dim w As Workbook
' For every open workbook...
For Each w In Application.Workbooks
' Activate the current workbook
w.Activate
' Find the comments column (K12 should be the "Comments" column)
If Worksheets("FIRE EXT.").Range("K12").Value = "Comments" Then
' Then we loop through all cells in the specified range (anything below the header row)
Dim rng As Range, cell As Range
' I'm using a range of 500 to look for values, so if a file has more than 500 rows, you'll have to look at it manually
Set rng = Range("A1:A500")
' No loop to change all comments
For Each cell In rng
.......................
...at the "If Worksheets("FIRE EXT.").Range("K12").Value = "Comments" Then" line. So I'm thinking it's starting with the blank workbook and not finding the worksheet named "FIRE EXT.", so what would be the best practise to first test if the activated workbook first has that sheet name, otherwise move on to the next workbook? Thanks!
UPDATE
This was what worked for me, but the other responses would have worked too. Thanks everyone!
Sub Button1_Click()
'
' Button1_Click Macro
' Fire Ext. Comments
'
'
Dim w As Workbook
' For every open workbook...
For Each w In Application.Workbooks
' Don't work on the current/blank workbook
If w.FullName <> ThisWorkbook.FullName Then
' Find the comments column (K12 should be the "Comments" column)
If w.Sheets("FIRE EXT.").Range("K12").Value = "Comments" Then
' Then we loop through all cells in the specified range (anything below the header row)
Dim rng As Range, cell As Range
' I'm using a range of 500 to look for values, so if a file has more than 500 rows, you'll have to look at it manually
Set rng = w.Worksheets("FIRE EXT.").Range("A13:A500")
' No loop to change all comments
For Each cell In rng

You need to fully qualify all of your references. You can also put in a check to ensure it skips the blank workbook (see my comments in this updated code):
Sub Button1_Click()
'
' Button1_Click Macro
' Fire Ext. Comments
'
'
Dim w As Workbook
' For every open workbook...
For Each w In Application.Workbooks
If w.FullName <> ThisWorkbook.FullName Then '<-- TA: Add check to verify you're not working on the blank workbook
'TA: I removed the .Activate line, that is never necessary. Instead, fully qualify all your references
' Find the comments column (K12 should be the "Comments" column)
If w.Worksheets("FIRE EXT.").Range("K12").Value = "Comments" Then '<-- TA: Note that Worksheets is now qualified to w so that it is checking worksheets in the current w workbook
' Then we loop through all cells in the specified range (anything below the header row)
Dim rng As Range, cell As Range
' I'm using a range of 500 to look for values, so if a file has more than 500 rows, you'll have to look at it manually
Set rng = w.Worksheets("FIRE EXT.").Range("A1:A500") '<-- TA: Note that Range is now qualified to w.Worksheets("FIRE EXT.") (if that isn't the correct sheet name, change this to the correct sheet name)
' Now loop to change all comments
For Each cell In rng
.......................

I wanted to type a comment, but it got pretty long.
You will get an error if the Sheet Name "FIRE EXT." (with the period) does not exist on the workbook you are working with. You are looping ALL workbooks, if you have one that doesn't have that sheet, it will error out.
It's better to stick with Sheets when using the Sheet Name, and Worksheets when using the Sheet Number. Sheets("SheetName") vs Worksheets(1)
Avoid using Activate/Select by using the assigned workbook variable, in your code, that's "w"
Sub Button1_Click()
Dim w As Workbook
' For every open workbook...
For Each w In Application.Workbooks
' Find the comments column (K12 should be the "Comments" column)
If w.Sheets("FIRE EXT.").Range("K12").Value = "Comments" Then
' Then we loop through all cells in the specified range (anything below the header row)
Dim rng As Range, cell As Range
' I'm using a range of 500 to look for values, so if a file has more than 500 rows, you'll have to look at it manually
Set rng = w.Sheets("FIRE EXT.").Range("A1:A500")
' Now loop to change all comments
For Each cell In rng
' Now here you dont use "w.Sheets("FIRE EXT.")" because it is already set on `rng'
' so you can just use `cell` like cell.value = "Blah"
.........

Related

variable referencing a cell date value not passing to a copy paste filter

I have a spreadsheet that operators input data in, with the A column being the date, and the data is input by row. The A column is a formula that adds +1 to the date in the previous cell, going all the way down recursively to auto-populate the date as the sheet is filled out.
I have to have a report printed out at the end of every day, and I am trying to use VBA to filter the rows out by a date that the operator inputs on another sheet in cell B2. I need the macro to grab that date value, and pass it as a variable to the filter in order to pull the 12 rows of that date and paste it into a new sheet. Unfortunately, the value it pulls is not being passed, and when I put a MsgBox command in there, it shows it's pulling 12:00 AM and not a date. When using the Date variable, it also breaks the filter on the bottom macro below (trying 2 different versions just to get this working).
I'm not good with VBA, so my macros were pulled from example websites and I tailored them to what I need.
This is one macro I have tried:
Sub For_RangeCopy()
Dim rDate As Date
Dim rSheet As Worksheet
Set rSheet = ThisWorkbook.Worksheets("EOS")
rDate = CDate(rSheet.Range("B2").Value)
MsgBox (rDate)
' Get the worksheets
Dim shRead As Worksheet
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Dim shWrite As Worksheet
Set shWrite = ThisWorkbook.Worksheets("Report")
' Get the range
Dim rg As Range
Set rg = shRead.Range("A1").CurrentRegion
With shWrite
' Clear the data in output worksheet
.Cells.ClearContents
' Set the cell formats
'.Columns(1).NumberFormat = "dd/mm/yyyy"
'.Columns(3).NumberFormat = "$#,##0;[Red]$#,##0"
'.Columns(4).NumberFormat = "0"
'.Columns(5).NumberFormat = "$#,##0;[Red]$#,##0"
End With
' Read through the data
Dim i As Long, row As Long
row = 1
For i = 1 To rg.Rows.Count
If rg.Cells(i, 1).Value2 = rDate Or i = 1 Then
' Copy using Range.Copy
rg.Rows(i).Copy
shWrite.Range("A" & row).PasteSpecial xlPasteValues
' move to the next output row
row = row + 1
End If
Next i
End Sub
And here is another Macro I have tried to use. This one actually gives me the 3 header rows which I don't need, but I don't mind, this paste is a reference for the report layout anyway, so the operators won't see this sheet. But this macro does give me the first block of the date range: 1/1/2023. I do know that the "rgCriteria As String" is likely incorrect, but that is how I get anything useful from this macro. If I change that rgCriteria to a Date, it breaks the rgData.AdvancedFilter command, and I haven't learned enough VBA to know why. And my boss wants this done today, although here I am posting here, thus it's not getting done today.
Sub AdvancedFilterExample()
' Get the worksheets
Dim rSheet As Worksheet
Set rSheet = ThisWorkbook.Worksheets("EOS")
Dim shRead As Worksheet, shWrite As Worksheet
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Set shWrite = ThisWorkbook.Worksheets("Report")
' Clear any existing data
shWrite.Cells.Clear
' Remove the any existing filters
If shRead.FilterMode = True Then
shRead.ShowAllData
End If
' Get the source data range
Dim rgData As Range, rgCriteria As String
Set rgData = shRead.Range("A1").CurrentRegion
' IMPORTANT: Do not have any blank rows in the criteria range
'Set rgCriteria = rSheet.Range("B2")
rgCriteria = rSheet.Range("B2").Value
MsgBox (rgCriteria)
' Apply the filter
rgData.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=rgCriteria _
, CopyToRange:=shWrite.Range("A1")
End Sub
I don't know which method of filtering and pasting is best for my situation, but I do know that the faster is better. I'm copying entire rows, and it needs to be efficient because this log contains a lot of data. I only need one of these macros to work, but I will be heavily modifying them and chaining them together with about 5 other filter/copy/paste sequences to follow, along with printOut commands after that, and finalized by clearing the sheets it pastes to, and then re-enabling all the functionality of the sheet (calculations, displaystatusbar, events, and screenupdating) all to make it quicker while the macro is running. All of these reports will be run using the macro with a button click.
Any thoughts or suggestions would be greatly appreciated. I've been struggling with this for a couple of weeks now. I'm at a loss and turning to the community that has helped me with a TON of questions over the past 20 or so years just by a Google search!
Other information:
I'm using Office 365 on a Windows 10/11 machine. The headers of the sheet it filters does contain merged cells as the header is rows 1-3, there is a lot of data in this sheet that grows through the year. 12 rows per day for an entire year. These macros are written in a Module aptly named "Module 1" if that helps. I do have this workbook, and the original log saved on OneDrive that can be shared.
When using Advanced Filter your criteria range should have headers which match your data table.
Sub AdvancedFilterExample()
Dim rSheet As Worksheet, shRead As Worksheet, shWrite As Worksheet
Dim rgData As Range, rgCriteria As Range
Set rSheet = ThisWorkbook.Worksheets("EOS")
Set shRead = ThisWorkbook.Worksheets("Bi-Hourly Report")
Set shWrite = ThisWorkbook.Worksheets("Report")
Set rgData = shRead.Range("A1").CurrentRegion 'source data range
'## criteria range needs to include a matching date header...
Set rgCriteria = rSheet.Range("B3:B4") 'eg. "Date" in B3, date value in B4
shWrite.Cells.Clear ' Clear any existing data
If shRead.FilterMode = True Then shRead.ShowAllData ' Remove the any existing filters
rgData.AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=rgCriteria, _
CopyToRange:=shWrite.Range("A1")
End Sub

Selecting Columns based on Value in first Row and Copy those Columns to another sheet

I had a few classes of VBA over 10 years ago. Since then it changed a bit and I totally forgot how to do even the basic stuff.
I have a project at University and I would like to automate a process using VBA with Excel 2016/2017.
I get a huge Excel table from an application. From those columns I only need a couple of them based on the column name and I want to select the ones that interest me and erase the ones that don't.
I thought about a couple ways of achieving it:
Search all the columns with loop and copy them to a new Sheet
Just erase the columns that donĀ“t interest me.
I tried different options with if and case statements but my "VBA Grammar" and knowledge are horrible. Does anyone have any tips?
Table example:
A
B
C
D
E
Customer
Product
Age
Data
Color
John
something
3
x
blue
Sheet 1
If I am only Interested in the Customer, Product and Color, how can I automate the whole process?
Thanks in advance for any tips or code snippets that could help me :)
Export Columns to Another Worksheet
It will create a copy of the worksheet and delete the undesired columns.
Adjust the values in the constants section.
Option Explicit
Sub ExportColumnsToWorksheet()
' Source
Const sName As String = "Sheet1"
' Destination
Const dName As String = "NewSheet"
Const dColumnTitlesList As String = "Customer,Product,Color"
' Create a reference to the workbook containing this code ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook
' Create a reference to the Source Worksheet ('sws').
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Application.ScreenUpdating = False
' (Attempt to) create a reference to the Destination Worksheet ('dws').
Dim dws As Worksheet
On Error Resume Next
Set dws = wb.Worksheets(dName)
On Error GoTo 0
If Not dws Is Nothing Then ' if it exists...
Application.DisplayAlerts = False ' (without confirmation)
dws.Delete ' ... delete it
Application.DisplayAlerts = True
End If
' Copy the worksheet as the last sheet. The copy becomes the active sheet.
sws.Copy After:=wb.Sheets(wb.Sheets.Count)
' Create a reference to the copied worksheet i.e. the Destination Worksheet.
Set dws = ActiveSheet
' Give it a name.
dws.Name = dName
' Create a reference to the Destination Header Range
' (you may need a different way).
Dim dhrg As Range: Set dhrg = dws.Range("A1").CurrentRegion.Rows(1)
' Write the column titles to the Column Titles Array ('dColumnTitles').
Dim dColumnTitles() As String: dColumnTitles = Split(dColumnTitlesList, ",")
Dim delrg As Range
Dim dhCell As Range
' Loop through the cells ('dhCell') of the Destination Header Range.
For Each dhCell In dhrg.Cells
' Check if the value of the current Header Cell is not found
' in the Column Titles Array.
If IsError(Application.Match(CStr(dhCell.Value), dColumnTitles, 0)) Then
' Combine the current Header Cell into the Delete Range ('delrg').
If delrg Is Nothing Then
Set delrg = dhCell
Else
Set delrg = Union(delrg, dhCell)
End If
End If
Next dhCell
' Check if no cells were combined.
If delrg Is Nothing Then Exit Sub
' Delete the entire columns of the Delete Range.
delrg.EntireColumn.Delete
Application.ScreenUpdating = True
MsgBox "The worksheet with the desired columns has been created.", _
vbInformation, "Export Columns to Worksheet"
End Sub

Excel VBA - search the same column across all worksheets, looking for ANY data

i can do simple row deletions etc in single sheets in VBA, but this one has me stumped.
I need to search for ANY data (likely to be an email address, but in reality could be any text or number) in N1:N100, across all open worksheets...except Sheet1 (i.e. from Sheet2 to whatever the last open worksheet is)...
...if any data is found in the N1:N100 range for that particular worksheet (i.e. Sheet2) then do nothing and search the next worksheet (i.e. Sheet3)...if no data is found then enter "NONE" in cell N1 (on i.e. Sheet2) and then move onto the next worksheet (i.e. Sheet3).
Ive seen IF/ELSEIF/THEN code from other people, but it all seems to be sheet specific...and as i said im a bit out of my depth with this particular part.
This will feed into a larger bit of VBA code that ive got thats already spread over several sheets of A4, thought i had worked out all the bugs by now :D
Sub WorksheetLoop()
Dim WS_Count As Integer
Dim I As Integer
' Set WS_Count equal to the number of worksheets in the active
' workbook.
WS_Count = ActiveWorkbook.Worksheets.Count
' Begin the loop.
For I = 1 To WS_Count
' Insert your code here.
' The following line shows how to reference a sheet within
' the loop by displaying the worksheet name in a dialog box.
MsgBox ActiveWorkbook.Worksheets(I).Name
Next I
End Sub
Or you can loop over all of the worksheets using a For Each loop:
Sub WorksheetLoop2()
' Declare Current as a worksheet object variable.
Dim Current As Worksheet
' Loop through all of the worksheets in the active workbook.
For Each Current In Worksheets
' Insert your code here.
' This line displays the worksheet name in a message box.
MsgBox Current.Name
Next
End Sub

SUMPRODUCT formula with changing number of worksheets

I have an Excel workbook with multiple worksheets and I would like to use SUMPRODUCT formula to sum values from all worksheets with name Page 1, Page 1(2), Page 1(3), Page 1(4) etc.:
=SUMPRODUCT(SUMIF(INDIRECT("'"&D2:D4&"'!B11:B100"),$B3,INDIRECT("'"&D2:D4&"'!E11:E100")))
The problem is that the number of Page 1 worksheets is different every time and I need to update &D2:D4& every time manually. Is there any way I could automate it so I don't need to change the range manually?
Please paste the code below in the ThisWorkbook code sheet of the workbook in which you have your SUMPRODUCT formula. Then adjust the code to comply with the comments I added into it (especially with regard to the name of the worksheet on which you have the range D2:D4.
Option Explicit
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
SetPageRange
End Sub
Private Sub SetPageRange()
' this procedure creates a named range "Pages" which you can
' use in your formula instead of "D2:D4"
' =SUMPRODUCT(SUMIF(INDIRECT("'"& Pages &"'!B11:B100"),$B3,INDIRECT("'"& Pages &"'!E11:E100")))
Const WsName As String = "Page" 'Page, Page (1), Page(x)
' change to "Page 1" if that is what you need
Dim Wb As Workbook
Dim MasterWs As Worksheet
Dim Arr() As String
Dim i As Integer
Dim Ws As Worksheet
' the Active workbook is not necessarily the workbook containing this code !!
Set Wb = ActiveWorkbook
ReDim Arr(1 To 20) ' maximum number of sheets you expect
' this number of rows must be available
' below D2 in the sheet where your
' formula resides
For Each Ws In Wb.Worksheets
' this collects all sheets whose name starts with "Page"
If InStr(1, Ws.Name, WsName, vbTextCompare) = 1 Then
i = i + 1
Arr(i) = Ws.Name
End If
Next Ws
' Change the name "Sheet5" to the name of the sheet where your
' SUMPRODUCT formula resides and where you currently have D2:D4
' if it isn't in the ActiveWorkbook, then where is it?
Set MasterWs = Wb.Worksheets("Sheet5")
With MasterWs.Cells(2, "D") ' this is where the names will be written
' from D2 down (20 rows, as set above)
.Resize(UBound(Arr)).Value = Application.Transpose(Arr)
Wb.Names.Add Name:="Pages", RefersTo:="=" & .Resize(i).Address(True, True, xlA1, True)
End With
End Sub
Save the workbook as macro-enabled (xlsm format) Now, this code will run automatically when you save the workbook. I imagined that you would open it, import a number of "Page 1" sheets and then want to make your totals. So, now you will have to save it first. The range D2:D4 (D2:D20 in my code) will be adjusted automatically.
If you don't like the automation, delete the Workbook_BeforeSave procedure entirely (or place an apostrophe at the beginning of each of its lines). You can run the SetPageRange procedure manually by placing the cursor anywhere in it and pressing F5. You can get it to be available for being run from Excel's worksheet interface by changing the attribute Private to Public.
The code will create a named range named "Pages" and you will need to change your formula to point to the named range (the size and content of which the code manipulates) instead of the present "D2:D4". The revised formula is included in the code's comments above.

Excel: Populate Data Across Multiple Worksheets

Unfortunately for my employer, none of my network engineering courses included advanced Excel formula programming. Needless to say, I know nothing about Excel save for basic SUM and COUNT formula commands.
My employer has an Excel workbook with multiple worksheets within it representing each month of the calendar year. We want to be able to have a "total" worksheet in the workbook that reflects all data across the entire workbook in each column/row.
An example for the sake of clarity:
In the worksheet "May_2013", column A is labeled "DATE". Cell A2 contains the data "MAY-1".
In the worksheet "June_2013", column A is labeled "DATE". Cell A2 contains the data "JUNE-1".
In the worksheet "Total", column A is labeled "DATE". We want cells A2 to reflect "MAY-1" and A3 to reflect "JUNE-1".
We want to do this for all worksheets, columns A-Q, rows 2-33 and populate a master sheet at the very end containing all data in all worksheets in their corresponding columns.
Is this possible?
Here are two VBA solutions. The first does this:
Check if a sheet "totals" exists. Create it if it does not
Copy the first row (A to Q) of first sheet to "totals"
Copy block A2:Q33 to "totals" sheet starting at row 2
Repeat for all other sheets, appending 32 rows lower each time
The second shows how to do some manipulation of the column data before copying: for each column it applies the WorksheetFunction.Sum(), but you could replace that with any other aggregating function that you would like to use. It then copies the result (one row per sheet) to the "totals" sheet.
Both solutions are in the workbook you can download from this site. Run the macros with , and pick the appropriate one from the list of options that shows up. You can edit the code by invoking the VBA editor with .
Sub aggregateRaw()
Dim thisSheet, newSheet As Worksheet
Dim sheetCount As Integer
Dim targetRange As Range
sheetCount = ActiveWorkbook.Sheets.Count
' add a new sheet at the end:
If Not worksheetExists("totals") Then
Set newSheet = ActiveWorkbook.Sheets.Add(after:=Sheets(sheetCount))
newSheet.Name = "totals"
Else
Set newSheet = ActiveWorkbook.Sheets("totals")
End If
Set targetRange = newSheet.[A1]
' if you want to clear the sheet before copying data, uncomment this line:
' newSheet.UsedRange.Delete
' assuming you want to copy the headers, and that they are the same
' on all sheets, you can copy them to the "totals" sheet like this:
ActiveWorkbook.Sheets(1).Range("1:1").Copy targetRange
Set targetRange = targetRange.Offset(1, 0) ' down a row
' copy blocks of data from A2 to Q33 into the "totals" sheet
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> newSheet.Name Then
ws.Range("A2", "Q33").Copy targetRange
Set targetRange = targetRange.Offset(32, 0) ' down 32 rows
End If
Next ws
End Sub
Sub aggregateTotal()
Dim thisSheet, newSheet As Worksheet
Dim sheetCount As Integer
Dim targetRange As Range
Dim columnToSum As Range
sheetCount = ActiveWorkbook.Sheets.Count
' add a new sheet at the end:
If Not worksheetExists("totals") Then
Set newSheet = ActiveWorkbook.Sheets.Add(after:=Sheets(sheetCount))
newSheet.Name = "totals"
Else
Set newSheet = Sheets("totals")
End If
' assuming you want to copy the headers, and that they are the same
' on all sheets, you can copy them to the "totals" sheet like this:
Set targetRange = newSheet.[A1]
ActiveWorkbook.Sheets(1).Range("A1:Q1").Copy targetRange
Set targetRange = targetRange.Offset(1, 0) ' down a row
For Each ws In ActiveWorkbook.Worksheets
' don't copy data from "total" sheet to "total" sheet...
If ws.Name <> newSheet.Name Then
' copy the month label
ws.[A2].Copy targetRange
' get the sum of the coluns:
Set columnToSum = ws.[B2:B33]
For colNum = 2 To 17 ' B to Q
targetRange.Offset(0, colNum - 1).Value = WorksheetFunction.Sum(columnToSum.Offset(0, colNum - 2))
Next colNum
Set targetRange = targetRange.Offset(1, 0) ' next row in output
End If
Next ws
End Sub
Function worksheetExists(wsName)
' adapted from http://www.mrexcel.com/forum/excel-questions/3228-visual-basic-applications-check-if-worksheet-exists.html
worksheetExists = False
On Error Resume Next
worksheetExists = (Sheets(wsName).Name <> "")
On Error GoTo 0
End Function
Final(?) edit:
If you want this script to run automatically every time someone makes a change to the workbook, you can capture the SheetChange event by adding code to the workbook. You do this as follows:
open the Visual Basic editor ()
In the project explorer (left hand side of the screen), expand the VBAProject
Right-click on "ThisWorkbook", and select "View Code"
In the window that opens, copy/paste the following lines of code:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
' handle errors gracefully:
On Error GoTo errorHandler
' turn off screen updating - no annoying "flashing"
Application.ScreenUpdating = False
' don't respond to events while we are updating:
Application.EnableEvents = False
' run the same sub as before:
aggregateRaw
' turn screen updating on again:
Application.ScreenUpdating = True
' turn event handling on again:
Application.EnableEvents = True
Exit Sub ' if we encountered no errors, we are now done.
errorHandler:
Application.EnableEvents = True
Application.ScreenUpdating = True
' you could add other code here... for example by uncommenting the next two lines
' MsgBox "Something is wrong ... " & Err.Description
' Err.Clear
End Sub
Kindly use RDBMerge add-in which will combine the data from different worksheet and create a master sheet for you. Please see the below link for more details.
http://duggisjobstechnicalstuff.blogspot.in/2013/03/how-to-merge-all-excel-worksheets-with.html
Download RDBMerge
You can use the indirect function to reference the sheet name. In the image below this function takes the header name (B37) and uses it as the sheet reference. All you have to do is choose the correct "total cell" which I made "A1" in "MAY_2013". I put an image below to show you my reference name as well as tab name

Resources