More than one workbook object in project explorer - vba

I have this strange problem. I have a vba project, where the project explorer displays more than one workbook object. It is exactly the same as in this SO question, but I don't have any faulty references to uncheck.
However, I do know what's caused it and I'm sure you can all duplicate. What I did was use the codename for a sheet as a byref argument for a simple sub and at the end of the sub, nulled the worksheet object. So I nulled the whole sheet through the reference.
Something like:
Option Explicit
Sub test_1()
test_2 sh:=Sheet2
End Sub
Sub test_2(ByRef sh As Worksheet)
Set sh = Nothing
End Sub
If you run test_1, it will run without problems. But after that, the 'Sheet2' codename is invalid. And if you close and reopen the workbook, you'll see what I mean. Excel creates a new worksheet with the same name (but another CodeName). Any data stored in the cells of the sheet is not lost. The old CodeName references the Workbook Object.
I have not found a way to restore or remove the old references so far (other then move all the objects to a new workbook). I am using Excel 2013.
Solution is of course not so null the worksheet, but does anyone have any idea how to restore?

Well, after suffering this weird behaviour of Excel I can finally say that I have tried man times and that I can confirm that the cause (at least for my experiments) was indeed using User Defined Functions (UDFs). There is no way of fixing a Workbook once the extrange sheets appear. My approach was to just create a new Workbook and copy all the Modules and all the sheets from the old (broken) one.
Just make sure to not use user defined functions if you are having this issue.

Related

Updating a macro to be identical across all worksheets (or making the code more global?)

I have a workbook with a few dozen worksheets, which is growing. I might also end up with copies of this workbook pretty soon.
Each sheet is based on the same worksheet template, which includes a macro on its Worksheet_Change event for auto-calculations. As I improve the workbook and add capabilities, and sheets are added, it's taking longer and longer to copy-paste the updated macro to all sheets.
I'm wondering if there's a way to either:
Make another macro that copies the updated version of the macro from the template to all other worksheets, overwriting the old versions in the process
And/or
Move the code from worksheet_change to a more global place where I'd only need to update one version of it per workbook (which is fine and much better than updating 20+ worksheets manually across soon-to-be 3 workbooks...)
Solution #2 would be better because more elegant I think? But I have no clue if it's possible. Barring that I'd gladly take #1 for the time-saving aspect!
Thank you in advance.
If we are talking about one workbook with multiple worksheets, then an easy approach (which solves the updating issue) would be:
Add a Module and write a procedure containing the original change events code:
Option Explicit
Public Sub MyGlobalWorksheet_Change(ByVal Target As Range)
' here the code from your orignal Worksheet_Change.
' make sure you reference worksheets correctly
' the worksheet can eg be addressed like
' set ws = Target.Parent
End Sub
So in your worksheets you only need to add a generic call like
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
MyGlobalWorksheet_Change Target
End Sub
to call the global procedure. Therefore the Worksheet_Change event never needs to be changed, however you just need to add it once.
Whenever you need to change something at the code you just need to change one procedure which is MyGlobalWorksheet_Change and it affects all your desired sheets at once (but only sheets you added the call to your global event).
Remember it is always a bad idea to copy the same code over and over again, because it is hard to maintain. Instead always use one procedure you call again and again.
Another way would be using the Workbook_SheetChange event within the ThisWorkbook scope. But this will affect any sheet within the workbook. The previous solution will only affect the workbooks you choose by adding a call.

How to run a macro from a different workbook in a shared network?

