Cannot remove code modules from a destination workbook through VBA - excel

I am running an Excel 2010 macro that opens another workbook and removes a few code modules (a form and a BAS module). After that it re-imports them as an updated version. This is the code:
For Each x In destination_wb.VBProject.VBComponents
If LCase(x.Name) Like LCase("frmCCLogin*") Or _
LCase(x.Name) Like LCase("modCQ_test*") Then
destination_wb.VBProject.VBComponents.Remove (x)
Next
I have no problem with the import but the remove process doesn't always work as expected. For some reason the BAS (modCQ_test.bas) module is not always removed. As a result, when I re-import, a new duplicated module is created ending with a "1" (i.e. modCQ_test1.bas).
I could see that many people experienced the same problem however, none of the proposed solutions worked for me. Not sure why this is happening?
Please advise.

If you can use the exact name of the module, you can write something like:
Public Sub RemoveComponent(ByVal Book As Workbook, ByVal Name As String)
On Error Resume Next
With Book.VBProject.VBComponents
Call .Remove(.Item(Name))
End With
End Sub
If you're stuck with wildcard matching (i.e. SomeName*) you could iterate the VBComponents collection and cache the names into a collection or array or whatever and call the function above for each name matched.
Additionally, if you wish to enumerate the VBComponents collection and remove like your code sample, I recommend that you go in the reverse order.
So something like:
Public Sub RemoveComponent1(ByVal Book As Workbook, ByVal NameSearch As String)
Dim oCompS As VBComponents
Dim oComp As VBComponent
Dim i As Integer
Set oCompS = Book.VBProject.VBComponents
For i = oCompS.Count To 1 Step -1
Set oComp = oCompS(i)
If oComp.Name Like NameSearch Then Call oCompS.Remove(oComp)
Next
End Sub

Problem is resolved. This simple line of code that hides the destination workbook, fixed the duplication issue in my case:
destination_wb.Windows(1).Visible = False
After this you can remove, then add the components. No duplication will occur.

I have experienced exactly the same phenomenon and it drove me mad for weeks already.
I do check whether the Code Module had definitely been removed directly after the removal and although it had vanished from the VBE's Project View, it still exists and consequently the subsequent import creates a Code Module named xxx1.
Any of the hints given proved not to be reliable on the long run and thus are nothing but guesses. Since the phenomenon is unpredictable as mentioned you never can really tell what did the trick.

As time (some years) has passed I do now have an answer for the phenomenon and a solid and stable solution.
In order not to "cut off the branch you sit on" one will have to envoke another Workbook/VB-Project for deleting and re-importing a Component.
Even another VB-Project performing the task will have to consider that the Component is definitely removed when the code which removed it has "finished".
Conclusion: Rename, Remove, Import, all performed by a VB-Project invoked via "Run ...." will do the trick.

Related

Excel VBA hard crash on ModifyAppliesToRange with no error message

