VBA Error Handling only works on first pass - excel

My code is:
Sub zaa()
'code to find another open file that has the defined name "data"
' - useful where the name changes each month or week
For Each wb In Workbooks
On Error GoTo abcd
x = wb.Name
Workbooks(x).Activate
If Range("Data").Address <> "" Then y = wb.Name
Exit Sub
abcd:
Next wb
End Sub
Basic goal to find an Excel file with a specific named range when I know it exists but don't know the file name as it changes each week or month. Aim is to find the file and exit the sub at that point (or then do other code on that file and exit rather than going to other files.)
I find it works okay if I only have two files open but not if have more (unless the target one is second in line). Whilst I can run with what I have I thought others may benefit from what I have and I can have a more robust solution.
UPDATE:
Thanks to all those who have responded & to Mitch for putting original post in readable format! (I have since learnt how to correct that issue and to be able to copy code directly - which I have indicated in some comments below that I was having trouble with.)
I have had varying degrees of success - code from PaulStock & Reafidy both worked for me originally but PaulStock code has stopped working for me. Responses & code from Jean-François Corbett and Chris Neilsen have been helpful to me but presently when I run the code (as is) it appears to do nothing - ie doesn't leave the sheet I run it from (and that's not where the range name data appears). I was originally running the code in Excel 2007 in a sheet with other macros but to try and get different results have moved them to a stand alone sheet with no other macro files open. Have also tried running them from a file saved as '97-'03 format. Neither yielded different results. Others with more experience than I (see the errors I made evidenced in comments discussion with Reafidy & bear in mind that my original posted code was result of material found through google and modified by me for specific task & application and implication that I am not savvy enough to have come up with it on my own) may find other solutions better for them but for now:
I am more than happy as Reafidy's code works for me.
As I am not registered (I did try but it didn't work) and don't have enough reputation points I can not vote but I have put a check mark next to Reafidy's solution.
FURTHER UPDATE:
I have now discovered that PaulStock & Jean-François Corbett's code wasn't working for me due to a very minor discrepancy on my part - the code contained "Data" whilst my named range was "data". Making this change (to allow for case sensitivity) in their code means that both of their solutions now work for me and hence I have attempted to add a tick to their solutions! Unfortunately I have found that only one solution can have a tick.
I have also learnt that I took Chris' code too literally. In an effort to test each code 'as is' that is what I did. A simple additon of 'wb.activate' in the section where he has 'do stuff' makes the code do what I want.
THANKS again for all four contributions.

You're doing a few convoluted things in your code, and I think that's what's confusing the issue.
Having you error handler (abcd:) start in the middle of a For...Next loop is incredibly bad practice and can only lead to confusion. I can't think of a reason why this should ever be done.
Anyhow, there's no need to use error handling for this task. I understand that there are exceptional cases where bad VBA design forces us to use error handling instead of what should be built-in VBA functionality (e.g. testing whether a non-Variant array has been allocated), but this is not one of those cases. There's a standard way to do it (.Names collection of workbook object), and using error handling instead of this is messy and convoluted and asking for trouble.
Also, why say
x = wb.Name
Workbooks(x).Activate
when you can just say wb.Activate? You're not using x for anything. Or y, for that matter.
The following works and is both simplified and optimised, relative to your original code as well as to the other answers that have been given up to now:
Sub zaa2()
Dim wb As Workbook
Dim nm As Name
For Each wb In Workbooks
For Each nm In wb.Names
If nm.Name = "Data" Then
wb.Activate
Exit Sub
End If
Next
Next wb
End Sub
' A workbook containing a range named "Data" is now activated
' (if one is found amongst the open workbooks).
' Note that there may be more than one, but only the first found is activated...
EDIT: In your comment, you mention you had trouble due to confusion between uppercase "Data" and lowercase "data". To guard against this in the future, one possibility is to ignore case. This could be done as follows:
If StrComp(nm.Name, "data", vbTextCompare) = 0 Then
or
If LCase(nm.Name) = "data" Then
Both will return True if nm.Name is "Data", "data", "dATa", etc.

You can't use error handling like that. Either move the error handling out of the loop or reset the error handler each time it occurs, so use error handling like this:
A much preferred alternative would be:
Sub Test()
For Each wb In Workbooks
x = wb.Name
Workbooks(x).Activate
If RangeExists("Data") Then
y = wb.Name
Exit Sub
End If
Next wb
End Sub
Function RangeExists(s As String) As Boolean
On Error Resume Next
RangeExists = Range(s).Count > 0
End Function
EDIT:
#Jean-François Corbett, I have to say you a very quick to jump on the down vote button. The first solution I posted was because I made the assumption that the OP was not posting his entire code hence why I did not attempt to simplify it or "clean it up" like I usually do. I agree I did not word my answer well, but with regard to the first solution I was trying to demonstrate that he needed to reset the error handler. Unfortunately I should have said a "prefered alternative would be".
#Derek, Sorry I was unable to answer your further questions in time. Obviously you are free to choose whatever method you like. In my opinion the multiple loop solution provided by others which digs into the workbook name collection is unnecessary and long winded. Now more importantly the name collection can contain names which refer to a constant, formula, or a range. I presume you want to only check if the defined name is specifically a named range which means the looping method provided by others would need to be adjusted to be reliable.
I agree with comments made by others that error handling should be avoided BUT unnecessary looping in excel can be as much of an evil as using error handling and personally I avoid it like the plague.
The function above can be placed in its own module and be reused as much as you like. It is quick, reliable, avoids unnecessary looping, checks SPECIFICALLY for a named range in a workbook and is the most widely accepted/used method for checking if a named range exists within the excel vba community (by this I mean using a function and error handling over looping through the name collection). Do a google search for "Check If Named Range Exists" if you don't believe me. Or ask at www.ozgrid.com/forum if you want other excel vba experts opinion's.
Now that I know you have posted your entire code and that you did not intend to activate every workbook, you could use this code which will activate the first workbook found with the named range "data":
Sub Test3()
Dim wbLoop As Workbook
For Each wbLoop In Workbooks
If RangeExists("data", wbLoop) Then
wbLoop.Activate
Exit Sub
End If
Next wbLoop
End Sub
Function RangeExists(s As String, wb As Workbook) As Boolean
On Error Resume Next
RangeExists = wb.Names(s).RefersToRange.Count > 0
End Function
I completely understand the need for positive Criticism and I believe in the down vote system if it is used correctly. However, with two down votes for what I believe was a reasonable solution and along with my help with the ops formatting issues - unfortunately I cant help but feel like I want to distance myself from this forum.

Heres an alternative method without getting fancy with the error handler
Sub zaa()
Dim wb As Workbook
Dim CheckForNamedRange As Boolean
Dim nm As Name
On Error GoTo EH
For Each wb In Workbooks
CheckForNamedRange = True
Set nm = wb.Names("data")
If CheckForNamedRange Then
' Name found
' do stuff
Exit For
End If
Next
Exit Sub
EH:
If CheckForNamedRange Then
' Name not found
Err.Clear
CheckForNamedRange = False
Resume Next
Else
' Some other error occured, so handle it
'...
End If
End Sub

Try this code. Shouldn't have to worry about getting errors.
Sub zaa()
For Each wb In Workbooks
x = wb.Name
Workbooks(x).Activate
For Each n In Workbooks(x).Names
If n.Name = "Data" Then
y = wb.Name
Exit Sub
End If
Next
Next wb
End Sub

Related

Why Is My Spreadsheet Showing Ghost Images From Another Worksheet After Running My Script

(07/08/21 - I edited my text to update and sharpen the problem).
I have made an Excel VBA program that provides the conditional formatting of a large number of cells (which are formatted using the formulas option which refer to cell values in the target spreadsheet). The script and spreadsheet works fine, but I have a problem as immediately after I have run my script (or to be precise a particular input box script has been run) then ghost images appears. (I can easily replicate the issue including on different Windows machines.) The ghost images no longer happen if the user saves the sheet and then re-opens it. However, to me this is not a good solution and makes the program look poor in quality and trustworthiness!
I have a "first" routine that when run (via a button press) uses an Application.Inputbox - this allows the user to select a range of cells. These selection of cells are located in the target worksheet which is a different workbook to where the code is run from. Also, the selection of cells are located in a sheet that is not the front sheet of the workbook concerned.
I then have another second button which when pressed uses collected data and conditionally formats the target spreadsheet. However, after doing this button press I get ghost images appearing (which shows cells from selection made earlier from the first button press).
The screenshot below illustrates the occurrence - you can see that there is a table being shown from the second sheet on the top left-hand side of the sheet (despite not fitting the cells of screen 1!). I hope that makes sense.
Someone kindly below said that I needed to use:
Application.ScreenUpdating=False
and then return it to true at the end.
However, I still have the same ghost images occur and I note these happen after the script has been run.
From researching the topic, I found that this is a common issue from using the property Application.InputBox. If I run my second program without using the first one immediately before it (which has the Application.InputBox) then no ghost images appear. Therefore, I think it is pretty safe to assume the problem has come from this Application.InputBox! However, I have not been able to find a solution! I list below the code used for the first Application.InputBoxs routine.
Sub UserSelectsCells()
Dim rng As Range
Dim wks As Worksheet
Dim wkb As Workbook
If Range("C9") <> False Then
Workbooks.Open Filename:=Range("C9")
End If
On Error Resume Next
Set rng = Application.InputBox( _
Title:="Select Test Cells", _
Prompt:="Please Find The Cells In Your Workbook That Test Whether The User Has Answered The Questions Correctly" & vbCrLf & "Remember this may be in a different sheet in your workbook" & vbCrLf & "These cells must be in a single column", _
Type:=8)
On Error GoTo 0
'Test to ensure User Did not cancel
If rng Is Nothing Then
Workbooks("Version060821.xlsm").Activate
Exit Sub
End If
Workbooks("Version060821.xlsm").Activate
Range("C32").Value = rng.Parent.Parent.Name
Range("C33").Value = rng.Parent.Name
Range("C34").Value = rng.Address
Range("D35").HorizontalAlignment = xlLeft
Range("D35").Value = rng.Count
End sub
Can anyone please find a solution? As an idea, is it possible to somehow clean the memory before my second program is run?
I note that if there is a ghost images problem and I delete all of the conditional formating then the ghost images still appear. I think this is significant because the conditional formatting is linked to the ghost image cells that appear. So, to me this suggests there is some kind of microsoft bug?
I'm not exactly sure why these ghost screens pop up sometimes but I've found that preventing the screen from flashing during your code normally fixes the issue. You can do this by setting Application.ScreenUpdating to False and the beginning of your code. Just be sure to set it back to True at the end! Something like this:
Application.ScreenUpdating = False
[Your code]
Application.ScreenUpdating = True
edit:
After further research, it would appear this is an issue that has been already identified. The workaround below originally comes from here.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim wSheet As Worksheet
On Error Resume Next
Application.ScreenUpdating = False
For Each wSheet In Worksheets
wSheet.Select
Range("A1").Select
Next
Application.ScreenUpdating = True
End Sub
This is definitely a dirty fix but if it works it works. An alternative solution was to scroll up and down using:
Private Sub worksheet_change(ByVal target As Range)
Application.ScreenUpdating = False
ActiveWindow.SmallScroll Down:=-100
ActiveWindow.SmallScroll Up:=100
Application.ScreenUpdating = True
End Sub
Please let me know what works best!
Not enough rep for a comment!
Alec, Workbook_BeforeClose is a workbook event, you don't call it like procedures.
Workbook_BeforeClose Event
From that documentation, "False when the event occurs. If the event procedure sets this argument to True, the close operation stops and the workbook is left open.". So if you add Cancel=True inside it, say, after an if statement check, you can stop the close operation.
Edit: In order to answer the question in comments.
The event is fired when you close the workbook, either from the X in the corner or from the menu, or if you have something like ActiveWorkbook.Close in your code.
You don't have to have a Cancel=True/False inside BeforeClose event's code, depends on if you want to control a premature closure of the workbook. It is required, say, if you were writing the event yourself instead of selecting it in VBA editor. Editor already inserts that parameter.

Sheet name does not exist when obviously it does

I have been developing a VBA, UserForm1 program for the past few weeks and I have performed numerous test and never once had this problem.
The code, shown below, is part of the Initialization routine. I made some enhancements to the program, far deeper in the code than the code under discussion. Now, I can't get past this point in the program. I replaced "Activate" with "Select", but this did not affect the outcome, i.e., an aborted run.
Please, can someone suggest what I am doing wrong? Or, how can a program that been tested dozens of times, suddenly develop a fault out of thin air?
'======================================================================
Dim WPA() As Variant 'Workbook path for Category Class
Dim WBA() As Variant 'Workbook name for Category Class
Dim WSA() As Variant 'Worksheet name for Category Class
'======================================================================
Dim WBK() As Workbook
..........
Set WBK(S) = Workbooks.Open(WPA(S))
Workbooks(WBA(S)).Worksheets(WSA(S)).Activate 'Activate Worksheet
NWS(S) = CheckSheetExists(WSA(S)) 'Check for Worksheet
..........
Function CheckSheetExists(SheetName) As Boolean
CheckSheetExists = Evaluate("ISREF('" & SheetName & "'!A1)")
If CheckSheetExists = False Then
MsgBox "Worksheet " & SheetName & " does not exist. Run aborted."
End '<==================Run aborts
End If
End Function
It looks like you are using global variables?
If so, you should be aware that they are not as permanent as you might expect.
There are a number of things which cause global variables to reset and become undefined.
See this answer for more info: https://stackoverflow.com/a/7043901/1473412
Using "End"
An unhandled runtime error
Editing code
Closing the workbook containing the VB project
Is it possible that the "enhancements to the program, far deeper in the code than the code under discussion", causes one of these things to happen, and so resets the global variables?
If you have to use global variables, you could store them safely in a worksheet. Or, everytime you try and use one, you could check that it is defined, and if not, redefine it?
That is a good question, Storaxs' answer should have fixed this though to my knowledge.
However when something like this happens I try to create a whole new workbook and paste all of this code into it (just for the sheet in question) and then copy all the cells data into the new sheets.
This has saved me once before and it may help you in this case as well.
I also recommend not deleting your old workbook in case you don't port everything over.
If this still doesn't work let us know and we will continue to look into this.

Why does the same code (Sheets.Add) exhibit two different behaviours?

Earlier this morning I asked my first question. Whilst investigating the suggestion put forward by Marco, I encountered a "does not make sense" situation. I went back to the immediately previous version of the macro suite to see what behaviour that gave. Let's call these Suite A and Suite B
In both Suite A and B there is the following code:
Public wsTemp As Worksheet
Sub DrillDown()
In Suite A there is the following:
Sub HandleNewFileNumber()
If iK <> 0 Then
wbTgt.Close
End If
Set wsTemp = ThisWorkbook.Sheets.Add(After:=Worksheets(1))
In Suite B there is:
Sub HandleNewFileNumber()
If iK <> 0 Then
wbTgt.Close
End If
Set wsTemp = ThisWorkbook.Sheets.Add(After:=Worksheets(1))
No, I have not simply copied the same code twice. I took each piece of code out of the two separate workbooks. The code is identical.
When I run the macro suite A everything runs through to successful completion. When I run macro suite B I get Error 1004. In both cases the Set of wsTemp is the first time wsTemp has been referenced.
I am at a loss to understand how the same code can give two radically different results.
Try
Set wsTemp = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Worksheets(1))
Or
With ThisWorkbook
Set wsTemp = .Sheets.Add(After:=.Worksheets(1))
End With
It's a very common problem: When you access an object from Excel (Sheet, Range, ...) and don't specify where it belongs to (eg a Worksheet belongs to a Workbook, a Range belongs to a Worksheet), VBA assumes you are referring to whatever is currently active (currently having the input focus).
If you write Worksheets(1), Excel will translate this to the first worksheet of the current active Workbook. When ThisWorkbook is not the active workbook, this will lead to the situation that you ask Excel to add a sheet to one workbook but put it after the sheet of another workbook - and that's not possible and will throw an error.
VBA beginners will often try to solve this by put activate-statements in the code, but that's the wrong attempt. There is no need to activate anything. Instead, just tell VBA exactly what you want. By writing ThisWorkbook.Worksheets(1), you specify that you mean the first sheet of ThisWorkbook (which is the WB where the code lives in). The second piece of code (using With) is just another way of writing it - but note that .Worksheet has a leading . that signals that we are accessing the sheet of the Workbook specified in the With-clause.
As a rule of thumb, never put anything in your code that let's VBA guess something.
Having spent an hour or two working through the entire suite of macros, applying the technique suggested by #Fun Thomas, and tidying up some other loose code as I went along, I can no longer reproduce the failure on my most up-to-date version of the macros.
Thank you everybody for your thoughts and suggestions. I will now concentrate on offering further diagnostic data to my very original post about flickering grey screens when opening workbooks.

