Can I use the Set command globally? - vba

I have several subs that use
Dim wb1 As Workbook
Set s1 = wb1.Worksheets("Sheet1")
I'm looking to move all the Dim statements out to a new module and make them globals (or would it better to make them public at place the top of the module they are currently contained in?).
Is there a way to do this the Set statements so I don't need this same line in each sub?

In a general code module (insert->module) enter something like this:
Public wb1 As Workbook
Public s1 As Worksheet
Public s2 As Worksheet 'etc.
Public Initialized As Boolean
Sub Initialize()
Set wb1 = ActiveWorkbook
Set s1 = wb1.Sheets(1)
Set s2 = wb1.Sheets(2)
Initialized = True
End Sub
Then in the ThisWorkbook code module, have:
Private Sub Workbook_Open()
Initialize
End Sub
Then (mostly as a safety in case something (e.g. a run-time error) resets the project) include the line
If Not Initialized Then Initialize
at the top of every sub that needs access to those variables.
Having said all this -- heavy use of global variables is considered by many to be poor design. On the other hand -- if they are variables that you use throughout the project and they never change their reference then something like this could cut down on the code clutter.

Related

Calling a global/public variable in moduleB whose value was defined in moduleA

I wrote 4 macros to do things, but it requires 2 inputs from the user to make sure the right file is being used because some of the macros switch back and between 2 workbooks. I only had access to a few of the files, but I knew that eventually I would have access to the rest of the 35 files. If I didn't have the inputs, I would have to manually change the filename in the macro code, but I don't want to do that, so I used inputs. But now that I have all the files in the right format, I am trying to a separate macro that has a list of the other files in a separate workbook, and then opens those files and does the macros, but it would require the inputs a lot. So now, I'm trying to remove that need for the inputs. But I'm unfamiliar with public variables and somewhat familiar with the calling of other subroutines.
My setup is this:
option explicit
public current as string
Sub master_macro
dim i as integer
dim path as string
dim wb as workbook
dim sht as worksheet
set wb = workbooks("name.xlsx")
set sht = wb.worksheets(1)
path = "C:\xxx\"
wb.activate
for i = 1 to 20
currun = sht.cells(i,1).value 'this takes the value from the separate workbooks that has the file names
full_currun = currun & ".xlsx"
with workbooks.open(path & full_currun)
.activate
call blanks
call lookup
call transfer
call combine
.save
.close
end with
next i
The last 2 macros switch between 2 sheets. So in those macros, the currun is generated the an inputbox, albeit a different name.
nam = inputbox("yadda yadda")
set wb = workbooks(nam & ".xlsx")
I'm trying to get the currun vaue that is defined in the master macro to macro3 and macro4.
You see the part where it says Sub master_macro? What you are doing there is declaring a procedure, which is a basically a general term to describe "a block of self-contained code that does something when it is run." Procedure declarations have three major components:
type - this is what you are doing with Sub; you are saying it is a subroutine, which is distinct from a function Function in that it does not return a value
name - this is the identifier you use to refer to the procedure elsewhere in your code. it is supposed to be descriptive since that enhances the readability. "master_macro" is not bad, but as a general rule you don't want to use underscores when naming procedures in VBA.
parameters - this is where you define the set of variable values that can be passed to the procedure when it is run. each parameter is separated by a comma and declared using the syntax [pass type] + [variable name] + [variable type]. [pass type] is either ByRef or ByVal; the basic distinction is that ByRef sends a direct reference to the variable, while ByVal sends a copy of the value.
The last part is what you are missing to solve this problem. Both macro3 and macro4 are declared (in module B) like master_macro is here. If they need to know what the currun value is then simply add (ByVal currun As String) to their declarations. When they are called from another procedure, as they are in master macro, they will expect to receive a string. Change the two lines in master macro from:
Call macro3
Call macro4
to
Call macro3(full_currun)
Call macro4(full_currun)
and macro3 and macro4 will have the value of full_currun stored in their own internal variable currun for use as they need.
Thanks guys. managed to get it to work. Here's the finished work below
sub master()
dim i as integer
dim path, currun, fullcurrun as string
dim wb as workbook
dim sht as worksheet
set wb = workbooks("Name.xlsx")
set sht = wh.worksheets(1)
path = "C:\xxx\"
wb.activate
for i = 1 to ?
currun = sht.cells(i,1).value
fullcurrun = currun & ".xlsx"
workbooks.open(path & fullcurrun)
call blank(currun)
call lookup(currun)
call transfer(currun)
activeworkbook.save
activeworkbook.close
call transfer(currun)
next i
end sub
public sub blank/lookup/transfer(byval currun as string)
blah blah blah
end sub

