All,
I have been struggling with this for a while: is it possible to pass an object to a function?
Here is what I am trying to accomplish:
Get the name of which control was pressed on a form (as object?)
Send the control's name to function "MyFunction" (as reference?)
Disable that same control on "MyFunction"
Called from form1:
Private Sub button1_Click()
Dim caller As String
caller = Form1.ActiveControl.Name
MyFunction(caller)
End Sub 'I'm able to pass it as a string
button1_Click calls MyFunction and passes caller to it:
Private Sub MyFunction(caller As String)
caller.Enabled = False
End Sub
I understand this will not work as a string. How could I possibly do it as an actual object?
Thank you!
There is little problem passing an object to a sub:
Private Sub Disable(c As Control)
MsgBox c.Name
c.Enabled = False
End Sub
Private Sub CommandButton1_Click()
Disable CommandButton1
End Sub
Private Sub CommandButton2_Click()
Disable CommandButton2
End Sub
Private Sub CommandButton3_Click()
Disable CommandButton3
End Sub
In the above I created a userform with three buttons, they say who they are when clicked and are then disabled.
Note that
Disable CommandButton1
can be replaced by
Disable Me.ActiveControl
or even just
Disable ActiveControl
You can even use Variant like so (rough example):
Private Sub CommandButton1_Click()
EnableDisable ActiveControl, "disable"
End Sub
Private Sub EnableDisable(control As Variant, status As String)
If status = "enabled" Then
control.Enabled = True
Else
control.Enabled = False
End If
End Sub
John Coleman's example is better than mine, though.
Related
Update:
It seems Visio and Excel on my PC do not use the same Office Object Libraries, at least that is what a look at the used references tells me. Visio uses 15.0, Excel uses 16.0, we use different subscriptions for both, Excel is part of the MS office 365 ProPlus Package, Visio is separate.
I tested it on a PC with a current subscription of Visio with the 16.0 Office Object Libraries. This time closing the Userform did not cause a crash. So I guess the problem was with the old version. If anybody has the ability to cross check this and test the code in a Excel 15.0 installation that would be great.
Original Post
I have been using an Observer Implementation in VBA to create some "better" UserForms that implement an Interface.
The solution works great as far as I'm concerned, but there is one minor problem:
Whenever I close the UserForm by pressing the FormControlMenu Close Control (the red X in the top/right corner) my application crashes with a "Out of Stack Space Error".
Crashing occurs in the following way: I get the Message box with the error (standart VBA), when I close it there seems nothing amiss, but as soon as I try to run another piece of code (any) Visio will crash to the Desktop.
Now the strange part: I actually catch the vbQueryClose event, cancel it and run my own closing routine which only hides the userform. When closing (hiding) the UserForm via a commandButton the same way (me.hide), the error does not occur.
This happens only in Visio, the exact same code causes no error/crashing in Excel!
I hope someone with some more knowledge on the whole Reference/Object/COM-Business can shed some light into this
The Code:
Code also available as zipped file (no Excel/Visio Files, just the exported modules) so you don't have to copy/paste and create UserForms: https://www.dropbox.com/s/ziqjv2umcy3co5t/ObserverExample.zip?dl=0
The actual Observer Implementation is a bit longer, but this code is the minimal verifiable example.
Module1 (Module):
'#Folder("ObserverTest")
Option Explicit
Sub StartTest()
With New Foo
.Test
End With
End Sub
Foo (Class):
'#Folder("ObserverTest")
Option Explicit
Private WithEvents myObs As Observer
Private myView As IBar
Public Sub Test()
Set myView = New Bar
Dim myViewAsObservable As IObservable
Set myViewAsObservable = myView
myViewAsObservable.AddObserver myObs
Set myViewAsObservable = Nothing
myView.Show
Debug.Print myView.howClosed
End Sub
Private Sub Class_Initialize()
Set myObs = New Observer
End Sub
Private Sub Class_Terminate()
Set myObs = Nothing
End Sub
Private Sub myObs_Notify(source As Object, arg As Variant)
If VarType(arg) = vbString Then
Debug.Print arg
End If
End Sub
IBar (Class)
'#Folder("ObserverTest")
Option Explicit
Public Sub Show(): End Sub
Public Property Get howClosed() As String: End Property
IObservable(Class)
'#Folder("ObserverTest")
Option Explicit
Public Sub AddObserver(ByVal obs As Observer): End Sub
Bar (UserForm)
'#Folder("ObserverTest")
Option Explicit
Implements IBar
Implements IObservable
Private obsCol As Collection
Private cancelHow As String
'---IBar Stuff
Private Sub IBar_Show()
Me.Show
End Sub
Private Property Get IBar_howClosed() As String
IBar_howClosed = cancelHow
End Property
'--- Closing Stuff
Private Sub btCancel_Click()
cancelHow = "Closed by pressing the >Cancel< Button"
onCancel
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True
cancelHow = "Closed by pressing the >X< Control"
onCancel
End If
End Sub
Private Sub onCancel()
Me.hide
End Sub
'---Observer Stuff
Private Sub UserForm_Initialize()
Set obsCol = New Collection
End Sub
Private Sub UserForm_Terminate()
Set obsCol = Nothing
End Sub
Private Sub tbTest_Change()
Notify Me.tbTest.Text
End Sub
Private Sub IObservable_AddObserver(ByVal obs As Observer)
obsCol.Add obs
End Sub
Private Sub Notify(ByVal arg As Variant)
Dim obs As Observer
For Each obs In obsCol
obs.Notify Me, arg
Next obs
End Sub
Observer (Class)
'#Folder("ObserverTest")
Option Explicit
Public Event Notify(source As Object, arg As Variant)
Public Sub Notify(ByVal source As Object, ByVal arg As Variant)
RaiseEvent Notify(source, arg)
End Sub
The Problem lay apparently with the version of the Office Object Library I was using. Using the 16.0(2016) Library instead of the 15.0(2013) solved the problem for me.
If anybody has a good explanation, post it here and I will accept it as answer.
I'm trying to write a macro that will have many different buttons. I would like all the buttons to trigger the same function but I need to set a variable in the function to identify which button triggered the macro.
I'm not really sure how I should do this. Is there a way I can say like variableA = ClickedButton()?
Any advice is appreciated
Hi , I think something like this should work
Private Sub CommandButton1_Click()
myfunction "b1"
End Sub
Private Sub CommandButton2_Click()
myfunction "b2"
End Sub
Private Sub CommandButton3_Click()
myfunction "b3"
End Sub
sub myfunction (v as variant)
msgbox v
end sub
Try the following
In the button module:
Sub CommandButton1_Click()
myFunction btn_name:= Me.Name
End Sub
In a normal module:
Public Function myFunction(btn_name As String)
MsgBox "The button pressed was:" & btn_name
'do stuff
End Sub
Doing this, you pass the name of the pressed button (Me.Name) to the function as a variable, so it can be used generally inside that function.
I have a Problem with a Userform which I called "ComboTest2". It only consists of two Comboboxes. If I instantiate the USerform as an object then the following Code doesn't work in the sense that the second combobox of the Userform doesn't contain the desired data.
Sub FillCombo(ByVal row As Long)
Dim rgCities As Range
Set rgCities = Worksheets("Tabelle2").Range("B2:D2").Offset(row)
ComboTest2.ComboBox2.Clear
ComboTest2.ComboBox2.List = WorksheetFunction.Transpose(rgCities)
ComboTest2.ComboBox2.ListIndex = 0
End Sub
Sub FillMain()
Dim ComboForm2 As ComboTest2
Set ComboForm2 = New ComboTest2
ComboForm2.Show
End Sub
UserForm-Code:
Private Sub CommandButton1_Click()
Me.Hide
End Sub
Private Sub CommandButton2_Click()
Me.Hide
End Sub
Private Sub ComboBox1_Change()
FillCombo ComboBox1.ListIndex
End Sub
Private Sub UserForm_Initialize()
ComboBox1.List = Worksheets("Tabelle2").Range("A2:A5").Value
ComboBox1.ListIndex = 0
FillCombo ComboBox1.ListIndex
End Sub
But if I use the "default instantiation" by VBA which means that I change the FillMain Sub to:
Sub FillMain2()
Dim ComboForm2 As ComboTest2
Set ComboForm2 = New ComboTest2
'ComboForm2.Show
ComboTest2.Show
End Sub
Then everything is fine. Why is that so?
Best regards
It's because FillCombo is referring to the userform by name, and therefore to the default instance (you're actually creating a new instance of the form). If that code is not in the userform, and I'm not sure why you would have it outside the form, you should pass the combobox as an argument to it.
I want to pass a CommandButton as an argument.
Example:
Sub calc(btn as button)
btn.Caption = "Something"
End Sub
Private Sub CommandButton1_Click()
calc(CommandButton1)
End Sub
Private Sub CommandButton2_Click()
calc(CommandButton2)
End Sub
Is something like the above possible? If yes how can I do it?
edit
Thanks for your response, but I dont get it. So it looks like this now:
Public Sub calc(ByRef btn as Object)
btn.Caption = "Something"
End Sub
Private Sub CommandButton1_Click()
calc(CommandButton1)
End Sub
Private Sub CommandButton2_Click()
calc(CommandButton2)
End Sub
Maybe someone can explain it to me in more detail, because Im very new to VBA.
You need:
Sub calc(btn As MSForms.CommandButton)
btn.Caption = "Something"
End Sub
And you must invoke it following the rules:
calc CommandButton1 // best
call calc (CommandButton1) // ok but verbose
calc (CommandButton1) // type mismatch!
(The type mismatch is because the parentheses evaluate CommandButton1 which results in its default property (a string) which is incompatible with the method argument type)
This is the sub:
Public Sub temp(ByRef cmdb As Object)
cmdb.Caption = "somethine else"
End Sub
This is how you would call it
call sub (commandbutton_1)
I have the following button on a Form:
Private Sub CommandButton1_Click()
Dim pass As String
pass = UserForm1.TextBox1
Unload UserForm1
End Sub
I then have a Module called Module1:
Public Sub Login()
...
UserForm1.Show
driver.findElementByName("PASSWORD").SendKeys pass
...
End Sub
The idea is whatever password the users enters into the input box will be assigned to the variable pass. What I'm having trouble doing however is passing pass from UserForm1 into Module1's Login sub.
I would of thought adding something like Module1.Login (pass) to my form before I unload it would work, however that doesn't seem to pass anything. Any help would be much appreciated. Thanks.
Don't declare the variable in the userform. Declare it as Public in the module.
Public pass As String
In the Userform
Private Sub CommandButton1_Click()
pass = UserForm1.TextBox1
Unload UserForm1
End Sub
In the Module
Public pass As String
Public Sub Login()
'
'~~> Rest of the code
'
UserForm1.Show
driver.findElementByName("PASSWORD").SendKeys pass
'
'~~> Rest of the code
'
End Sub
You might want to also add an additional check just before calling the driver.find... line?
If Len(Trim(pass)) <> 0 Then
This will ensure that a blank string is not passed.
Siddharth's answer is nice, but relies on globally-scoped variables. There's a better, more OOP-friendly way.
A UserForm is a class module like any other - the only difference is that it has a hidden VB_PredeclaredId attribute set to True, which makes VB create a global-scope object variable named after the class - that's how you can write UserForm1.Show without creating a new instance of the class.
Step away from this, and treat your form as an object instead - expose Property Get members and abstract away the form's controls - the calling code doesn't care about controls anyway:
Option Explicit
Private cancelling As Boolean
Public Property Get UserId() As String
UserId = txtUserId.Text
End Property
Public Property Get Password() As String
Password = txtPassword.Text
End Property
Public Property Get IsCancelled() As Boolean
IsCancelled = cancelling
End Property
Private Sub OkButton_Click()
Me.Hide
End Sub
Private Sub CancelButton_Click()
cancelling = True
Me.Hide
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
cancelling = True
Cancel = True
Me.Hide
End If
End Sub
Now the calling code can do this (assuming the UserForm was named LoginPrompt):
With New LoginPrompt
.Show vbModal
If .IsCancelled Then Exit Sub
DoSomething .UserId, .Password
End With
Where DoSomething would be some procedure that requires the two string parameters:
Private Sub DoSomething(ByVal uid As String, ByVal pwd As String)
'work with the parameter values, regardless of where they came from
End Sub