Excel VBA run macro across dynamic range of sheets - vba

As an extension from the last question I asked, I'm trying to run a macro across all worksheets, which you guys successfully helped me to do.
I've been told that the worksheet names can't be hardcoded, so I'm going to have to modify my current solution.
Sub RemoveCarriageReturns()
Dim MyRange As Range
Dim NameList() As Variant
NameList = Array("OTCUEXTR", "OTFBCUDS", "OTFBCUEL")
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For i = 0 To 2
With Worksheets(NameList(i))
For Each MyRange In .UsedRange
If 0 < InStr(MyRange, Chr(10)) Then
MyRange = Replace(MyRange, Chr(10), "")
End If
Next MyRange
End With
Next i
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
I've tried to populate the array with a For loop that gathers names of each worksheet however I feel after 2 days blankly staring at this, my limited VBA knowledge has run out and I'm stuck, I would really appreciate some pointers on how to get this macro to work across an range of sheets that can change in quantity and names.
Happy to provide any more information you need in a comment

You can do it like this (or could use the index along the lines of your original code).
Sub RemoveCarriageReturns()
Dim MyRange As Range
Dim ws As Worksheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each ws In Worksheets
With ws
For Each MyRange In .UsedRange
If 0 < InStr(MyRange, Chr(10)) Then
MyRange = Replace(MyRange, Chr(10), "")
End If
Next MyRange
End With
Next ws
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Public Function GetSheetNames(ByVal wbk As workbook) As String()
Dim names() As String
Dim count As Integer
Dim i As Integer
count = wbk.Worksheets.count
ReDim names(count - 1)
For i = 1 To wbk.Worksheets.count
names(i - 1) = wbk.Worksheets(i).Name
Next
GetSheetNames = names
End Function
Usage: GetSheetNames(Application.ActiveWorkbook)
UPDATE: For selected sheets only:
Public Function GetActiveSheetNames(ByVal wbk As workbook) As String()
Dim names() As String
Dim count As Integer
Dim i As Integer
count = wbk.Windows(1).SelectedSheets.count
ReDim names(count - 1)
For i = 1 To wbk.Windows(1).SelectedSheets.count
names(i - 1) = wbk.Windows(1).SelectedSheets(i).Name
Next
GetActiveSheetNames = names
End Function

Related

Display all available sheets in combobox, except the hidden ones, (loop through sheets add to list) VBA

Okay genius hive mind, what am I doing wrong this time?
'wb and ws dimmed in module level declarations...
Set wb = ThisWorkbook
wb.activate
Dim I As Integer, sheetCount As Integer
sheetCount = wb.Worksheets.Count
Dim sheetNum As Integer
sheetNum = 1
With cboCopyFromSheet 'combobox
For I = 0 To sheetCount - 1
'not sure why the capital 'I' describing an object?
'copied from MS documentation
If wb.Worksheets(sheetNum).Visible = True Then
.AddItem wb.Worksheets(sheetNum).Name, I '<----Error
End If
sheetNum = sheetNum + 1
Next I
End With
Weirdly this only happens when I = 9 and sheetnum = 10
None of the sheets are currently hidden ( but some will be )
sheet 10 happens to be a blank sheet...
We are very confucius.
Error thrown is "invalid argument"
Any Clues?
A similar approach to Fane's answer, using the For Each statement.
Sub Whatever()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Visible Then combo.AddItem ws.Name
Next
End Sub
Try the next (simple) code, please. Creating the habit to use combobox List property, will be helpful when you will need to rapidly load a big range (multi-columns, too):
Sub testLoadComboSheetsNames()
Dim sh As Worksheet, arrSh As Variant, k As Long
ReDim arrSh(1 To ThisWorkbook.Worksheets.count)
For Each sh In ThisWorkbook.Worksheets
If sh.Visible = True Then k = k + 1: arrSh(k) = sh.Name
Next
ReDim Preserve arrSh(k)
cboCopyFromSheet.list = arrSh
End Sub
In order to work, your code must look like the next:
Sub testLoadComboShbis()
Dim i As Long, wb As Workbook
Set wb = ThisWorkbook
For i = 1 To wb.Worksheets.count
If wb.Worksheets(i).Visible = True Then
cboCopyFromSheet.AddItem wb.Worksheets(i).Name
End If
Next i
End Sub

VBA - Trim function : reduce time of operation / freezing