VBA - Reference an object by using a variable

Not sure how to reference the worksheet object with a variable that changes each time a sheet is activated.
The point is to reference a cell value based on the last worksheet that was activated (this code affects Sheet1 which does not set the variable when activated)
--Module1
Public MyWS as String
--Sheet3 (Deactivation)
MyWS = Sheet3.Codename
--Sheet2 (Deactivation)
MyWS = Sheet2.Codename
--Sheet1
Sheet1.Range("A3").Value = MyWS.Range("A3").Value
Updated:
Thanks for all the guidance but your instructions are not working for my project at least.
Sheet5.Range("C4").Value = Worksheets(MyWS).Range("A2").Value
Subscript out of range error when the above code is executed on Sheet5 deactivate.
MyWS is declared as a public string.
MyWS is assigned the Sheet5.CodeName string when Sheet5 is activated. Sheet5 exists and that is the unmodified codename of the sheet. I can not use the user defined name of the sheet because that can change.
Public MyWS As String declares a String variable, not an object.
CodeName
The CodeName property returns a String that contains an identifier that VBA uses to generate a project-scoped object variable for a Worksheet; in the properties toolwindow (F4), that's the (Name) property.
This is how such code is legal:
Sheet1.Range("A3").Value = 42
Because Sheet1 has a code name string that returns Sheet1. Note that this identifier isn't necessarily the sheet's name (it is by default though), which the user can change at any time without accessing the Visual Basic Editor.
So if you rename the "Sheet1" tab/sheet to "Summary", but don't change its code name, then it will still be Sheet1 in code - so these two instructions do exactly the same thing:
Sheet1.Range("A3").Value = 42
ThisWorkbook.Worksheets("Summary").Range("A3").Value = 42
Now, if you want an object variable holding a reference to a worksheet that exists at compile-time, you already have one - Sheet1 is exactly that.
If you added a worksheet a run-time (doesn't exist at compile-time), then there's no such project-scope object variable for that sheet; that's when you need to declare your own, and assign it with the Set keyword:
Dim someSheet As Worksheet
Set someSheet = ThisWorkbook.Worksheets.Add
ActiveSheet
The Excel object model also has the ActiveSheet object, which returns whatever sheet is currently active.
Sheet1.Range("A3").Value = ActiveSheet.Range("A3").Value
Notice the explicit qualifiers. If it's written in a standard module (.bas), this code is equivalent:
Sheet1.Range("A3").Value = Range("A3").Value
If it's written in the code-behind of a specific worksheet module, then the above code will instead be doing this:
Sheet1.Range("A3").Value = Me.Range("A3").Value
Where Me is whatever the specific worksheet module you're in is, so if you're writing that code in a worksheet module, you will want to explicitly qualify the Range member call with the ActiveSheet object.
Worksheet Events
If you need to execute code when a worksheet is activated, you can handle the SheetActivate event in the ThisWorkbook module:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Dim sheet As Worksheet
If TypeOf Sh Is Worksheet Then
Set sheet = Sh
Else
'Sh is not a worksheet. could be a chart sheet, or something else.
Exit Sub
End If
Debug.Print sheet.Name & " activated!"
End Sub
If you need to handle the Activated event of a specific worksheet that exists at compile-time, you need an event handler for it in that worksheet's code-behind:
Private Sub Worksheet_Activate()
Debug.Print Me.Name & " activated!"
End Sub
If you need to handle that event for a worksheet that is created at run-time, you need a WithEvents object variable in a class module (.cls):
Private WithEvents MySheet As Worksheet
And then you can write a handler for MySheet_Activate in that module, but that's more advanced stuff and I'm barely scratching the surface here, but that should get you going :)
With ActiveSheet as mentioned in the comments is really the best solution.
However, if you want to do it "your way", write these Activate events in every worksheet:
Private Sub Worksheet_Activate()
lastWS = Me.Name
End Sub
Then lastWs would be the name of the ActiveSheet. And you would be able to refer to it like this Worksheets(lastWs). Thus:
Sheet1.Range("A3").Value = Worksheets(lastWs).Range("A3").Value

