How to end the loop is the variable is empty - excel

Sub max()
Sheets(1).Select
Sheets(1).Name = "Sheet1"
Dim rng As Range
Dim celladdress As String
Dim celling As Variant
Do Until IsEmpty(celling)
If celling > "G4" Then
Set rng = Range("G3:G1000").Find(what:="Description")
rng.Find what:="Description"
celladdress = rng.Address(-1)
celling = celladdress
Else: Call Source
End If
Loop
MsgBox "done"
End Sub
Hi im trying to find the word description in my range, if description is foudn then it should run the macro and then loop. but if the variable is empty and the variable description is not found i want the loop to end and display the msgbox. I have tried to end the loop using loop until the celling is empty but it doesnt seem to work. The variable celling is quoting as empty so im unsure why this is not working. Any help would be greatly appreicated thanks max

Max, this is worth posting as a new answer to highlight the unintuitive behaviour of FindNext. This works - better candidate for accepted answer than that above. May be a bit pedantic, as in a more elegant solution is possbile:
Sub max()
Sheets(1).Select
Sheets(1).Name = "Sheet1"
Dim rng As Range
Set rng = Range("G3:G1000")
Dim celladdress As String
Dim celladdressPrevious As String
Dim celling As Range
Set celling = rng.Find(what:="Description")
If celling Is Nothing Then
MsgBox "Not found, exiting"
Exit Sub
End If
Do
'Set celling = range.FindNext 'Keeps returning first range found! Maybe "With" block on rng will work.
If celling.Row > 4 Then
'celling.Activate
celladdress = celling.Offset(-1, 0).Address
If celladdress = celladdressPrevious Then GoTo WereDone
celladdressPrevious = celladdress
MsgBox celladdress
'Else: Call Source 'What is Source? Not this sub, is it?
End If
If celling.Row = 1000 Then Exit Sub
Set rng = Range("G" & celling.Row & ":G1000")
Set celling = rng.Find(what:="Description")
Loop Until celling Is Nothing
WereDone:
MsgBox "done"
End Sub

'Max, guessing a little at your intent - May need your help there. Does this get you closer? I don't think I can do better on a GNU/Linux box.
Sub max()
Sheets(1).Select
Sheets(1).Name = "Sheet1"
Dim rng As Range
Set rng = Range("G3:G1000")
Dim celladdress As String
Dim celling As Range
Set celling = rng.Find(what:="Description")
If celling Is Nothing Then
MsgBox "Not found, exiting"
Exit Sub
End If
Do
'Set celling = range.FindNext 'Keeps returning first range found! Maybe "With" block on rng will work.
If celling.Row > 4 Then
'celling.Activate
celladdress = celling.Offset(-1, 0).Address
MsgBox celladdress
'Else: Call Source 'What is Source? Not this sub, is it?
End If
Set celling = range.FindNext
Loop Until celling Is Nothing
MsgBox "done"
End Sub

Related

modification for multiple string search

My code searches for custom text and highlights the rows containg the user specified text/ search string.
How do I modify it so it searches for multiple strings?
Dim Rng As Range
Dim myCell As Object
Dim myUnion As Range
Set Rng = Selection
searchString = InputBox("Please Enter the Search String")
For Each myCell In Rng
If InStr(myCell.Text, searchString) Then
If Not myUnion Is Nothing Then
Set myUnion = Union(myUnion, myCell.EntireRow)
Else
Set myUnion = myCell.EntireRow
End If
End If
Next
If myUnion Is Nothing Then
MsgBox "The text was not found in the selection"
Else
myUnion.Select
End If
End Sub
Please, try the next adapted code. You should place the strings to be searched separated by comma:
Sub searchStringS()
Dim Rng As Range, myUnion As Range, searchString As String
Dim myCell As Range, arrSrc, El
Set Rng = Selection
If TypeName(Rng) <> "Range" Then MsgBox "You must select a range...", vbCritical, "Wrong selection": Exit Sub
searchString = InputBox("Please Enter the Search Strings, separated by comma!")
searchString = Replace(searchString, ", ", ",") 'to eliminate eventual ", " instead of only ","...
arrSrc = Split(searchString, ",")
For Each El In arrSrc
For Each myCell In Rng
If InStr(myCell.Text, El) Then
If Not myUnion Is Nothing Then
Set myUnion = Union(myUnion, myCell.EntireRow)
Else
Set myUnion = myCell.EntireRow
End If
End If
Next
Next El
If myUnion Is Nothing Then
MsgBox "The text was not found in the selection"
Else
myUnion.Select
End If
End Sub
Another way to do this, using ParamArray:
Public Sub fnTestSearch()
Call fnSearchMultipleStrings("01", "2022", "A", "B", "V")
End Sub
Public Sub fnSearchMultipleStrings(ParamArray aArgumentsArray() As Variant)
Dim vArg As Variant
Dim rngMyCell As Excel.Range
Dim rngMyUnion As Excel.Range
For Each vArg In aArgumentsArray
For Each rngMyCell In Selection
If InStr(rngMyCell.Text, vArg) Then
If Not rngMyUnion Is Nothing Then
Set rngMyUnion = Union(rngMyUnion, rngMyCell.EntireRow)
Else
Set rngMyUnion = rngMyCell.EntireRow
End If
End If
Next
Next vArg
If rngMyUnion Is Nothing Then
MsgBox "The text was not found in the selection"
Else
rngMyUnion.Select
End If
End Sub

