Public variable to save the sheet last clicked on - excel

I have a workbook that has several sheets that contain cells that open up the same userform to be filled out.
Currently, closing the form returns the workbook to one page in particular.
I am trying to return to the page that was previously being worked on.
I set up a public worksheet variable (wsWorking) that would be set to the sheet last clicked on before opening the UserForm.
I get:
Run Time Error '9': Subscript out of range
and the debug message shows my wsWorking variable as empty.
If I put in the name of a sheet instead of trying to use the variable, I can open it to the page I want.
Declaration: '(in my ThisWorkbook module, (General) (Descriptions)
Public wsWorking As Worksheet
Attempted Set: '(In a private module, extraneous code removed)
Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Set wsWorking = ThisWorkbook.Worksheets("MRL 1")
Dim clickRow As Integer
Dim ClickCol As Integer
Attempted Use: (in a different, private module)
Private Sub CloseForm_Click()
Call SaveFormToScorecard_Click
Unload Me
ActiveWorkbook.Sheets(wsWorking).Activate
End Sub
I tried setting the wsWorking in the general declarations for the module, when the worksheet is activated, and created a public sub in the module solely for the purpose of setting that variable, and none of it works.
Changing
ActiveWorkbook.Sheets(wsWorking).Activate
to
wsWorking.Activate
gives me a new error
Run-Time Error '424': Object Required
I don't believe the setting of the variable in the one module is transferring to the other one.