How to declare and use public functions and subroutines in Worksheet code

I have a public function declared in one of my worksheet modules:
Public Function isValidContract(contract As String) As Boolean
' Code reads cell values from the worksheet and determines
' if passed-in contract is one of them.
End Function
I'd like to be able to access it from other modules and Class modules. I've tried the following:
Public Sub someRandomSubInAntoherModule()
Dim contract As String
Dim sh as Worksheet
' Code that sets contract
Set sh = Sheets("Matrix")
If Not sh.isValidContract(contract) Then
' blah
End If
End Sub
But I get a compile error: "Method or data member not found", probably because I declared sh as a Worksheet object, and the Worksheet object doesn't have an isValidContract() method. But I want to use the isValidContract() method defined in my Matrix worksheet.
The only way I can get it to work is to declare sh as an Object. But then I don't get the nifty little code hints when I type
sh.
Is there any way to dimension sh such that I get the code hints for the Worksheet object and my specific Matrix code?
OK - so I just figured it out.
Change the "Excel name" of the sheet to something that makes sense... in this case, I renamed Sheet1 to MatrixSheet by editing its Properties.
Then in the client code:
Public Sub someRandomSubInAntoherModule()
Dim contract As String
Dim sh as MatrixSheet
Set sh = Sheets("Matrix")
' Code that sets contract
If Not sh.isValidContract(contract) Then
' blah
End If
End Sub
It compiles, I get code hints, it's great.

How to clear the VBA code of a worksheet via a macro?

I have a file where there's a template sheet that needs to run some code when it's activated. This sheet is being duplicated to create sheets that don't need to run this code. Currently, I have the code to check for worksheet's codename when run so that it does nothing on extra sheets, but it still slows usage down when you switch between sheets.
Is there any way to make the macro that makes duplicates also clear their VBA code contents?
(Edit) Please note that the code I need to clear is not in a module. After some research, it seems I found a way to remove modules (by accessing VBProject.VBComponents), but I'm not sure how to access the VBA code of a worksheet.
To remove complete code in all Sheet modules you could try something like this:
Sub Remove_some_vba_code()
Dim activeIDE As Object 'VBProject
Set activeIDE = ActiveWorkbook.VBProject
Dim Element As VBComponent
Dim LineCount As Integer
For Each Element In activeIDE.VBComponents
If Left(Element.Name, 5) = "Sheet" Then 'change name if necessary
LineCount = Element.CodeModule.CountOfLines
Element.CodeModule.DeleteLines 1, LineCount
End If
Next
End Sub
Another way you could approach this is to keep all of your code out of the worksheet. Then you don't have to delete anything. The worksheet's code module is a handy place to code events, but you can create your own class module to handle events too. Put this in a standard module:
Public gclsEvent As CEvent
Sub Auto_Open()
Set gclsEvent = New CEvent
Set gclsEvent.This = Sheet1
End Sub
This will create an instance of CEvent that's global, so it won't lose scope as long as your workbook is open. It assigns the worksheet codenamed Sheet1 to the This property of the class. Create a class module named CEvent with this code
Private WithEvents mwsThis As Worksheet
Public Property Set This(ByVal wsThis As Worksheet): Set mwsThis = wsThis: End Property
Public Property Get This() As Worksheet: Set This = mwsThis: End Property
Private Sub mwsThis_Activate()
Me.This.Copy , Me.This.Parent.Sheets(Me.This.Parent.Sheets.Count)
End Sub
The WithEvents keyword exposes events for that object. Since we're only hooking up the events for Sheet1, activating another sheet won't trigger the code.