VBA subscript out of range and Excel's default variables

This is my first post on here and I have very little formal training in coding, so this is probably a very easy problem.
I'm running into an error 9, VBA Subscript out of range, when running macros defined by the code below.
Specifically, it is the Sub Select_Last() function. Excel does not like the subsequent expression, however if this is used on its own in a separate Excel file then it works fine.
I think the problem is that Excel's default variable (Activesheet etc) are conflicting with each other. But I am not sure how to remedy this. The other subs work fine. Can anyone help? Thank you.
Public lastsheet As String
Sub Select_Last()
Sheets(lastsheet).Select
End Sub
Sub Protect()
For i = 1 To Sheets.Count
Sheets(i).Protect
Next i
End Sub
Sub UnProtect()
For i = 1 To Sheets.Count
Sheets(i).UnProtect
Next i
End Sub
Sub SelectUnlockedCells()
Dim WorkRng As Range
Dim OutRng As Range
Dim Rng As Range
On Error Resume Next
Set WorkRng = Application.ActiveSheet.UsedRange
Application.ScreenUpdating = False
For Each Rng In WorkRng
If Rng.Locked = False Then
If OutRng.Count = 0 Then
Set OutRng = Rng
Else
Set OutRng = Union(OutRng, Rng)
End If
End If
Next
If OutRng.Count > 0 Then OutRng.Select
Application.ScreenUpdating = True
End Sub
The other functions work OK.
Consider:
Public lastsheet As String
Sub Select_Last()
lastsheet = Sheets(Sheets.Count).Name
Sheets(lastsheet).Select
End Sub
The key issue is to assign a value to a variable before using it.

VBA user defined range and check if it is empty

I have been struggling with this for over an hour. I need to write a VBA code where the user selects a range and then I check if this selected range is empty before I go and do anything else.
This is what I have so far:
Sub test()
Set rng= Application.InputBox("Select the range of the raw data please", Type:=8)
If Application.WorksheetFunction.CountA(Range(rng)) > 0 Then
MsgBox "do this, this and that!"
End If
End Sub
When I run this I get a "Method Range of object_Global failed". I know it lets the user select the range just fine but the Range(rng) is not working right. Any help would be appreciated!
Your problem is that your variable rng is a range and you're trying to wrap that in a range, which is why it's throwing an error. Try this instead.
Sub test()
Dim rng As Range
Set rng = Application.InputBox("Select the range of the raw data please", Type:=8)
If Application.WorksheetFunction.CountA(rng) > 0 Then
MsgBox "do this, this and that!"
End If
End Sub
Just some code
Sub main()
Dim sentRange As Range
Set sentRange = Application.InputBox("Select the range of the raw data please", Type:=8)
If isRangeEmpty(sentRange) = False Then
MsgBox "Range is not empty."
Else
MsgBox "Good to go!"
End If
End Sub
Function isRangeEmpty(ByRef myRange As Range) As Boolean
Dim rngLoop As Range
Dim rangeEmpty As Boolean
For Each rngLoop In myRange
If rngLoop.Value = Empty Then
'All Good
isRangeEmpty = True
Else
'Need to exit
isRangeEmpty = False
Exit For
End If
Next rngLoop
End Function
If you are only acting on the instance of data being present then something like below will work. I would also adding Option Explicit to the top of your code and declare all variables.
Sub How()
Dim rng As Range
Set rng = Application.InputBox("Select Target Range", Type:=8)
If Application.WorksheetFunction.CountA(rng) <> 0 Then
'Actions
End If
End Sub