I have written code in VBA that removes some potential spaces between characters. The code works pretty well but becomes really slow when the file contains thousands of rows. I'd like to know if it's possible to improve it, in order to reduce the time of operation, but also mainly to stop the file from freezing. Here is the code:
Sub Test()
Dim cell as Range
Dim sht As Worksheet
Dim LastRow As Long
Dim StartCell As Range
Dim areaToTrim As Range
Set sht = ThisWorkbook.Worksheets("SS upload")
Set StartCell = sht.Range("A14")
LastRow = sht.Cells(sht.Rows.Count, StartCell.Column).End(xlUp).Row
Set areaToTrim = sht.Range("B14:B" & LastRow)
For Each cell In areaToTrim
cell.Value = Trim(cell.Value)
Next cell
End Sub
The fastest way is to read the range into an array, trim it there and then write it back to the range:
Sub Test()
Dim sht As Worksheet
Dim LastRow As Long
Dim StartCell As Range
Dim areaToTrim As Range
Dim varArray() As Variant
Dim i As Long
Set sht = ThisWorkbook.Worksheets("SS upload")
Set StartCell = sht.Range("A14")
LastRow = sht.Cells(sht.Rows.Count, StartCell.Column).End(xlUp).Row
Set areaToTrim = sht.Range("B14:B" & LastRow)
varArray = areaToTrim ' Read range into array
For i = LBound(varArray, 1) To UBound(varArray, 1)
varArray(i, 1) = Trim(varArray(i, 1))
Next i
areaToTrim.Value = varArray ' Write array back to range
End Sub
No need to worry about Application.ScreenUpdating or Application.Calculation. Nice and simple!
If you are still worried about any responsiveness, put a DoEventsin the body of the loop.
You can prevent the freezing when you insert DoEvents in your loop.
And then execute it, say every hundredth time.
This will make the loop run a little slower, but allows the user to use the GUI meanwhile.
...
Dim cnt As Integer
For Each cell In areaToTrim
cell.Value = Trim(cell.Value)
cnt=cnt + 1
If cnt Mod 100 = 0 Then
DoEvents
End If
Next cell
...
You can play around with the number to optimize it for your needs.
DoEvents brings also some problems with it. A good explanation about DoEvents can be found here.
Try like this, to reduce screenupdating. This is a piece of code, that I always use, thus some of the commands are probably a bit too much for the current question, but they can be still useful.
As a second point - do not declare a variable with the name Cell, you can suffer a bit from this later. Declare it rngCell or myCell or anything else, which is not part of the VBE variables.
Public Sub TestMe()
Call OnStart
'YourCode
Call OnEnd
End Sub
Public Sub OnEnd()
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.AskToUpdateLinks = True
Application.DisplayAlerts = True
Application.Calculation = xlAutomatic
ThisWorkbook.Date1904 = False
Application.StatusBar = False
End Sub
Public Sub OnStart()
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.AskToUpdateLinks = False
Application.DisplayAlerts = False
Application.Calculation = xlAutomatic
ThisWorkbook.Date1904 = False
ActiveWindow.View = xlNormalView
End Sub
If you feel like it, you may save the range as an array and do the trim operation there. However, it may overcomplicate your code, if you are not used to work with arrays - Trim Cells using VBA in Excel

How to import worksheet with desired columns? Excel VBA