ThisWorkbook is a class module with a PredeclaredId attribute that makes it accessible from anywhere, but its members are still its members. You can access any public member of ThisWorkbook from anywhere in your code, by qualifying it with the predeclared ThisWorkbook object:
Debug.Print ThisWorkbook.wsWorking.Name
If you want a global variable, then you cannot use an object module, because objects require an instance (the ThisWorkbook instance being automagically created doesn't make it any less of an object instance). Declare a public variable in a standard module instead, and then you can access it unqualiifed from anywhere in your code, whether to read it or to write it.
Wait. Say this out loud.
and then you can access it unqualiifed from anywhere in your code, whether to read it or to write it.
This probably isn't a good idea. Consider declaring it Private, and only exposing it as a Public Property Get so that it cannot be overwritten at any time by anything anywhere.
This should work if it's Public in ThisWorkbook:
Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Set ThisWorkbook.wsWorking = ThisWorkbook.Worksheets("MRL 1")
Dim clickRow As Integer '<~ this will explode at row 32,768. Use a Long!
Dim ClickCol As Integer '<~ should still be a Long
A few severe issues here:
Call SaveFormToScorecard_Click '<~ event handlers aren't supposed to be invoked like this
Unload Me '<~ self-destructing object, danger!
ActiveWorkbook.Sheets(wsWorking).Activate '<~ will throw error 1004 End Sub ```
ActiveWorkbook is whatever workbook happens to be active at the time, and while that's probably ThisWorkbook, there's a good chance it's not. And whenever it isn't, this will blow up. wsWorking is a Worksheet reference, it's redundant to dereference it (by name, implicitly!) from a Sheets collection - just ThisWorkbook.wsWorking.Activate would do.
Note that if wsWorking exists in ThisWorkbook at compile-time, you're probably better off just using its own predeclared instance.
Every Worksheet module has a (Name) property that is the name of the VB project component; this value becomes the name of a global object, just like ThisWorkbook. See implicit containing workbook reference Rubberduck inspection for more info.

Related

Using VBA in Excel - Static variable not retained when Multipage object has change event (changing pages)

I am having trouble with losing static variables within an Excel user form.
I have been working on a routine for excel. I am a (very) novice coder.
I am attempting to populate a cell range to an array. I have been able to do this without issue.
However, when I attempt to store the array as a *static * variable, the variable is not retained for as long as I want it to be.
I think the problem occurs when another page is selected in the multipage, the static variable is cleared.
Code is something like this:
Sub UserForm_Initialize ()
static myArray () as variant
dim myRange as range
set myRange = [namedrange]
myArray=myRange
msgbox myArray(0,0) 'this works fine
call OtherSub
end sub
sub OtherSub ()
msgbox myArray(0,0) 'this returns a blank
end sub
The first sub of code shows the array element just fine. The array element is blank in the second sub.
I have tried:
Declaring the array as a public variable, but this returns an error (I think that variables within user forms are private by default and cannot be changed).
using a very small variable (a simple string)
writing code in a module before opening the user form (variable is not retained).
I am aware that I can just write data to a cell range, but this would defeat the purpose. I was hoping to avoid multiple instances of reading large arrays from the worksheet.
This might explain it a bit clearer. Moving MyArray outside of the Procedure will set it's scope to a Module Level, making it usable through other subs within that module. You will generally want to keep the scope of your variables to the lowest level required. The other option would be to pass your variable as a parameter to your other procedure.
Option Explicit
Dim MyArray() As Variant ' Private Module Level Scope
Public ExampleVariable As String ' Public Module Level Scope (Not required, just showing an example.)
Sub UserForm_Initialize()
Dim myRange As Range ' Procedure Level Scope
Set myRange = [namedrange]
MyArray = myRange
MsgBox MyArray(0, 0) 'this works fine
Call OtherSub
End Sub
Sub OtherSub()
MsgBox MyArray(0, 0) 'this returns a blank
End Sub

How to Use the Find Function

I have recently Written some code to take an input from a userform text box and search for it in my database. If found I would like it to return the value and insert it into cell A1149; I have written the below code but it gives me error #424 "Object Required". I am very new to VBA so any and all help is greatly appreciated.
Private Sub CMDSearch_Click()
Dim pesquisa As Range
Set pesquisa = Worksheets("Petrobras").Activate.Range("$W:$W") _
.Find(What:=Opp_Num_Search.Value, LookIn:=xlValues, Lookat:=xlWhole).Activate
Worksheets("Petrobras").Range(A1149).Value = pesquisa.Value
UserForm1.Hide
End Sub
Range.Activate doesn't return anything, it's like a Sub procedure:
Public Sub DoSomething()
' does something...
End Sub
If you did Set foo = DoSomething.Something, you'd get the same error: an object is required, otherwise that .Something member call is illegal (except now the error would be at compile-time and not run-time, because of how binding works).
Set pesquisa = Worksheets("Petrobras").Activate...
You don't want to Activate any sheets.
Part of the problem is the implicit late-binding going on: Worksheets returns an Object, so everything you wrote after that, all these chained member calls, can only be resolved at run-time.
Make it early-bound, by declaring an explicit Worksheet variable:
Dim sheet As Worksheet
Set sheet = Worksheets("Petrobras")
Now if you want to activate it, you can do sheet.Activate (but you don't need to). And if you want to get a Range from that worksheet, you can make a .Range member call, and the IDE will now help you do it:
Dim result As Range
Set result = sheet.Range("$W:$W").Find(...)
NEVER chain any member calls to what Range.Find returns. If the search turned up a result, you have a Range object. If it didn't, you have Nothing - and any member call made against Nothing will always raise run-time error 91.
Validate the search result first:
If result Is Nothing Then
MsgBox "Could not find '" & Opp_Num_Search.Value & "' in column W."
Exit Sub
End If
Or:
If Not result Is Nothing Then
sheet.Range("A1149").Value = result.Value
End If
Note that A1149 is a string literal representing a cell address, and as such it must be surrounded with double quotes ("). If it's not in double quotes and it looks like a valid variable name, VBA will treat it as ..a variable... and that will cause yet another error (1004), because Range will be rather unhappy to work with a Variant/Empty value.
To prevent VBA from "declaring" on-the-fly variables with a typo (and causing hard-to-find bugs), make sure you have Option Explicit at the very top of every module in your project.
One last thing:
UserForm1.Hide
This hides the default instance of UserForm1, which may or may not be the current object / instance that's shown - and the form itself has no way to know how it was shown:
UserForm1.Show '<~ shows the default instance
With New UserForm1
.Show '<~ shows a new (non-default) instance
End With
For this reason, you should avoid referring to the default instance in a form's code-behind. Use Me instead:
Me.Hide
That way you're referring to whatever instance of the form is currently shown, whether that's the default instance or not. See UserForm1.Show for more information, tips, pitfalls and common mistakes involving userforms.
Note that Rubberduck has quite a few inspections that can identify and warn you about (and often, automatically fix) most of these problems. Rubberduck is a free and open-source VBIDE add-in project I manage.

how do I change the code in a sheet by applying a module

I made a module that I want to be a plugin. My issue is I want it to rerun every time I open the sheet and only the sheet I activated it on.
I found a solution on how to do it by using the
Private Sub Worksheet_Activate()
Call Func
End sub
inside the sheet, I applied the macro to. How can I make it apply this code snipped to the currently active sheet when automatically when I activate the macro.
Basically, when I use my plugin while I am on sheet x I want it to apply
Private Sub Worksheet_Activate()
Call Func
End sub
this function to that specific sheet and that specific sheet only
Just to clarify better.
I want to sit on the sheet that has NO VBA code associated,
activate my add-in
and have a predefined code-block run in the context of the activated sheet.
You need to add the following codes to your Add-In, it is important to add them to the right module (as indicated by the comments in the code).
' ThisWorkbook
Option Explicit
Private Sub Workbook_Open()
InitializeMyEventHandler ThisWorkbook.Application
End Sub
' modMyEventHandler
Option Explicit
Public g_mehHandler As clsMyEventHandler
Public Sub InitializeMyEventHandler(eapApplication As Application)
Set g_mehHandler = New clsMyEventHandler
Set g_mehHandler.eapApplication = eapApplication
End Sub
' clsMyEventHandler
Option Explicit
Public WithEvents eapApplication As Application
Private Sub eapApplication_SheetActivate(ByVal Sh As Object)
If TypeName(Sh) = "Worksheet" Then
Dim ewsSheet As Worksheet: Set ewsSheet = Sh
Debug.Print "Worksheet activated. Workbook: " & ewsSheet.Parent.Name & ", Worksheet: " & ewsSheet.Name
End If
End Sub
The first part in the Add-In's ThisWorkbook module makes sure that our event handler class will be initialized each time the Add-In is loaded (when a new Excel Application is opened).
The second part is a normal public module, which is capable of holding global public objects, in our case an object of class clsMyEventHandler. At initialization g_mehHandler will be set to a new instance of clsMyEventHandler, and its member, eapApplication will be assigned to the Application object received from the Add-In's Workbook_Open function.
The third part must be added as a class module. It has a variable that is declared with WithEvents, this means that any time an event (e.g. activation of a new sheet) happens, the appropriate functions of this class module will be called. If a SheetActivate event is fired in eapApplication, then eapApplication_SheetActivate function of this class will be called. The function is selected based on its name (object name + underscore + event name). After you declared eapApplication (Public WithEvents eapApplication As Application), you will be able to select eapApplication from the ComboBox above the VBA code, which usually contains the word (General). If you selected eapApplication there, you will be able to select events to which you want to react from the ComboBox next to it.

Having a public variable available in userform / sheet objects

I've spent the last hour reading pages about variable scope in various flavours of excel vba, and could not find a definite documentation reference addressing my scope problem... even though i'm convinced it is such a classic. Oh well, here goes.
I've got a workbook that contains just one sheet and one userform. I have a list of students sitting in column 1 on my sheet. I would like to :
load this list up into some global Collection variable named students_list (i do this using a Workbook-Open() procedure in the ThisWorkbook object)
use the contents of students_list to initialize a listbox in my userform
remove elements from students_list when a button on my userform is clicked on
All i need is a variable that is seen from within my userform's procedures, as well as from inside the ThisWorkbook object.
I tried declaring it as public, global, in the sheet's code, in the userform, in ThisWorkbook, in a separate module dedicated to globals... I just can't seem to find the right way to have the students_list variable visible from everywhere.
What am I missing ? My apologies for this question that should be so basic and yet beats me :-/
Place the declaration of your Public variables inside a Module (use Insert / Module from the menu to create one, if you don't already have one). The scope will then extend to your whole project.
So in a Module (e.g. Module1) have:
Public foo As Integer
And in the worksheet (e.g. Sheet1) code have:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
foo = 4
MsgBox "foo set to 4"
End Sub
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
MsgBox "foo = " & foo
End Sub
If you were to place the declaration in the code for ThisWorkbook you would need to reference it as Thisworkbook.foo because, although it is accessible from any part of the code, it is a variable specific to that ThisWorkbook object.
So, in the code for ThisWorkbook have:
Public foo As Integer
And in the worksheet (e.g. Sheet1) code have:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
ThisWorkbook.foo = 4
MsgBox "foo set to 4"
End Sub
Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
MsgBox "foo = " & ThisWorkbook.foo
End Sub

How do you store a worksheet reference in a VBA object?

This is going to seem trivial to those of you steeped in Excel object programming but it's beat me.
In the past, I've done the following in Excel's vba to restore the activesheet before exiting a subroutine..
sub foo()
dim cursheet
cursheet = ActiveSheet
someOtherSheet.activate
....
cursheet.activate
end sub
That works fine. I attempted to do something similar using objects and after several different approaches, wrote the following in a new Problem class...
''''''''''''''''''''''
' sheet property
''''''''''''''''''''''
Public Property Get sheet() As Worksheet
Set sheet = psheet
End Property
Public Property Let sheet(Value As Worksheet)
Set psheet = Value
End Property
Public Sub saveCursheet()
Me.sheet = ActiveSheet
End Sub
Public Sub activateSheet()
Me.sheet.Activate
End Sub
In my code, I invoke the methods this way...
Sub TallyQuizScore()
Dim curStudent As Problem
Set curStudent = New Problem
curStudent.saveCursheet
Worksheets("QuizTallies").Activate
...
curStudent.activateSheet
End Sub
When I attempt to execute curStudent.activateSheet, I get an error saying I need an object. So I reran the calling code and stepped through the saveCursheet method. I see the activesheet get stored but notice that the sheet object disappears as soon as I hit the setter's end property line. I don't know if that's an artifact of the debugger or if the sheet really does get tossed when I hit the end property line but whatever it is, the object is gone when I attempt to reactivate it when I'm done.
The frustrating thing is what I really wanted to write in my caller was
curStudent.sheet = Activesheet
and
curStudent.sheet.Activate
by somehow inheriting the builtin worksheet methods but that led to a rabbit's warren of code as I tried to make it work.
So three questions:
Why did the sheet I stored in saveCursheet disappear?
What do I need to change to make the code work?
What do I need to do differently from the above approach to make the curStudent.sheet = Activesheet and it's partner, curStudent.sheet.Activate approach work?
You need a module-level variable to store the value while your code is doing other things. Note that it's private.
Also, as caught by ja72, in the case of objects it's Set, not Let:
UNTESTED:
Private m_Sheet as Worksheet
Public Property Get Sheet() As Worksheet
Set sheet = m_Sheet
End Property
Public Property Set Sheet(Value As Worksheet)
Set m_Sheet = Value
End Property
Public Sub saveCursheet()
Me.Sheet = ActiveSheet
End Sub
Public Sub activateSheet()
Me.m_Sheet.Activate
End Sub

Resources