How to Recreate a Sheet and Keep References Valid?

I have a client who is hand holding a bunch of worksheets that should be standardized. They are created from importing CSV files. Basically, I need to replace the current manual sheets while they are being referenced from another tab without breaking the current references.
I've reduced the problem to a single workbook with 2 sheets. Sheet1 cell A1 references Sheet2 cell A1 which holds the string "Sheet2A1CellData"
Everything commented out below has been tried including Application.Volatile and Application.Calculation.
Option Explicit
Sub TestSheet2Delete()
Dim TmpSheet2 As Worksheet: Set TmpSheet2 = Sheets("Sheet2")
'Application.Volatile
If TmpSheet2 Is Nothing Then
Exit Sub
End If
'Application.Calculation = False
Application.DisplayAlerts = False
TmpSheet2.Delete
Application.DisplayAlerts = True
Set TmpSheet2 = Worksheets.Add(After:=Sheets("Sheet1"))
If TmpSheet2 Is Nothing Then
Exit Sub
End If
TmpSheet2.Name = "Sheet2"
TmpSheet2.Range("A1").Value = "Sheet2A1CellData"
'Application.Calculation = True
End Sub
Sheet1 A1 was originally =Sheet2!A1. When I run the function above from the VBE, Sheet1 cell A1 is set to =#REF!A1.
How can I keep the reference valid after the sheet has been replaced?
Obviously, the real world problem is much larger and re-importing CSV data requires updating 132,000 cells. 6000 rows x 22 Columns.
Thanks for any help.
Thank you presenting a real good question.
First of all disclaimer: This is not an direct solution but and workaround we had to adopt years back.
Exactly similar problem problem had been encountered in my workplace (literally made us to pull out our hairs), and we also tried to go for iNDIRECT. But since the formulas in the working sheets are complex we failed to replace them with INDIRECT. So instead of lengthy manual replacement of the hundreds of Formulas in the working sheet, we used to insert a temp Sheet and change the formulas reference to that sheet. After importing new sheet and renaming it as old sheet's name, formulas were reverted back to original.
I tried to reproduce the code used (since I don't have access to same files now). We only used the Sub ChangeFormulas, Here I used the same in line with your code.
Option Explicit
Sub TestSheet2Delete()
Dim Wb As Workbook
Dim Ws As Worksheet, Ws1 As Worksheet, Ws2 As Worksheet
Dim Xstr As String, Ystr As String
Set Wb = ThisWorkbook
Set Ws = Wb.Sheets("Sheet1")
Xstr = "Sheet2"
Ystr = "TempSheetX"
Set Ws1 = Wb.Sheets(Xstr)
Set Ws2 = Worksheets.Add(After:=Ws)
Ws2.Name = Ystr
DoEvents
ChangeFormulas Ws, Xstr, Ystr
Application.DisplayAlerts = False
Ws1.Delete
' Now again add another sheet with Old name and change formulas back to Original
Set Ws1 = Worksheets.Add(After:=Ws)
Ws1.Name = Xstr
DoEvents
ChangeFormulas Ws, Ystr, Xstr
Ws2.Delete
Application.DisplayAlerts = True
End Sub
Sub ChangeFormulas(Ws As Worksheet, Xstr As String, Ystr As String)
Dim Rng As Range, C As Range, FirstAddress As String
Set Rng = Ws.UsedRange
With Rng
Set C = .Find(What:=Xstr, LookIn:=xlFormulas)
If Not C Is Nothing Then
FirstAddress = C.Address
Do
C.Formula = Replace(C.Formula, Xstr, Ystr)
Set C = .FindNext(C)
If C Is Nothing Then Exit Do
If C.Address = FirstAddress Then Exit Do
Loop
End If
End With
End Sub
Another simplest workaround is not to delete the Sheet at all and import the CSV and copy the full sheet onto the sheet in question. However This fully depends on actual working conditions involving CSV and all.
AFTER I posted (of course :-)), this link came up on the right: Preserve references that recommends using INDIRECT. I have now changed Sheet1 A1 to =INDIRECT("Sheet2!"&"A1").
I am not certain why the named ranges suggested in the link are needed. The indirect call above seems to work without a named range.
If this works in the larger project, I will mark this as complete.
My original answer did not work for non-contiguous cells. However, I really like the Range to Variants and then back to Range pattern. Very fast. So I rewrote my original answer into more reusable code that tests using non-contiguous cells.
Function PreserveFormulaeInVariantArr(ByVal aWorksheet As Worksheet, _
ByVal aIsNoFormulaErr As Boolean, _
ByRef aErrStr As String) As Variant
Dim TmpRange As Range
Dim TmpAreaCnt As Long
Dim TmpVarArr As Variant
Dim TmpAreaVarArr As Variant
PreserveFormulaeInVariantArr = Empty
If aWorksheet Is Nothing Then
aErrStr = "PreserveFormulaeInVariantArr: Worksheet is Nothing."
Exit Function
End If
Err.Clear
On Error Resume Next
Set TmpRange = aWorksheet.Cells.SpecialCells(xlCellTypeFormulas)
If Err.Number <> 0 Then 'No Formulae.
PreserveFormulaeInVariantArr = Empty
If aIsNoFormulaErr Then
aErrStr = "PreserveFormulaeInVariantArr: No cells with formulae."
End If
Exit Function
End If
TmpAreaVarArr = Empty
On Error GoTo ErrLabel
ReDim TmpVarArr(1 To TmpRange.Areas.Count, 1 To 2)
For TmpAreaCnt = LBound(TmpVarArr) To UBound(TmpVarArr)
TmpVarArr(TmpAreaCnt, 1) = TmpRange.Areas(TmpAreaCnt).Address 'Set 1st Element to Range
TmpAreaVarArr = TmpRange.Areas(TmpAreaCnt).Formula 'Left TmpArrVarArr for Debugging
TmpVarArr(TmpAreaCnt, 2) = TmpAreaVarArr 'Creates Jagged Array
Next TmpAreaCnt
PreserveFormulaeInVariantArr = TmpVarArr
Exit Function
ErrLabel:
aErrStr = "PreserveFormulaeInVariantArr - Error Number: " + CStr(Err.Number) + " Error Description: " + Err.Description
End Function
Function RestoreFormulaeFromVariantArr(ByVal aWorksheet As Worksheet, _
ByVal aIsEmptyAreaVarArrError As Boolean, _
ByVal aAreaVarArr As Variant, _
ByRef aErrStr As String) As Boolean
Dim TmpVarArrCnt As Long
Dim TmpRange As Range
Dim TmpDim1Var As Variant
Dim TmpDim2Var As Variant
Dim TmpDim2Cnt As Long
Dim TmpDim2UBound As Long
RestoreFormulaeFromVariantArr = False
On Error GoTo ErrLabel
If aWorksheet Is Nothing Then
Exit Function
End If
If IsEmpty(aAreaVarArr) Then
If aIsEmptyAreaVarArrError Then
aErrStr = "RestoreFormulaeFromVariantArr: Empty array passed."
Else
RestoreFormulaeFromVariantArr = True
End If
Exit Function
End If
For TmpVarArrCnt = 1 To UBound(aAreaVarArr)
TmpDim1Var = aAreaVarArr(TmpVarArrCnt, 1) 'This is always the range.
TmpDim2Var = aAreaVarArr(TmpVarArrCnt, 2) 'This can be a Variant or Variant Array
aWorksheet.Range(TmpDim1Var).Formula = TmpDim2Var
Next TmpVarArrCnt
RestoreFormulaeFromVariantArr = True
Exit Function
ErrLabel:
aErrStr = "PreserveFormulaeInVariantArr - Error Number: " + CStr(Err.Number) + " Error Description: " + Err.Description
End Function
Sub TestPreserveFormulaeInVariantArr()
Dim TmpPreserveFormulaeArr As Variant
Dim TmpErrStr As String
Dim TmpIsNoFormulaErr As Boolean: TmpIsNoFormulaErr = True 'Change If Desired
Dim TmpEmptySheet1 As Boolean: TmpEmptySheet1 = False 'Change If Desired
Dim TmpSheet1 As Worksheet: Set TmpSheet1 = Sheets("Sheet1")
Dim TmpSheet2 As Worksheet
Err.Clear
On Error Resume Next
Set TmpSheet2 = Sheets("Sheet2")
On Error GoTo 0
'Always Delete Sheet2
If (TmpSheet2 Is Nothing) = False Then
Application.DisplayAlerts = False
TmpSheet2.Delete
Application.DisplayAlerts = True
Set TmpSheet2 = Nothing
End If
If TmpSheet2 Is Nothing Then
Set TmpSheet2 = Worksheets.Add(After:=Sheets("Sheet1"))
TmpSheet2.Name = "Sheet2"
TmpSheet2.Range("A1") = "Sheet2A1"
TmpSheet2.Range("B1") = "Sheet2A1"
TmpSheet2.Range("C4") = "Sheet2C4"
If TmpEmptySheet1 Then
TmpSheet1.Cells.ClearContents
Else
TmpSheet1.Range("A1").Formula = "=Sheet2!A1"
TmpSheet1.Range("B1").Formula = "=Sheet2!B1"
TmpSheet1.Range("C4").Formula = "=Sheet2!C4"
End If
End If
TmpPreserveFormulaeArr = PreserveFormulaeInVariantArr(TmpSheet1, TmpIsNoFormulaErr, TmpErrStr)
If TmpErrStr <> "" Then
MsgBox TmpErrStr
Exit Sub
End If
'Break Formulae and Cause #Ref Violation
Application.DisplayAlerts = False
TmpSheet2.Delete
Application.DisplayAlerts = True
'Add Sheet2 Back
Set TmpSheet2 = Worksheets.Add(After:=Sheets("Sheet1"))
TmpSheet2.Name = "Sheet2"
TmpSheet2.Range("A1") = "Sheet2A1"
TmpSheet2.Range("B1") = "Sheet2A1"
TmpSheet2.Range("C4") = "Sheet2C4"
'Restore Formulas Back to Sheet1
If RestoreFormulaeFromVariantArr(TmpSheet1, TmpIsNoFormulaErr, TmpPreserveFormulaeArr, TmpErrStr) = False Then
MsgBox TmpErrStr
Exit Sub
End If
End Sub
The TestPreserveFormulaeInVariantArr can be run in the VBE with options to set empty values. Any comments appreciated.