I'm able to import worksheets successfully to my workbook. But is it possible to just import the columns that I want? The data is really huge and I don't want to have the trouble to go through every part of the cells.
Below are my codes:
Sub ImportSheet()
Dim wb As Workbook
Dim activeWB As Workbook
Dim sheet As Worksheet
Dim FilePath As String
Dim oWS As String
Set activeWB = Application.ActiveWorkbook
FilePath = "C:\Report.xlsx"
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Set wb = Application.Workbooks.Open(FilePath)
wb.Sheets("Report").Copy After:=activeWB.Sheets(activeWB.Sheets.Count)
activeWB.Activate
wb.Close False
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Not sure if I'm breaking protocol here but this is a completely different approach and the option to Add Another Answer was there. This method uses the 'copy to new worksheet' approach which should be easier on limited resources.
Sub ImportSheet()
Dim iWB As Workbook, aWB As Workbook, ws As Worksheet
Dim FilePath As String, v As Long, vCOLs As Variant
Application.ScreenUpdating = False
Application.DisplayAlerts = False
FilePath = "C:\Report.xlsx"
vCOLs = Array(1, 13, 6, 18, 4, 2) 'columns to copy in this order
Set aWB = Application.ActiveWorkbook
With aWB
.Sheets.Add after:=.Sheets(.Sheets.Count)
Set ws = .Sheets(.Sheets.Count)
'.name = "Report" 'you can name the new ws but do NOT duplicate
End With
Set iWB = Application.Workbooks.Open(FilePath)
With iWB.Sheets("Report").Cells(1, 1).CurrentRegion
.Cells = .Cells.Value
For v = LBound(vCOLs) To UBound(vCOLs)
.Columns(vCOLs(v)).Copy Destination:=ws.Cells(1, v + 1)
Next v
End With
iWB.Close False
Set iWB = Nothing
Set ws = Nothing
Set aWB = Nothing
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
My primary concern here is not knowing the layout of the 'Report' worksheet. The boundaries of the .CurrentRegion are dictated by the first fully blank column to the right and the first fully blank row down. A block of data rarely has this but worksheets called Report often do.
You are closing the freshly opened workbook (without saving or warnings) after the copy so I would suggest that you loop through the columns you do not want and delete then prior to the copy. Incorporate this snippet into your existing code
Dim v As Long, vNoCopy As Variant, wb As Workbook
vNoCopy = Array(1, 3, 5, 7) 'should in ascending order (reversed below)
With wb.Sheets("Report")
.Cells = .Cells.Value 'just in case there are referenced formulas involved
For v = UBound(vNoCopy) To LBound(vNoCopy) Step -1
.Columns(vNoCopy(v)).EntireColumn.Delete
Next v
wb.Sheets("Report").Copy After:=activeWB.Sheets(activeWB.Sheets.Count)
End With
wb.Close False
That should remove columns A, C, E & G from the report before copying. Closing without saving should leave the original Report.xlsx unaffected.

Worksheet_Change Macro - Changing multiple cells

I wrote a macro and while it works, functionally its not what is needed. It's an interactive checklist that breaks down multiple areas of machines and if they are working checks them off and then this updates a master list with multiple sections. However, it only works with one cell at a time and it needs to be able to work with multiple cells at a time (both with rows and columns). Here's my current code:
'Updates needed:
' Make so more than one cell works at a time
' in both x and y directions
Private Sub Worksheet_Change(ByVal Target As Excel.range)
Dim wb As Workbook
Dim mWS As Worksheet
Dim conName As String
Dim mCol As range
Dim mCon As Integer
Dim count As Long
Dim cell As range
Dim y As String
count = 1
y = ""
Set wb = ActiveWorkbook
Set mWS = wb.Sheets("Master")
Set mCol = mWS.range("B:B")
mCon = 0
'Selects the name of the string value in which we need to search for in master list
If Target.Column < 100 Then
ThisRow = Target.Row
conName = ActiveSheet.Cells(ThisRow, "B")
y = Target.Value
End If
'search for matching string value in master list
For Each cell In mCol
If cell.Value = conName Then
mCon = count
Exit For
End If
count = count + 1
Next
'mark as "x" in Master list
Dim cVal As Variant
Set cVal = mWS.Cells(count, Target.Column)
cVal.Value = y
End Sub
What is happening - If I drag down "x" for multiple rows or columns my codes breaks at y = Target.Value and will only update the cell I first selected and its counterpart on the master list. What it should do is if I drag and drop the "x" onto multiple rows of columns it should update all of them in the sheet I'm working on and the master list. I only set up the macro for one cell at a time and I have no idea how to set it up for dragging and dropping the "x" value for multiple rows
I think you need a For ... Each iteration over the Target in order to work with multiple cells. As Michael noted in the comments, the _Change event fires only once, but the Target reflects all cell(s) that changed, so you should be able to iterate over the Target range. I tested using this simple event handler:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim myRange As Range
Dim myCell As Range
Set myRange = Target
For Each myCell In myRange.Cells
Debug.Print myCell.Address
Next
End Sub
I am not able to test obviously on your data/worksheet, but I think it should put you on the right track.
Private Sub Worksheet_Change(ByVal Target As Excel.range)
Dim wb As Workbook
Dim mWS As Worksheet
Dim conName As String
Dim mCol As range
Dim mCon As Integer
Dim count As Long
Dim cell As range
Dim y As String
count = 1
y = ""
Set wb = ActiveWorkbook
Set mWS = wb.Sheets("Master")
Set mCol = mWS.range("B:B")
mCon = 0
'Add some new variables:
Dim myRange as Range
Dim myCell as Range
Set myRange = Target
Application.EnableEvents = False '## prevents infinite loop
For each myCell in myRange.Cells
If myCell.Column < 100 Then
ThisRow = myCell.Row
conName = ActiveSheet.Cells(ThisRow, "B")
y = myCell.Value
End If
'search for matching string value in master list
For Each cell In mCol
If cell.Value = conName Then
mCon = count
Exit For
End If
count = count + 1
Next
'mark as "x" in Master list
Dim cVal As Variant
Set cVal = mWS.Cells(count, Target.Column)
cVal.Value = y
Next
Application.EnableEvents = True '## restores event handling to True
End Sub
You need to iterate through the cells using a ForEach loop.
Also, you may be better using the Selection object rather than Target
Private Sub Worksheet_Change(ByVal Target As Range)
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each cell In Selection
Debug.Print cell.Address
Next cell
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub

Excel Macro for creating new worksheets

I am trying to loop through some columns in a row and create new worksheets with the name of the value of the current column/row that I am in.
Sub test()
Range("R5").Select
Do Until IsEmpty(ActiveCell)
Sheets.Add.Name = ActiveCell.Value
ActiveCell.Offset(0, 1).Select
Loop
End Sub
This code creates the first one correctly starting at R5 but then it appears that the macro switches to that worksheet and doesn't complete the task.
The Sheets.Add automatically moves your selection to the newly created sheet (just like if you insert a new sheet by hand). In consequence the Offset is based on cell A1 of the new sheet which now has become your selection - you select an empty cell (as the sheet is empty) and the loop terminates.
Sub test()
Dim MyNames As Range, MyNewSheet As Range
Set MyNames = Range("R5").CurrentRegion ' load contigeous range into variable
For Each MyNewSheet In MyNames.Cells ' loop through cell children of range variable
Sheets.Add.Name = MyNewSheet.Value
Next MyNewSheet
MyNames.Worksheet.Select ' move selection to original sheet
End Sub
This will work better .... you assign the list of names to an object variable of type Range and work this off in a For Each loop. After you finish you put your Selection back to where you came from.
Sheets.Add will automatically make your new sheet the active sheet. Your best bet is to declare variables to your objects (this is always best practice) and reference them. See like I've done below:
Sub test()
Dim wks As Worksheet
Set wks = Sheets("sheet1")
With wks
Dim rng As Range
Set rng = .Range("R5")
Do Until IsEmpty(rng)
Sheets.Add.Name = rng.Value
Set rng = rng.Offset(0, 1)
Loop
End With
End Sub
Error handling should always be used when naming sheets from a list to handle
invalid characters in sheet names
sheet names that are too long
duplicate sheet names
Pls change Sheets("Title") to match the sheet name (or position) of your title sheet
The code below uses a variant array rather than a range for the sheet name for performance reasons, although turning off ScreenUpdating is likely to make the biggest difference to the user
Sub SheetAdd()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim strError As String
Dim vArr()
Dim lngCnt As Long
Dim lngCalc As Long
Set ws1 = Sheets("Title")
vArr = ws1.Range(ws1.[r5], ws1.[r5].End(xltoRight))
If UBound(vArr) = Rows.Count - 5 Then
MsgBox "sheet range for titles appears to be empty"
Exit Sub
End If
With Application
.ScreenUpdating = False
.EnableEvents = False
lngCalc = .Calculation
End With
For lngCnt = 1 To UBound(vArr)
Set ws2 = Sheets.Add
On Error Resume Next
ws2.Name = vArr(lngCnt, 1)
If Err.Number <> 0 Then strError = strError & vArr(lngCnt, 1) & vbNewLine
On Error GoTo 0
Next lngCnt
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = lngCalc
End With
If Len(strError) > 0 Then MsgBox strError, vbCritical, "These potential sheet names were invalid"
End Sub
This is probably the simplest. No error-handling, just a one-time code to create sheets
Sub test()
Workbooks("Book1").Sheets("Sheet1").Range("A1").Activate
Do Until IsEmpty(ActiveCell)
Sheets.Add.Name = ActiveCell.Value
Workbooks("Book1").Sheets("Sheet1").Select
ActiveCell.Offset(0, 1).Select
Loop
End Sub