I'm getting a strange problem in Excel and VBA.
I'm calling ModifyAppliesToRange on various FormatCondition, along with quite a lot of other FormatCondition related stuff.
It works fine for a while (about 2-300 calls) and then for no reason I can see my Excel (and the VBA development environment) will shut down with no error from either my error handler or excel, no notification, nothing except a 'pause' where nothing happens until it crashes. This happens whether I'm running it normally or with a breakpoint and Step.
Depending on the exact contents of the WorkSheet's FormatConditions the exact number of times it works seems to vary. But if the starting condition is the same then the point of failure is the same (i.e. it fails on the same FormatCondition). Code failing is:
myCF.ModifyAppliesToRange Union(range1, range2)
where myCF As FormatCondition
I tried
Dim rng As Range
Set rng = Union(range1, range2)
myCF.ModifyAppliesToRange rng
I checked: myCF, range1, range2 and rng are all valid as far as I can tell, and show valid .Address strings (for the ranges). For example, it will fail on range1.Address="$DO$9:$GN$39,$BD$8:$BD$39", range2.Address="$CI$9:$DN$39", and the resulting rng.Address="$BD$8:$BD$39,$CI$9:$GN$39"
So I tried
Set rng = Union(Range(CFFrom.AppliesTo.Address), Range(CFTo.AppliesTo.Address))
Same problem.
On occasion (under different start conditions) it will get past the above ModifyAppliesToRange but the moment I look at myCF.AppliesTo.Address the same crash happens - whether hovering a mouse over it, or showing it in Watches, or displaying it in Immediate. Yet rng.Address gives no such problem.
Yet it also seems to work just fine for 2-300 times.
I suspect a memory problem (but Excel is not using much memory or CPU, there's plenty of empty memory on the computer, I've used Option Explicit, and so on) or a hard limit or bug in Excel
Any ideas as to why this might be happening?
Any ideas as to how I can find out what is shutting down Excel?
NOTE: To give the exact way to reproduce it I'd have to post my entire spreadsheet and code, which I'm reluctant to do. Still I might be able to send one-on-one if need be.
I've encountered this problem as well. It does seem to be an Excel bug to be honest.
However, according to this Technet forum post, it seems that Excel changes the format conditions collection on the fly when updating conditions. Seeing as that might lead to nasty bugs when processing all conditions in a loop (like I did), I first rewrote my code so that it first collects all conditions to process in a Collection, and then loops over those. That still gave me Excel crashes; possibly the condition objects themselves also get updated (created/destroyed) behind the scenes mid-way, causing stale pointers to deleted VBA objects?
Not trusting Excel to keep the order identical either, I then rewrote my code to keep looping over all conditions, finding the first to update and just restarting the loop, until not a single condition was left unprocessed. So basically like:
Dim stillBusy As Boolean
Do
stillBusy = False
Dim nextCondition As FormatCondition
For Each nextCondition In myRange.FormatConditions
Dim newAreas As Range
Set newAreas = ResizeAreas(nextCondition.AppliesTo, myRange.Rows.Count)
If Not AreasEqual(nextCondition.AppliesTo, newAreas) Then
Call nextCondition.ModifyAppliesToRange(newAreas)
stillBusy = True
Exit For
End If
Next
Loop While stillBusy
And while this is super inefficient, it... miraculously works?! (for now). Hopefully it also works for anyone else?
Btw: ResizeAreas and AreasEqual are some of my own helper functions; ResizeAreas transforms e.g. [A1:A3,C1:D3] to [A1:A6,C1:D6], and AreasEqual looks if the given two ranges are composed of the exact same cells ([A1:A2,B1:B2] and [A1:B2] = True)

MS Excel VBA: Is it faster to use Public Variables or Pass Variables between Subs?

I'm not the most savvy VBA coder so I'd appreciate your advice! I have multiple modules where each of these modules need access to one workbook (Master.xlsm). Is it faster to 1) use a public variable to access this workbook in other modules or to 2) open it in each sub that uses it?
Option #1
I'd set the workbook to a public variable and have it assigned whenever the workbook is opened using Auto_Open.
Public wbk_MASTER As Workbook
Sub Auto_Open()
Set wbk_MASTER = Workbooks.Open("C:\Master.xlsm")
End Sub
Option #2
Alternatively, in each sub that uses Master.xlsm, I'd just pass it like:
Sub DoSomething(wbk_master as Workbook)
' do something
End Sub
That assumes that whatever sub calls this sub would look like:
Sub CallDoSomething()
Dim wbk_master as Workbook
Set wbk_master = Workbooks.Open("C:\Master.xlsm")
Call DoSomething(wbk_master)
End Sub
Note that there'd be multiple subs like DoSomething. If it makes a difference, I'd also like to have variables for important sheets in Master.xlsm and even values from specific ranges.
Personally, I think Option #1 is cleaner but which is faster?
Dirk has answered your question directly, however how about a more resilient 3rd option?
In a standard module:
Public Property Get SourceWorkbook() as Workbook
Static wkb_Master As Workbook
If wkb_Master is Nothing Then Set wkb_Master = Workbooks.Open("C:\Master.xlsm")
Set SourceWorkbook = wkb_Master
End Property
It can then be used:
Sub test()
SourceWorkbook.Sheets(1).Name
End Sub
It keeps the scope small and read only, additionally should your project be reset, will reopen the source document - neither of your approaches will do this.
To give a direct answer:
Using a global variable is "faster" in a special not noticeable way because:
What you are looking at is just a pointer to the real object.
This way there is by default no difference in speed for using globalVariable.Sheets(1).Name or subOrFunctionVariable.Sheets(1).Name.
But: Passing it to the sub/function creates a new pointer to it which takes time and uses memory.
Still: It's like blowing up a house and asking which needle makes more noise if dropped. There should never be any noticeable difference for a human being. ;)
Just looking for the use of pointers, using numerical/string/array/non-pointer-objects may create a full copy of the data (if used ByVal instead of ByRef) which may has an impact.

Workbook_BeforePrint event not working