So, I've done a lot of research on this and my code isn't working still. As per the title, the problem is this:
I pull a data report off of a website, this report is downloaded as an .xlsx file. I created a macro on the ribbon so I when I click it, it will then open another workbook and run that macro. The code I'm using is as below:
Option Explicit
Sub NotHardAtAll()
Dim ws As Worksheet,
Dim wb As Workbook
Set wb = ActiveWorkbook
Set ws = ActiveSheet
Workbooks.Open Filename:="C:\Users\a0c27n\Desktop\Projects\incident_extended_report1.xlsm"
'With Sheets("Sheet4").Activate '*Not sure if this is enter code here
necessary...at all*
Application.Run "!ADDHMKRFID"
'End With
End Sub
I've tried putting the path before the macro (i.e. Application.Run"'incident_extended_report1.xlsm!ADDHMKRFID") but it doesn't work either*
I'm aware, at least form the research I've done, that I should be able to just use the 'Application.Run' Method, however I couldn't get it to access the correct sheet.
When I run the Macro, it pulls a Run-time error '1004' error, a '400', or the it pulls the most is: "Cannot run the macro '!ADDHMKRFID'. The macro may not be available in this workbook or all macros may be disable."
The file that I'm trying to pull the macro from is below:
Workbook name: incident_extended_report1.xlsm
Worksheet name: Sheet4 (TEST MACRO)
Macro Name:
Sub ADDHMKRFID()
End Sub
I understand that the C:\ is not a shared network, the one I will be working out of will be the S:\, however I'm not sure how much information I can post due to confidentiality.
Please ask for any clarification or questions you may have. I've been stuck for a bit and am not sure what I'm doing wrong. Thanks in advance!
The string you need to pass to Application.Run depends on whether the workbook containing the macro is active, and if it isn't, the filename of the macro-containing workbook (IE: what's in the workbook.Name property).
if the macro is supposed to be run while the data report workbook is active, you want:
dim wb_data as Workbook: set wb_data = ActiveWorkbook
dim ws_data as Worksheet: set ws_data = ActiveSheet
dim wb_macro as Workbook
set wb_macro = Workbooks.Open(Filename:="C:\Users\a0c27n\Desktop\Projects\incident_extended_report1.xlsm")
ws_data.Activate
Application.Run wb_macro.Name & "!ADDHMKRFID"
This will guarantee that the correct string is supplied, even if you change the name of the macro file.
Otherwise, if the macro workbook is supposed to be active, skip activating the data worksheet, as the last opened workbook will be active by default, then use "ADDHMKRFID" as your string. Note that the "!" is missing. You need that only if you are specifying a macro in another workbook. It's the same kind of notation used when referring to data in other worksheets.
First of all, I solved my own problem. I would, however, be grateful if someone might explain to me why it worked the way it did.
I saved the original macro on the shared network, but I had to save it as a module (in this case Module1). I also saved the 2nd macro (to run the original one) in a different workbook (though it shouldn't matter, as long it is not a .xlsx file).
The Code I wrote was:
Sub Test() 'Name doesn't matter
Application.Run "'S:\xxxx\xxxx\xxxx\incident_extended_report.xlsm'!module1.ADDHMKRFID"
End Sub
Then I saved this macro to the ribbon so I could run it on the data report.xlsx file I have to download. Now, anytime I want to run the original macro, I just click the Test Macro, and it'll run the other one!
I'm guessing if you want to close the other workbook that you opened, you can just add a
Workbooks (“S:\xxxx\xxxx\xxxx\incident_extended_report.xlsm").Close Savechanges:=False
Good Luck!

VBA - excel closes the previous workbook on opening the new one

I have a strange problem, I suscpect it's connected to the version of the Excel, but I'm not sure at all. I can't figure it out alone so I need your help. I have a macro, which operates on a fresh workbook - it's not saved anywhere, as the worker will save it manually afterwards. The macro is a .xlam format add-in, adding a couple of buttons to the ribbon and these buttons start the code.
Inside the code I have simple lines for opening a new workbook, chosen earlier by an user:
Application.DisplayAlerts = False
Set wbMPA = Workbooks.Open(MPA_file)
Application.DisplayAlerts = True
Earlier, the code sets active workbook as an object/workbook the macro will mainly work on (tried both versions):
Set dwb = Application.ActiveWorkbook
and later in the code
dwb.activate
OR:
dwb = ActiveWorkbook.Name
and then
workbooks(dwb).Activate
The lines are in separate subs, but the variable is globally declared.
The code works fine until the opening of wbMPA (watching it in the locals all the time). When I try to open the new file with the code above, the earlier workbook (dwb) just closes itself from unknown reasons.
The error I get from the 1st method is this:
error screenshot
The second one gives a simple "Subscipt out of range".
The errors, however, are not a problem. The problem is the cause of them, which is closing of the workbook from unknown reasons.
It happens only when I open the completely new workbook (using the excel icon on the Start bar) - when I do it from File -> New -> Blank Workbook using already opened workbook, the error does not occur.
Another strange thing - me and my colleague from work use 2013 version of Excel. I never have this error, she has it every time.
This is a general scheme of the code, other things are meaningless in this case because there are no other manipulations of workbooks/worksheets.
Dim dwb As Object
Dim wbMPA As Object
Sub_1()
Set dwb = ActiveWorkbook
Set wbMPA = Workbooks.Open(MPA_file)
Call Sub_2
End Sub
Sub_2()
dwb.Activate
End Sub
I get an error on the activation of dwb in Sub_2, because it closes itself for God knows what the reason on the opening of wbMPA in the Sub_1.
If you have only opened a blank workbook (clicking Excel from Toolbar, for example) and then you open any named workbook before making any changes to the blank workbook, the blank workbook will disappear. I believe that is normal/expected behavior.
I can't speculate why this happens on one computer but not another, but this is always how I have observed new/blank documents (Excel, PowerPoint, Word) to behave, and assume this to be the normal behavior. You may have some different option/configuration on your Excel environment which is changing this default behavior, or maybe you are slightly altering the blank file before running the macro, and your co-worker isn't, etc.
A word of caution to avoid relying on ActiveWorkbook -- and especially in this case if the expectation is to always Set dwb to a new/blank workbook, the best way to do that is to explicitly create a new/blank workbook, rather than relying on the user to manually open a new/blank target workbook.
Set dwb = Workbooks.Add
If, on the other hand dwb must be assigned to some other known/existing workbook, then you should be either providing the file path to an Open statement, or the workbook name to the Workbooks collection.
On a related note, it's almost never necessary to Activate a workbook, see here:
How to avoid using Select in Excel VBA macros
And further note: your variables aren't globally scoped, they're scoped only to the module using Dim statement. A public declaration uses the Public keyword, not the Dim keyword. Both module-scoped and global-scoped should be used with caution (Public moreso than module-scoped) and in most cases it's preferable to pass objects/variables by reference to dependent subs and functions:
How to make Excel VBA variables available to multiple macros?

Referencing the selection in deactivated Workbook

Private Sub Workbook_Deactivate()
ThisWorkbook.ActiveSheet.Selection.Copy
End Sub
I'd like to copy the selection in the deactivated workbook. But this doesn't work, indeed, I got a run-time error.
Damn! It is possible!
Private Sub Workbook_Deactivate()
ThisWorkbook.Windows(1).Selection.Copy
End Sub
Explanation:
Both ActiveSheet and Selection are children objects of the Application object, not the Workshet or Workbook objects. (See MSDN: Selection and ActiveSheet.) Selection means the currently selected item in the active window. Same for ActiveSheet.
Now it turns out that you can specify a window object for both of them. Normally every Workbook has one window, but you can create multiple windows for each open workbook. Anyhow, every open workbook that you can click and select will have at least one window. This is whateverworkbook.Windows(1).
ThisWorkbook always references the workbook running the macro (that means the workbook actually containing the VBA code). In your case, that is the one you are leaving. (Unless you want the macro to run from any workbook you are deactivating, in which case you'd have to come up with a custom event handler that catches each workbook deactivate event...)
And that's it, really. You can reference a selection in another workbook, you just have to attack it through it's Window.
Thanks for the good question, I have learned something very valuable today. :)

VBA logic when using macros from personal.xls

I run a spreadsheet report that holds about 50 columns of data for anywhere from 1 to 5000 rows. I'm only interested in 4 columns, but they are never in the same location as these reports are set-up a bit differently for each client. I then take those 4 columns and paste into a new workbook that I can import into another program.
I have three macros created that accomplish this task flawlessy if ran from the local file. When I load them into the personal.xls for use on various files I have issues. Specifically workbook/worksheet referencing issues.
Parts of the macro run to the sheet I intend from them to result on, while other parts act on the personal.xls file itself. This confuses me because I don't have any lines that use commands such as 'thisworkbook' or 'activeworksheet'.
For example:
- The first line is coded to rename Sheet1. The macro renames Sheet1 in personal.xls.
- The next line is the first of four Find commands that locate where the columns i'm interested are located and then move them. This macro runs perfectly on the sheet I intend.
I think my best course is to begin each macro by naming the active workbook and then breaking out each command to the workbook level instead of starting with Worksheets, Range, etc.
Can anyone help me understand what VBA is thinking when performing macros from personal.xls and how to best avoid the macros being run on that sheet itself?
There are two approaches you can take. I use one or both in my code - it's not a one or the other situations.
Declare Variables
Start by defining each sheet that you want to work on in a variable. I generally stay at the sheet level, but that's just a personal choice. If you'd rather be at the workbook level, that's OK too. A procedure might looks like:
Dim shSource as Worksheet
Dim shDest as Worksheet
Set shSource = Workbooks("SomeBook").Worksheets(1)
Set shDest = ActiveWorkbook.Worksheets("Summary")
then whenever I reference a Range or Cells or anything else on a sheet, I preface it with that sheet object variable. Even if I need to get to the workbook, I start with the sheet. If I needed to, for instance, close the Source workbook from the above example, I would use
shSource.Parent.Close False
I set up the sheet variables I need and then everything I do is in terms of those variables.
Edit
If you're opening or creating workbooks, then variables is definitely the way to go. For example, if you're opening a workbook, you could use one of these two examples
Dim wb As Workbook
Set wb = Workbooks.Open(C:\...)
Dim ws As Worksheet
Set ws = Workbooks.Open("C:\...).Worksheets(1)
or creating new, one of these two examples:
Dim wb As Workbook
Set wb = Workbooks.Add
Dim ws as Worksheet
Set ws = Workbooks.Add.Worksheets(1)
With Blocks
When I'm only trying to get at something one time, it seems like a waste to set up a bunch of variables. In those cases, I use a With Block so I can still have fully qualified references, but without a bunch of clutter in my code.
With Workbook("MyBook")
With .Worksheets("First_Sheet")
.Range("A1").Value = "stuff"
End With
With .Worksheets("Second_Sheet")
.Range("G10").Formula = "=A1"
End With
End With
I probably prefer the variable method, but I use them both.
Edit 2: Implicit Referencing
You should always explicitly reference your workbooks and worksheets, but it's still instructional to know how Excel will behave if you don't. A line of code that starts like Range("A1").Value = ... is called an unqualified reference. You're referencing a range, but you're not saying which sheet its on or which workbook that sheet is in. Excel handles unqualified references differently depending on where your code is.
In a Sheet's Class Module (like where you use sheet events like SelectionChange), unqualified references refer to the sheet represented by that module. If you're in the Sheet1 module working in the Change event and you code x = Range("G1").Value then the G1 you are referring to is on Sheet1. In this case, you should be using the Me keyword rather than relying on Excel.
In any other module (like a Standard Module), unqualified references refer to the ActiveSheet. The same x = Range("G1").Value code in a Standard Module refers to G1 on whichever sheet has the focus.
Excel's treatment of unqualified references is very reliable. You could easily create robust code by relying on Excel to resolve the qualified references. But you shouldn't. Your code will be more readable and easier to debug if you qualify every reference. I qualify every reference. And that's not one of those things I "always" do except when I'm lazy - I really do it 100% of the time.