Protected Sheet Creating Error

I have kind of a paradox problem with my macro enabled workbook (paradox meaning that the very same commands work just fine in another protected sheet of mine):
Once I start protecting my Worksheet, the commands ".Interior.Color" and ".Borders(xlEdgeRight).LineStyle" will continue to produce "application-defined or object-defined errors"
Here's an example of one macro showing that very error:
Private Sub Con1_Click()
Sheet1.Unprotect Password:="bla"
Worksheets("SOLL").Range("N23:S25").ClearContents
Range("N23:S25").Interior.Color = RGB(225, 225, 225)
Range("P23:Q25").Borders(xlEdgeRight).LineStyle = xlNone
Sheet1.Protect Password:="bla"
End Sub
Note: I added the Unprotect/Protect commands to get rid of other errors that kept occuring. Like I said, the two commands work just fine in another sheet, so I really can't figure out any source of the problem. If anyone has encountered similar problems yet or has any ideas for solutions, I'd be glad for the help!
Too long/confusing to put in comments, so I will put this as an answer, perhaps it will shed some light on your problem. You say:
All commands of that Macro refer to the 'ActiveSheet',
But this is not necessarily true. Observe the three different constructs you're using to refer to worksheets:
Explicit reference to a worksheet by its codename: Sheet1.Unprotect ...
Explicit reference to a worksheet by its sheet name: Worksheets("SOLL")...
Implicit reference to whatever worksheet is active at run-time: Range("N23:S25")..
There are many circumstances which falsify your assertion that "all commands of that macro refer to the ActiveSheet. For example, Sheet1 may (or may not) be the same as Worksheets("SOLL").
So, potentially this code is referring to as many as three different worksheet objects! Only the implicit, unqualified Range statements can be guaranteed to refer to the ActiveSheet.
Perhaps this test will shed some light on the matter. Modify your code as follows:
Private Sub Con1_Click()
MsgBox "Sheet1.Name is: " & Sheet1.Name
MsgBox "Worksheets("SOLL").CodeName is: " & Worksheets("SOLL").Codename
MsgBox "The activesheet is: " & ActiveSheet.Name
End Sub
You may also add the following information, which could be useful to those offering assistance:
Where is this button located? (On which worksheet?)
Where is the code for the button event located? In a sheet module or a standard module?