I want to dynamically set some data to the header (and/or footer) of a sheet.
From several forums I found numerous examples showing how to do that, and they all seem pretty easy to understand without any difficulty.
So I applied what I read to a simple try like this:
Private Sub Workbook_BeforePrint(Cancel As Boolean)
ActiveSheet.PageSetup.CenterFooter = Range("A1")
End Sub
But it merely don't work: nothing appears in my sheet preview.
BTW the only ambiguous point in documentations was about the expression of the data to use, so I also tried replacing Range("A1") by Range("A1").Text, then by Range("A1").Value, also without success.
I suppose I'm missing some simple point...
Your code worked for me. Also tested your code with some extra lines for debugging:
Private Sub Workbook_BeforePrint(Cancel As Boolean)
Debug.Print "Running Workbook_BeforePrint"
ActiveSheet.PageSetup.CenterFooter = Range("A1")
Cancel = True
End Sub
Before printing, the footer was empty; after printing, the footer in Preview matched A1 and "Running Workbook_BeforePrint" was in the Immediate window.
It's not the beforeprint that isn't working, it's the .centerfooter. Try instead:
ActiveSheet.PageSetup.CenterFooter = CStr(Cells(1, 1))
Documentation explicitly calls for a string, but I'm not sure why it explicitly needs it as a string even if it already is a string in the cell.
As other answers already suggested, the code was fine and the problem wasn't there: in fact, the issue came from the fact that it was not invoked.
And this in turn came from a silly situation: the Design Mode was enabled!
With Design Mode disabled, all works fine...
Check yourApplication.EnableEvents is true before print sheet

Subroutine not fully executing when called indirectly

I have a spreadsheet, with the following two subroutines in it (there's a lot more to them, but I've stripped out all the code not directly relevant to the question):
Sub HF_Reset()
Feats_Reset
End Sub
Sub Feats_Reset()
Range("TblAllFeatsSelected").Value = CVErr(xlErrNA)
Range("Test").Value = "Success"
Range("Test2").Value = 1
End Sub
Test is a single cell, Test2 is a two-cell range, TblAllFeatsSelected is a large range.
If I call Feats_Reset, it executes absolutely fine, does what it's intended to do. If I call HF_Reset, then Testgets "Success" put into it, and Test2 is filled with 1s, but TblAllFeatsSelected doesn't change. I have absolutely no clue what's going on - any ideas?
For debugging purposes, I've also tried setting Range("TblAllFeatsSelected").Value = 1 and Range("TblAllFeatsSelected").Value = 0, and again it works fine when calling Feats_Reset but not when calling HF_Reset.
EDIT: I've played some more, and traced the problem to another subroutine called in Feats_Reset. I suspect I'm not going to be able to provide enough information here to get a useful answer - it's a complicated sheet, and there's a lot of interactions that could be the problem. Bother.
EDIT2: Found the problem. The subroutine was setting TblAllFeatsSelected to the value of another range, which when calling from HF_Reset needed to have an Application.Calculate or it would justset it back to what it used to be.
Is there any way I can delete this question as not useful? It's such a specific thing, I doubt it could help anyone else.
Problem Exists Between Keyboard and Chair. I was missing an Application.Calculate in a completely different part of the code.

How to change case of method name in VBA?

While learning VBA I did a lot of copy-paste of code from the internet. At some point I have noticed that whenever I use a method which name is "Add", the VBA IDE automatically change it to "add". For example when I type ActiveSheet.PivotTables.Add it's automatically converted to ActiveSheet.PivotTables.add. The same happens with Worksheets.Add which is changed to Worksheets.add. How can I restore default case of a method?
I know that VBA IDE changes variable's case accordingly to how it was declared (see: How does one restore default case to a variable in VBA (Excel 2010)?). But I am not sure if it might be related to my issue.
This will happen because you have declared some variable or function or sub as add in your Project (or in some add-in you might have loaded).
The VBA environment will get the last declared case representation of your variable names/function names and apply them to every instance where they occur.
E.g.
Function Func1()
Dim ART As Double
ART = 1
' in one function
End Function
' Now if I type the following in another function
Function Func2()
Dim aRt as Double
End Function
' the original declaration in the first function becomes
Dim aRt As Double
aRt = 1
This behavior may be desirable or undesirable, but it's definitely by design.
If you want to declare variables and not want them to change case (useful for certain Enums, etc.) you need to do something like
#If False Then
Dim HeWhoseCaseShallNotBeChanged
Dim ADd ' Even methods can be "declared" here. It will have no effect on execution
#End If
This will work for Enums, etc. And you can declare this compiler directive at the top of your module page.

Resources