VBA Infinite loop - When no value is enetered by the user - vba

I'm a newbie to VBA and I'm writing a VBA code that will accept user inputs for the serial numbers for the Start and End value and will check if the serial numbers in another sheet fall between the range specified. If it does, then my code will pick the complete record and paste it into another sheet.
Now, the challenging part is that the serial number value isn't consistent and it could be alpha-numeric string with no fixed length. So, I've used StrComp function to check if the value lies in the range specified by the user. The problem is if the user types in a value for start value and end value that doesn't exist in the sheet of serial numbers then it goes on an infinite loop.
For instance, if there is a serial number say 1120 and the user enters 1110(which doesn't exist) for start value and 1200 for end value, the code goes on a infinite loop. Technically, the code should pick the serial number 1120 and return since it does lie in the range 1110 and 1200 eventhough the value 1110 doesn't exist in the sheet.
Here is my code:
'Assigining values enterted by user to variables
start = Me.txtStart.Value
finish = Me.txtEnd.Value
'Checking Upper bound Vs Lower bound
If (Len(start) <> 0 And (Len(finish) <> 0)) Then
If (StrComp(start, finish) > 0) Then
MsgBox ("Lower Bound cannot be higher than the Upper Bound")
Exit Sub
Else
If Len(tempWorkPriority) = 0 Then
MsgBox ("Enter a value for Work Priority")
Exit Sub
Else
If Len(tempDescription) = 0 Then
MsgBox ("Enter a value for Description")
Exit Sub
Else
Goto Here
End If
End If
End If
Else
result = MsgBox("Please enter values for Upper and Lower bounds")
Exit Sub
End If
End Sub
Here:
Sheets("Imported e-Facilities Data").Activate
'Number of rows in Raw Data sheet
RowCount = Cells(Cells.Rows.Count, "A").End(xlUp).Row
'Loop to iterate and pick data that falls within the specified range
For i = 2 To RowCount
tempSerial = Range("A" & i).Value
tempAsset = Range("V" & i).Value
tempAssignedResource = Range("R" & i).Value
tempManufacturer = Range("F" & i).Value
'Condition to check if a Serial Number falls within the range
If (StrComp(start, tempSerial) <= 0 And StrComp(tempSerial, finish) <= 0) Then
'Selecting Export Sheet
Sheets("Data Ready for Import").Select
'Counting Rows in the Export sheet
RowCountExport = Cells(Cells.Rows.Count, "A").End(xlUp).Row
Range("A" & RowCountExport + 1).Value = tempSerial
End If
Next
End Sub
Please Help!!!

You're using unqualified calls to Range so you are relying on the active sheet being set correctly. When you find a serial number which falls within the range, you call Select on a different sheet which has the effect of changing the active sheet. The next time round your loop tempSerial, tempAsset etc will be read from the "Data Ready For Import" sheet which is now active rather than the "Imported e-Facilities Data" sheet which was being used before.
You should qualify your Range references instead of relying on the active sheet, Select or Activate:
Set wsInput = Worksheets("Imported e-Facilities Data")
tempSerial = wsInput.Range("A" & i).Value
or use With ... End With for repeated references to the same object:
Set wsInput = Worksheets("Imported e-Facilities Data")
With wsInput
tempSerial = .Range("A" & i).Value
tempAsset = .Range("V" & i).Value
tempAssignedResource = .Range("R" & i).Value
tempManufacturer = .Range("F" & i).Value
End With

Related

Vba unable to get correct value of a cell

I am trying to get the value of column I from the A workbook. I have closed all other workbooks but not able to get correct values. It is displaying me the term "Balancing" every time which is not present in the workbook A. I am not getting where that word is coming from.
Sub newapproach()
Set ws1 = Workbooks("A.xlsx").Worksheets(1)
Workbooks("A.xlsx").Activate
lastRow11 = Range("I" & Rows.Count).End(xlUp).Row
For i = lastRow11 To 1 Step -1
If i <> "" Then
Value = Workbooks("A.xlsx").Worksheets(1).Cells(i, "I").Value
MsgBox (Value)
End If
Next i
End Sub

Replacing hard value cells with subtotal formula - VBA

Essentially, our system runs off an expenditure listing of cost headings, with a subtotal on each. The issue being we adjust the data, so need to go through and manually turn the hard value subtotals into subtotal formula in each heading; which over hundreds of different headings, with variable numbers of costs, can be tedious and time consuming.
I've built a basic test example whereby for every instance of A (Heading), where the associated B has a value (an element of data from the system for a line of expenditure), the costs (C) will be subtotalled (109,...), replacing the hard copied value.
Sub insertsubtotal()
Dim cell As Range
Dim sumrange As Range
Set cell = Cells(Cells.Rows.Count, "A")
Do
Set cell = cell.End(xlUp)
Set sumrange = cell.Offset(1, 1).CurrentRegion.Offset(1, 2).Resize(cell.Offset(1, 1).CurrentRegion.Rows.Count - 1, columnsize:=1)
If sumrange.Cells.Count > 1 Then
sumrange.End(xlDown).Offset(2, 0).Formula = "=SUBTOTAL(109," & sumrange.Address & ")"
Else
sumrange.Offset(2, 0).Formula = "=SUBTOTAL(109," & sumrange.Address & ")"
End If
Loop Until cell.Row = 1
End Sub
This works whereby the first heading is in A1, and the cost data in column C as below...
However, where I'm struggling is, I need to amend the process to have the first 5 rows ignored (first heading being on 6), and the cost data and subtotal that needs replacing being in column M.
Any help would be appreciated.
Using SpecialCells to divide the UsedRange in Columns("C") into blocks of contant values, will allow you to easily identify and subtotal your data blocks.
Sub insertsubtotal()
Dim Source As Range, rArea As Range
With Worksheets("Sheet1")
On Error Resume Next
Set Source = Intersect(.UsedRange, .Columns("C")).SpecialCells(xlCellTypeConstants)
On Error GoTo 0
If Source Is Nothing Then
MsgBox "No data found", vbInformation, "Action Cancelled"
Exit Sub
End If
For Each rArea In Source.Areas
rArea.Offset(rArea.Rows.Count).Cells(2).Formula = "=SUBTOTAL(109," & rArea.Address & ")"
Next
End With
End Sub

How to pause macro, then do my stuff and continue/resume from where I left?

I got data in one sheet form B2:ZY191, and I want to copy each row (B2:ZY2,B3:ZY3, and so on till B191:ZY191) to another workbook worksheet for analysis. Now while doing so I sometimes need to stop and mark my results in between and then continue from where I left. For example, I started the macro and it copied from B2:ZY2 to B52:ZY52 then I pause the macro & mark my results. Now I want to continue from B52:ZY52 onwards then again if I want to stop after copying data till B95:ZY95 I should be able to pause the macro, mark my result and continue from B95:ZY95 thereon. I should be able to do this as many times as I want.
If provided with buttons like start, pause and resume would be very helpful.
you could adopt the following workaround:
choose the "sets" you want to virtually divide your data range into
let's say:
set#1 = rows 1 to 20
set#2 = rows 21 to 30
... and so on
mark with any character in column "A" the final rows of all chosen sets
so you'd put a "1" (or any other character other than "|I|" or "|E|" - see below) in the following cells of column "A" (i.e. the one preceding your data range):
A21
A31
..., and so on
(since your data starts at row 2 then its ith row is in worksheet row I+1)
then you put the following code in any module of your data range workbook:
Option Explicit
Sub DoThings()
Dim dataRng As Range, rngToCopy As Range
'assuming Analysis.xlsx is already open
Set dataRng = Worksheets("BZ").Range("B2:ZY191") '<--| this is the whole data range. you can change it (both worksheet name and range address) but be sure to have a free column preceeding it
Set rngToCopy = GetCurrentRange(dataRng) '<--| try and set the next "set" range to copy
If rngToCopy Is Nothing Then '<--| if no "set" range has been found...inform the user and exit sub!
MsgBox "There's an '|E|' at cell " _
& vbCrLf & vbCrLf & vbTab & dataRng(dataRng.Rows.Count, 1).Offset(, -1).Address _
& vbCrLf & vbCrLf & " marking data has already been entirely copied" _
& vbCrLf & vbCrLf & vbCrLf & "Remove it if you want to start anew", vbInformation
Exit Sub
End If
With rngToCopy
Workbooks("Analysis").Worksheets("Sheet1").Range(.Address).value = .value
End With
End Sub
Function GetCurrentRange(dataRng As Range) As Range
Dim f As Range
Dim iniRow As Long, endRow As Long
With dataRng
With .Offset(, -1)
Set f = .Resize(, 1).Find(what:="|E|", lookat:=xlWhole, LookIn:=xlValues) '<--| look for the "all copied" mark ("|E|")
If Not f Is Nothing Then Exit Function '<--| if "all copied" mark was there then exit function
Set f = .Resize(, 1).Find(what:="|I|", lookat:=xlWhole, LookIn:=xlValues) '<--| look for any "initial" mark put by a preceeding sub run
If f Is Nothing Then '<--|if there was no "initial" mark ...
iniRow = 1 '<--| ...then assume first row as initial one
Else
iniRow = f.row - .Cells(1).row + 1 '<--| ... otherwise assume "marked" row as initial one
f.ClearContents '<--| and clear it not to found it the next time
End If
endRow = .Cells(iniRow, 1).End(xlDown).row - .Cells(1).row + 1 '<--| set the last row as the next one with any making in column "A"
If endRow >= .Rows.Count Then '<--| if no mark has been found...
endRow = .Rows.Count '<--| ...set the last row as data last row...
.Cells(endRow, 1).value = "|E|" '<--|... and put the "all copied" mark in it
Else
.Cells(endRow, 1).ClearContents '<--| ...otherwise clear it...
.Cells(endRow + 1, 1).value = "|I|" '<--| ... and mark the next one as initial for a subsequent run
End If
End With
Set GetCurrentRange = .Rows(iniRow).Resize(endRow - iniRow + 1) '<--| finally, set the range to be copied
End With
End Function
and make it run as many times as you need: after each time it ends and you can mark your result and then make it run again and it'll restart form where it left
you can use Stop and Debug.Print to achieve the desired results when placed within your code. For example if you're looping through a range, add the statement of choice with an if statement:
for a = 1 to 150
if a = 20 or a = 40 then
debug.Print "The value of a is: " & a.value 'or whatever you want to see
end if
next
This will print to the immediates window, or use stop to pause your code in a strategic place in the same manner.
I dont understand what you mean by buttons? They surely aren't a good idea as the code will run too fast?

VBA Named Range most efficient way to check if name exists

I have a routine, that fills a calendar with all important events for the commodity markets for each day of the following week. I have a calendar grid laid out on the page and have ten named cells for each day i.e. Monday1, Monday2 and so on (each day only goes up to 10 for now, i.e.Monday10), in each days column. BTW the cells are 2 cells wide and 2 cells deep. Many times there are more than 10 events for a given day. I am trying to test for the named range to see if it exists, if not copy the format of the last named range cell and name that cell the next name in the series.
I am only having two issues with the above, first and foremost is how to test to determine in a name for a named range already exists. I am currently iterating thru the entire list of ThisWorkbook.Names, which has thousands of named ranges in it. Since this iteration could be running over 100 times when the calendar is generating, it is wicked slow (as would be expected). Is there a better, faster way to check if a name already exists as a named range?
The second issue is how to copy the formatting of a 4 cell, merged cell, since the address always comes up as only the top left corner cell so offsetting the range doesn't work appropriately. I hacked around to get this code to at least come up with the right range for the next merged cell group in the column
Set cCell = Range("Thursday" & CStr(y))
'even tho cCell is a 4 cell merged cell, cCell.Address returns the address of top left cell
Set destRange = Range(cCell.Address & ":" & cCell.offset(2, 0).offset(0, 1).Address)
Recording a macro to drag the formatting down, shows this code.
Range("G22:H23").Select
Selection.AutoFill Destination:=Range("G22:H25"), Type:=xlFillFormats
Range("G22:H25").Select
Since Range("G22:H23") is the same as cCell, and Range("G22:H25") is the same as destRange. The following code should work, but doesn't.
Set cCell = Range("Thursday" & CStr(y))
Set destRange = Range(cCell.Address & ":" & cCell.offset(2, 0).offset(0, 1).Address)
cCell.AutoFill Destination:=destRange, Type:=xlFillFormats
Application.CutCopyMode = False
cCell.offset(1, 0).Name = rangeName
FYI, it doesn't work if I select cCell and use Selection.AutoFill either.
Any thoughts on how to copy that cell formatting down the column one cell at a time when needed?
Update:
This now works for copying the formatting down from one merged cell to another of same size. For some reason setting destRange to the whole range (the copy cell and pastecell entire range as the macro recorder showed) didnt work but setting destRange to the cell range that needed formatting, and then doing a union of cCell and destRange worked, and made naming the new range easier.
rangeName = "Friday" & CStr(y + 1)
priorRangeName = "Friday" & CStr(y)
namedRangeExist = CheckForNamedRange(rangeName)
If namedRangeExist = False Then
Set cCell = Range(priorRangeName)
Set destRange = Range(cCell.offset(1, 0).Address & ":" & cCell.offset(2, 0).offset(0, 1).Address)
cCell.AutoFill Destination:=Union(cCell, destRange), Type:=xlFillFormats
Application.CutCopyMode = False
destRange.Name = rangeName
End If
Update #2
There is an issue with naming ranges in a For loop ( the code below is running inside a For loop). The first time the new rangeName is not found, Setting cCell to the prior range name and running through the code to copy the merged cell format and name the new range works fine. Here is the code
rangeName = "Thursday" & CStr(y + 1)
priorRangeName = "Thursday" & CStr(y)
namedRangeExist = DoesNamedRangeExist(rangeName)
If namedRangeExist = False Then
Set cCell = Range(priorRangeName)
Debug.Print "cCell:" & cCell.Address
Set cCell = cCell.MergeArea
Debug.Print "Merged cCell:" & cCell.Address
Set destRange = Range(cCell.offset(1, 0).Address & ":" & cCell.offset(2, 0).offset(0, 1).Address)
Debug.Print "Dest:" & destRange.Address
Debug.Print "Unioned:" & Union(cCell, destRange).Address
cCell.AutoFill Destination:=Union(cCell, destRange), Type:=xlFillFormats
Application.CutCopyMode = False
destRange.name = rangename
End If
results in the following ranges
cCell:$G$22
Merged cCell:$G$22:$H$23
Dest:$G$24:$H$25
Unioned:$G$22:$H$25
but if more than one new named range needs to be created the second time thru this code produces a range area as evidenced by the output shown below
cCell:$G$24:$H$25
so why does cCell's address show as only the upper left cells address when run the first time, but the second time thru cCell's address is shown as the whole merged cell range? And because it does, the next code line produces a range object error
Set cCell = cCell.MergeArea
Eliminating that code line and amending the first Set cCell to this;
Set cCell = Range(priorRangeName).MergeArea
produces the same error. I could kludge this by setting a counter, and if more than one, bypass that code line but that is not the preferred solution.
First and foremost, create a function to call the named range. If calling the named range generate an error the function will return False otherwise it will return True.
Function NameExist(StringName As String) As Boolean
Dim errTest As String
On Error Resume Next
errTest = ThisWorkbook.Names(StringName).Value
NameExist = CBool(Err.Number = 0)
On Error GoTo 0
End Function
As for your second question, I had not problem with the autofill.
I would replce Set destRange = Range(cCell.Address & ":" & cCell.offset(2, 0).offset(0, 1).Address) with Set destRange = cCell.Resize(2,1). It has the same effect but the later is much cleaner.
Application.Evaluate and Worksheet.Evaluate can be used to get error value instead of error :
If Not IsError(Evaluate("Monday1")) Then ' if name Monday1 exists
The error can be ignored or jumped over (but that can result in hard to detect errors) :
On Error GoTo label1
' code that can result in error here
label1:
If Err.Number <> 0 Then Debug.Print Err.Description ' optional if you want to check the error
On Error GoTo 0 ' to reset the error handling
Range.MergeArea can be used to get the Range of merged cell.
I created a function to extend the name ranges and fill in the formatting. The first named range in the series will have to be setup. The Name itself needs to be set to the top left cell in the merged area.
ExtendFillNamedRanges will calculate the positions of the named ranges. If a cell in one of the positions isn't part of a MergedArea it will fill the formatting down from the last named range. It will name that cell. The scope of the names is Workbook.
Sub ExtendFillNamedRanges(BaseName As String, MaxCount As Integer)
Dim x As Integer, RowCount As Integer, ColumnCount As Integer
Dim LastNamedRange As Range, NamedRange As Range
Set NamedRange = Range(BaseName & 1)
RowCount = NamedRange.MergeArea.Rows.Count
ColumnCount = NamedRange.MergeArea.Columns.Count
For x = 2 To MaxCount
Set NamedRange = NamedRange.Offset(RowCount - 1)
If Not NamedRange.MergeCells Then
Set LastNamedRange = Range(BaseName & x - 1).MergeArea
LastNamedRange.AutoFill Destination:=LastNamedRange.Resize(RowCount * 2, ColumnCount), Type:=xlFillDefault
NamedRange.Name = BaseName & x
End If
'NamedRange.Value = NamedRange.Name.Name
Next
End Sub
Here is the test that I ran.
Sub Test()
Application.ScreenUpdating = False
Dim i As Integer, DayName As String
For i = 1 To 7
DayName = WeekDayName(i)
Range(DayName & 1).Value = DayName & 1
ExtendFillNamedRanges DayName, 10
Next i
Application.ScreenUpdating = True
End Sub
Before:
After:
I found this on ozgrid and made a little function out of it:
Option Explicit
Function DoesNamedRangeExist(VarS_Name As String) As Boolean
Dim NameRng As Name
For Each NameRng In ActiveWorkbook.Names
If NameRng.Name = VarS_Name Then
DoesNamedRangeExist = True
Exit Function
End If
Next NameRng
DoesNamedRangeExist = False
End Function
You can put this line in your code to check:
DoesNamedRangeExist("Monday1")
It will return a Boolean value (True / False) so it's easy to use with an IF() statement
As to your question on merged cells, I did a quick macro record on a 2*2 merged cell and it gave me this (made smaller and added comments):
Sub Macro1()
Range("D2:E3").Copy 'Orignal Merged Cell
Range("G2").PasteSpecial xlPasteAll 'Top left of destination
End Sub

Vlookup doesn't seem to cooperate

I have the following Code for bank reconciliation which involves checking each cell in column D of sheet1 (bank statements) and see if it exists in column M of Sheet 2. If it doesn't flag it by saving it to arrOutput.
Being a new user, and because I could not attach the spreadsheet, I have links to what Sheet 1 and 2 look like.
Sheet1
Sheet2
Sub abc_3()
Dim i As Long, ii
Dim arrBank As Range
Dim arrAccounting As Range
Dim arrOutput
Dim temp As Variant
' setting bank transaction into range
Set bank = ActiveWorkbook.Sheets("Sheet1").Range("D25:E25" & Cells(Rows.Count, "D").End(xlUp).Row)
' setting accounting transactions into range
Set books = ActiveWorkbook.Sheets("Sheet2").Range("M1:N1" & Cells(Rows.Count, "M").End(xlUp).Row)
'everytime time the program is run arrOutput must be cleared. 3000 is an arbitrary number I chose because there will likely never be a higher number of transactions than this.
ReDim arrOutput(1 To 3000, 1 To 2)
ii = 0
' The main function of the program.. looping through every bank transaction checking if it can be found in accounting transactions,
' if it cannot be found, i.e error is thrown then save the cell to arrOutput because it needs to be flagged for checking.
' if it can be found, then ignore and check next bank transaction.
' Currently, the procedure is supposed to compare only Sheet1 credit transactions with Sheet2 credit transactions, therefore filter only credit transactions.
For Each cell In bank.Cells 'problem here is comparing both Column D and E of Sheet 1 whereas it should be comparing only column D.
If cell <> "" Then 'this is to avoid checking non-credit transactions.
On Error Resume Next
temp = Application.WorksheetFunction.VLookup(cell, books, 2, False)
If Err.Number <> 0 Then
MsgBox "Bank Transaction " & cell & " could not be found in Books Transaction history"
arrOutput(ii, 1) = cell
arrOutput(ii, 2) = ""
ii = ii + 1
End If
End If
Next
'all cells checked then dump arrOutput to range "L4" for reading
Range("l4").Resize(3000, 2) = arrOutput
bank.ClearContents
books.ClearContents
End Sub
The problem is that on every cell I get MSG "Bank Transaction " & cell & " could not be found in Books Transaction history". Consequently, Every cell gets saved to arrOutput and saved to Sheets("Sheet3").Range("L4") making me wonder whether Vlookup is not cooperating or I didn't setup the error handler correctly.
Looking forward to getting some help.. been stuck on this for too long. Thank you in advance.
1) you should qualify the ranges. 2) :E25 should be :D and :N1 should be :M. 3) Use Option Explicit and use the variables you declared (you declare some variable names but then you use other names...). 4) Finally, use Find instead of VLookup, since you only want to check the existence of the value, not a corresponding other value.
Option Explicit
Sub abc_3()
Dim bank As Range, books As Range, cell As Range
With ActiveWorkbook.Sheets("Sheet1")
Set bank = .Range("D26:D" & .Cells(.Rows.Count, "D").End(xlUp).Row)
End With
With ActiveWorkbook.Sheets("Sheet2")
Set books = .Range("M2:M" & .Cells(.Rows.Count, "M").End(xlUp).Row)
End With
Dim ii As Long, x As Range, arrOutput(1 To 3000, 1 To 2)
For Each cell In bank.Cells
If Trim(cell.Value) <> "" Then
Set x = books.Find(cell.Value, , xlValues, xlWhole)
If x Is Nothing Then
ii = ii + 1
arrOutput(ii, 1) = cell.Value
MsgBox "Bank Transaction " & cell.Value & " could not be found in Books Transaction history"
Else
x.Value = ""
End If
End If
Next
ActiveWorkbook.Sheets("Sheet3").Range("l4").Resize(3000, 2) = arrOutput
End Sub