Problem with adding event code to newly created sheet

I have a problem with adding an event code to a newly created sheet.
The problem seems to only occur right after I open the Excel workbook.
I use
Dim codemod As Object
codemod = ActiveWorkbook.VBProject.VBComponents(Worksheets("Sheet4").CodeName).CodeModule
to add the code to the created sheet module but when I try to run this code right after opening the Excel workbook it gives me an error: run-time error '9' Subscript out of range. The debug points to the codemod line.
The weird part is that this error does not come up again when I change the code just a tiny bit and then change it back to the original state. After I do this the code runs as it should i.e. inserts code to the newly created sheet.
Anyone got any idea what may be the problem?
There is not much else to the code except inserting the lines but that does not seem to be the problem.
Thanks in advance
(This is my first action on S.O. so please don't shout at me when I'm doing things wrong.)
Did you concider preparing a workbook + sheet with the desired event code (Test1.xls-Sheet1). Then, in the target workbook (Test2.xls), copy that prepared sheet. The code in the target workbook would look like
Sub Demo1()
Workbooks.Open "Test1.xls"
Sheets("Sheet1").Copy After:=Workbooks("Test2.xls"). _
Sheets(Workbooks("Test2.xls").Worksheets.Count)
Workbooks("Test2.xls").Activate
End Sub
This obviously is a workaround but it works instantly.
A second option could be to prepare the 'workbook-with-one-sheet' and save it as a (sheet) template in D:\Documents and Settings\User\Application Data\Microsoft\Excel\XLSTART. In that case the code can be
Sub Demo2()
Sheets.Add Type:="Test1"
End Sub

Resources