Multiple Range.Find() in VBA - excel

I have met this interesting problem today. I have a loop inside another loop and both use Find for different purposes. What happens is that using Find in the inside loop screws up the Find on the outer loop. I'm guessing excel keeps memory of only one search instance. Is there some way to work around this or is this a design matter ?
Here's some shortened version of my code.
Sub Main()
'Some boring stuff
Set lst_rapports = Worksheets("mappingTRANSIT").range("lst_rapports")
Set first_result = lst_rapports.Find(rap_choisi)
Set active_result = first_result
Sheets("req01").Unprotect "shoobidoowap"
If Not first_result Is Nothing Then
' ...
Do
Sheets("req01").Select
' ...
For i = 0 To 4
Set rubrique_cell = range("E:E").Find(rub(i))
If Not rubrique_cell Is Nothing Then
' ...
End If
Next i
' Yet more boring stuff...
Set active_result = lst_rapports.FindNext(active_result)
Loop Until active_result.Address = first_result.Address
Else
MsgBox "Impossible de trouver """ & rap_choisi & """ !"
End If
Sheets("req01").Protect "shoobidoowap"
End Sub
Notice the second use of .Find in the for loop.
Is there some way I can preserve the first search in some kind of temporary variable and restore it back after that ?
Many thanks.

When you run FindNext(MSDN for FindNext), it automatically uses the same what as the last call on Find, even if used for a different range.
To correct for this, instead of using
Set active_result = lst_rapports.FindNext(active_result)
use
Set active_result = lst_rapports.Find(rap_choisi,active_result)

Related

Use String text as Code in Visual Basic for Excel

For various reasons, I need to concatenate a text of the form [NAME].Value by changing the value of NAME to an inputbox entry.
Something like this:
Sub example()
data_in = InputBox("Give me something: ")
mystring1 = "[" & data_in & "].Value"
a = Evaluate(mystring1) 'I know this is wrong, but I don't know how to do so.
End Sub
I know it can be done in other ways, and the example in which I want to use this code is not exactly this one, and while it can be done in several ways here, in the original code it can only be done this way.
I want, based on the input in the imputbox, to concatenate the string in whatever way, and subsequently cast that string as code to store the value in another variable, to be used later in the code.
I am not able to get VBA to read the string text as code. I have seen that there is a way that consists of creating a macro from this first macro, execute it, and then delete the recently created macro. The problem with this solution is that doing that I can't save the variable when returning to the initial macro (I don't want to use global variables).
Surely there must be a way?
Thank you very much.
EDIT: The code above returns Error 2015
In order to use a string as if it was code, you can use the evaluate function (exists in most languages)
The official documentation mentions this example:
[a1].Value = 25
Evaluate("A1").Value = 25
trigVariable = [SIN(45)]
trigVariable = Evaluate("SIN(45)")
Set firstCellInSheet = Workbooks("BOOK1.XLS").Sheets(4).[A1]
Set firstCellInSheet = _
Workbooks("BOOK1.XLS").Sheets(4).Evaluate("A1")
I have figured out the easiest way to do it, sorry for posting the question so soon.
Thanks to #Andreas for the solution. I'll write it here in case than could be useful to someone.
Sub example()
data_in = InputBox("Give me something: ")
a = Range(data_in).Value
Debug.Print a
End Sub
In the end the simplest thing is the last thing you try...

Iterating through collections

I have a collection that I'm attempting to iterate through, which I am able to do no problem. What I would like to achieve is seeing the next object in the collection, but I am unable to find anything on this.
I've tried to look ahead using a (+ 1) in the if statement, but this doesn't seem to work.
For each a in CollBlank
if CollBlank(a + 1) <> "some value" then
'do code
end if
Next
Ideally, I'd like to be able to look ahead.
Access-vba & excel-vba are tagged since collections are used in both access and excel, I'm personally using it in Access right now, but most tutorials are through Excel.
Rather than using for each, use a for loop with an index variable, for example:
Dim i As Integer
For i = 0 to CollBlank.Count - 2
If CollBlank(i + 1) <> "some value" Then
' Do stuff
End If
Next i

Excel VBA On Error error

I am rather new at programming, and while learning Python also started experimenting with Excel VBA. I have an issue with the last one.
I have some large Excel sheets and tried to validate that data in specific columns matches data on another sheet in certain columns as they will be supposed to relate to each other by these values (and will be connected by a third value). To make this a bit more difficult, both of these columns may contain more than one value separated by "|". So, I have split these values in a list and I try to iterate through them to make sure all these values are set correctly, the connection will work fine.
All is fine as long as all is fine :) I have however an issue where there are two values in one of those columns and only one in the other. I would like this discrepancy to be noted on a sheet and then proceed to the next item.
The way that seemed to be applicable for me is to use "On Error GoTo ErrHandler", then note error on another sheet, and then user Resume to proceed.
Here is what I came up with:
For h = 0 To UBound(Split1())
For j = 1 To GetMaxRow("SpecificSheet", A)
On Error GoTo ErrHandler:
If Sheets("SpecificSheet").Cells(j, 1).Value = Split1(h) And Sheets("SpecificSheet").Cells(j, 2).Value = Split2(h) Then
DependencyOk = DependencyOk + 1
End If
Next j
Next h
ErrProceed:
Also ErrHandler is:
ErrHandler:
Sheets("Issues").Cells(x, 1) = "IssueDescription"
GoTo ErrProceed
It stops at line 2 with Subscript out of range for Split2(h) rather than moving on to ErrHandler and then ErrProceed. I have the feeling this must be something very obvious but I am just unable to get this working, and I am not able to find other way (like a try/except) in Excel VBA.
UPDATE:
Trying to clarify things a bit. The root of the issue is, that the Split2 list is shorter than Split1 - which is an issue with the input data and I'd like to capture this. I get the Split values from cells, where the values are separated by "|" characters:
CellValue = Sheets("SomeSheet").Cells(RowNumber, ColumNumber)
CellValueSplit() = Split(CellValue, "|")
And then iterate as:
For h = 0 To UBound(Split1())
So as Split1 moves on to the for example 3rd value, Split2 throws error and script stops. The best I was able to do so far was, that I let it proceed with the loop, but as this is a rather large sheet, it will fill the same error report ca. 200k times in this case, which I'd like to avoid. So I'd prefer it to proceed from after this loop once it hits out of range error, and proceed examining the next value.
Thank you for your help so far and in advance!
You have an issue with your syntax. The proper Error statement syntax is:
On Error GoTo <string>
On Error Resume Next
On Error GoTo 0
When using On Error GoTo <string> there is no ":" at the end. The ":" doesn't come into play until you create the target location. Example:
On Error GoTo Here
'// ---- Do something ---- //
Here:
'// ---- Handle the error ---- //
If you use On Error Resume Next, then you're telling the machine to ignore errors and proceed on to the next line of code.
When you useOn Error Return To 0, VBA will reset its error handling back to default. It's a good habit when using On Error Resume Next to insert On Error Return To 0 as soon as you no longer need it. On Error Resume Next has a real potential to break your code and make it behave strangely. Not to mention debugging can be a real nightmare. Check out the VBA manual from Microsoft for a more detailed explanation.
Finally, if your question is answered, you should mark it as answered.
vba-excelvbaexcel
The short and quick version is that VBA Error Handling Routine's only handle errors in the actual code execution, they do not fire when conditions expressed by the code are not met.
In your case, you do not need any error handling at all. In most cases it is actually best to avoid On Error GoTo .... There are cases where it's inevitable, but they are rare.
Try this IF THEN ELSE block:
If Sheets("SpecificSheet").Cells(j, 1).Value = Split1(h) And Sheets("SpecificSheet").Cells(j, 2).Value = Split2(h) Then
DependencyOk = DependencyOk + 1
Else
Sheets("Issues").Cells(x, 1) = "IssueDescription"
End If
Actually I have just found the issue. It was caused by a ":" left after an If statement a few rows earlier. I still don't really understand what it did, but I suggest not to reproduce it :)

After a WMI search in VBScript, can I create my search filter BEFORE my "For Each" statement?

I've created an alternative search utility to the Windows search utility with VBScript using a WQL search, but, as it turns out, it's quite slow. I would like to speed it up and I think I can do it, but I would need to place my search filter AFTER my WQL search and BEFORE my For Each statement. Is this even possible?
I've already tested by filtering in the WQL search, but it's about 40% faster if I filter after the WQL search. I've also tested with and without iFlags, but they tend to slow the search quite a bit, even though MS seems to believe otherwise.
Since the user can search by filename, creation date, last modified date and/or file size, if the filter is after the For Each statement then the script has to create the search filter each time it enumerates a file. I'd like to create the filter once in the hope of shaving some time off the search.
This will probably make better sense when you take a look at the snippet of code I've posted. Note that the sub subCreateSearchString will have calls to other search options and functions (ie: convert from UTC to local time, format file sizes, etc.)
Dim strSearchName, strComputer, objSWbemServices, objFile, colFiles
Dim strFileName, strReturnedFileName, strQueryDriveAndPath
strSearchName = "test" 'Text being searched for - change as needed
strQueryDriveAndPath = "PATH = '\\Drop_RW\\' AND DRIVE = 'D:'" 'Path and drive in which to search - change as needed
strComputer = "."
Set objSWbemServices = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colFiles = objSWbemServices.ExecQuery("Select * from CIM_DataFile WHERE " & "" & strQueryDriveAndPath & "")
'* I'd like to place the call to "subCreateSearchString" here
On Error Resume Next
For Each objFile in colFiles
strReturnedFileName = objFile.Name
subCreateSearchString ' Search filter - it works when placed here
If strSearchForString Then
MsgBox "File matches:" & vbCrLf & strReturnedFileName
Else
MsgBox "File DOES NOT match" & vbCrLf & strReturnedFileName
End If
Next
Sub subCreateSearchString
'* Set Filename Variable for search:
strFileName = InStr(LCase(strReturnedFileName), LCase(strSearchName))
strSearchForString = strFileName
End Sub
Since you depend on the names of the files you're iterating over in the For Each loop: no, not possible.
I'd strongly recommend making some adjustments, though.
Use a Function rather than a Sub if you want to return something from a subroutine.
Avoid using global variables. They have a nasty tendency of introducing undesired side effects and also make debugging your code a pain in the rear. Pass values into your subroutines via parameters, and return values as actual return values.
The returned value is an integer (or Null), but you use it like a boolean and named your variables (and sub) as if it were a string. Don't do that. Name your functions/procedures after what they're doing, and name your variables after what they contain. And if you want to use a boolean value make your function actually return a boolean value.
Avoid Hungarian Notation. It's pointless code-bloat the way most people use it. Even more if your naming doesn't even match the actual type.
Do not use global On Error Resume Next. Ever. It simply makes your code fail silently without telling you anything about what actually went wrong. Keep error handling as local as possible. Enable it only for single commands or short code blocks, and only if there is no other way to avoid/handle the error.
Function IsInFilename(searchName, fileName)
IsInFilename = InStr(LCase(fileName), LCase(searchName)) > 0
End Function
For Each objFile in colFiles
If IsInFilename(strSearchName, objFile.Name) Then
MsgBox "..."
Else
MsgBox "..."
End If
Next

VBScript - Either getting object required or type mismatch errors

I have scoured the web and this site looking for an answer on this, so I would really appreciate some help.
I'm creating a VBScript to do some modifications to a user-specified Excel spreadsheet. I have the first part of my script working fine, but the second part is driving me nuts. I need it to search the first column for a value and, if found, delete the row. Right now I'm not worrying about the deletion statement--I'm doing testing by seeing if I can get the For Each statement to run properly as well as the If Then statement. Here's the specific block of code:
For Each cell in objSheet.Columns("A:A").Cells
Set cell = objSheet.Columns("A:A").Cells
If cell.Value = "60802400040000" then
cell.font.bold = True
End If
Next
I have tried many variations of this and cannot find the right combination. Initially I was getting an "Object Required" messages, and after reading a number of posts, found that I needed to put in a Set statement for cell, which I did. Now I am getting a Mismatch Type error message.
The funny thing is, before I put in the Set statement, the code would execute, but it would throw the Object Required error when I closed the spreadsheet. After adding it, the error for the Type Mismatch pops up immediately.
Most examples I keep finding on the web are for VBA, and I try to modify them for VBS, which I don't know very well. Any assistance anyone can give me will be greatly appreciated.
You are redefining cell, cell is defined automatically in the For Each statement.
Delete this line
Set cell = objSheet.Columns("A:A").Cells
This is an example from Help, unfortunately Help doesn't have any examples that uses For Each, only For x = n to n and other means. For Each is the right thing to do.
Set r = Range("myRange")
For n = 1 To r.Rows.Count
If r.Cells(n, 1) = r.Cells(n + 1, 1) Then
MsgBox "Duplicate data in " & r.Cells(n + 1, 1).Address
End If
Next n
For vba to vbs, you have to create the object and use, as some objects are automatically available in VBA (like app object) - Set exceldoc = CreateObject("c:\blah\blah.xls) then to use Set r = exceldoc.worksheets(0).range("MyRange").
Also you have to use constant values not names as vbscript can't look them up.

Resources