Copying and renaming a template worksheet depending on a range of cell values on a loop

I have a template sheet that I have set up named "Template".
I have a range of cells on another worksheet called "Formulation" that I would like it to look through the range "G7:W7" and create a copy of the "Template" and rename it accordingly.
I have adapted a piece of code I have found but I keep encountering a run time error 13 - type mismatch.
Here is the code:
`Sub CopyInfoSheetandInsert()
'
' CopyInfoSheetandInsert Macro
'
Dim rcell As Range
Dim Background As Worksheet
Set Background = Sheets("Formulation")
For Each rcell In Range("D7:W7")
If rcell.Value <> "" Then
Sheets("Template").Copy Before:=Sheets("COSHH")
Sheets("Template (2)").Name = rcell.Value
End If
Next rcell
End Sub
Any advice would be greatly appreciated!
UPDATE
By moving the macro button to the formulation page the copy function now works however, on the following line of code I now get a subscript out of range error?
Sheets("Template(2)").Name = rcell.Value
Kind Regards,
Aidan
You need something like:
Sub CopyInfoSheetandInsert()
Dim rcell As Range
Dim Background As Worksheet
Set Background = Sheets("Formulation")
For Each rcell In Range("D7:W7")
If rcell.Value <> "" And SheetExists(rcell.Value) = False Then
Sheets("Template").Copy Before:=Sheets("COSHH")
Sheets(Sheets("COSHH").Index - 1).Name = rcell.Value
End If
Next rcell
End Sub
Function SheetExists(SheetName) As Boolean
Dim sht As Worksheet
'Assume Failure
SheetExists = False
For Each sht In ActiveWorkbook.Sheets
If sht.Name = SheetName Then
'Success
SheetExists = True
Exit Function
End If
Next sht
End Function

Resources