Can a worksheet object be declared globally in Excel VBA?

I'm refactoring a number of modules in an Excel 2003 workbook and the same set of worksheets are declared in each procedure in each module; I'd like to just declare them once globally. I can set the worksheet name as a literal, e.g.:
Public Const xlwkGSModel = "gs_model" As String
And then in the procedure use:
...ActiveWorkbook.Worksheets(xlwkGSModel).Cells(1,1)
But is there a way to declare the worksheet object so that the code in the procedure could be:
...xlwkGSModel.Cells(1,1)
'1. Insert a module
'2. Declare worksheet public variable in the module as follows
Public xlwkGSModel As Worksheet
'3. Instantiate this public variable in the application load event
Sub Workbook_Open()
Set xlwkGSModel = ActiveWorkbook.Worksheets("gs_model")
End Sub
'Now you can refer the gs_model worksheet with the xlwkGSModel variable
'For example
dim x as string
x = xlwkGSModel.Cells(1,1)
It's a long time ago, but better late than never ;-)
I don't know if that works in Excel 2003 (tested it with 2007):
You don't even need to declare worksheets in the code, because they are already declared. I'm working with a German version of Excel and I can access a worksheet by typing "Tabelle1.Cells(...) ...". I assume in the English version it is something like "Table1" or "Sheet1".
You can also change these names. In the Visual Basic Editor have a look at the Microsoft Excel Objects of your VBA-Project. (They are right above your Modules and UserForms in the VBA-Project view). There are the Worksheet-Objects of your workbook. Select a sheet and activate the Properties Toolwindow. There you can edit the name of the sheet and access it by that name in your code.
You could, but do you really want more global variables? Why not create (within a standard module) a public property ModelWorksheet, like this:
Public Property Get ModelWorksheet As Worksheet
Const ModelWorksheetName As String = "gs_model"
Set ModelWorksheet = ActiveWorkbook.Worksheets(ModelWorksheetName)
End Property
...then your code can do:
With ModelWorksheet
.Cells(1,1).Value = "foo"
.Font.Bold = True
End With
Note also that you can refer to any of the worksheets in the current workbook directly, so if there's a sheet called Sheet1 you can do:
With Sheet1
.Cells(1,1).Value = "foo"
.Font.Bold = True
End With
Or if you do not want to use the Excel Event, you can also declare your worksheet publicly, then create a sub to set value for it. Call that sub at the beginning of each sub to initialize the value. (I did this from orwell's answer.)
Public WSRawData As Worksheet
Public Sub RawDataWSInit()
Set WSRawData = Worksheets(RawData)
End Sub
Sub Main()
Call RawDataWSInit
End Sub
Your Worksheets are "Microsoft Excel Objects" defined in your VBA-Project-Explorer:
In The Screen above i have named my Worksheet DataSheet, so now i can access it directly from within the code:
Set SomeRange = DataSheet.Range("A3:B6")
By Default your Worksheets will be named "Sheet1", "Sheet2", aso... depending on your language.
Edit: The comment by Alistair Knock is correct, I should have read the question thoroughly - of course my answer is not valid for objects, only for types like string or integer. For objects you need a function or sub that creates an instance.
Yes, you can, I recently did it. If you define your definitions as Public you can use them directly in your other modules (within the same workbook, of course).
Maybe the best approach is to have a seperate module Globals and put them there.