Suppose, for instance, that I want a method that adds a ComboBox. Maybe I try this
Public Sub AddComboBox()
Dim cb As MSForms.ComboBox
Set cb = <Calling form module>.Controls.Add("Forms.ComboBox.1")
End Sub
How can I get <Calling form module>?
As others have said, pass the instance of the form to class method. Unlike others, I'm going to add:
Declare the argument AS
MSForms.UserForm
Pass the parameter ByVal.
If calling from the UserForm itself,
use the Me keyword in the call.
He's a brief example:
' <Module1.bas>
Option Explicit
Sub Main()
UserForm1.Show vbModeless
UserForm2.Show vbModeless
End Sub
' </Module1.bas>
' <UserForm1.frm>
Option Explicit
Private Sub UserForm_Activate()
Dim c As Class1
Set c = New Class1
c.AddComboBox Me
End Sub
' </UserForm1.frm>
' <UserForm2.frm>
Option Explicit
Private Sub UserForm_Activate()
Dim c As Class1
Set c = New Class1
c.AddComboBox Me
End Sub
' </UserForm2.frm>
' <Class1.cls>
Option Explicit
Public Sub AddComboBox(ByVal MSForms_UserForm As MSForms.UserForm)
Dim cb As MSForms.ComboBox
Set cb = MSForms_UserForm.Controls.Add("Forms.ComboBox.1")
End Sub
' </Class1.cls>
I think you're writing this the wrong way. Instead of trying to determine who called the method, just pass the <Calling Form Module> to AddComboBox() as an argument. Like this:
Public Sub CallToAddComboBox()
AddComboBox(<Calling form module>)
End Sub
Public Sub AddComboBox(CallingFormModule as <Module Object Type>)
Dim cb As MSForms.ComboBox
Set cb = CallingFormModule.Controls.Add("Forms.ComboBox.1")
End Sub
Related
I'm trying to build a simple Userform with a single text box with scroll bars for informational messages.
Unfortunately, I'm getting a Run-time Error 438, Object doesn't support this properly or method error.
My testing code is simple
'three different attempts
Userform including different attempts
Dim inString As String
Sub txtBoxVal(passedStr As String)
inString = passedStr
MyMessageBox = inString
End Sub
public Property get txtBoxVal2(passedStr As String) as string
inString = passedStr
MyMessageBox = inString
End Sub
Public Sub update()
MyMessageBox.Text = publicStrVarDeclaredInFunction
End Sub
Private Sub UserForm_Initialize()
End Sub
Sheet1 Code
Public publicVar As String
Private Sub userformTest()
Dim myForm As UserForm
Dim testString As String
testString = "This is a test string"
Set myForm = New MyUserForm
With myForm
' My 3 different way attempts
Call .txtBoxVal(testString)
.update publicVar
Call .txtBoxVal2(testString)
.Show
End With
End Sub
Anyone have any insights where I'm making my mistake?
Assuming that your Userform's name is MyUserForm and MyMessageBox is the TextBox control:
You should Dim myForm As MyUserForm instead of Dim myForm As UserForm.
Your update sub does not have an argument but you passed publicVar in .update publicVar, you will need to declare the argument in your update sub like this:
Public Sub update(argNewText As String)
MyMessageBox.Text = argNewText
End Sub
Call .txtBoxVal2(testString) won't work for 2 reason:
a) txtBoxVal2 is a Property, not a Sub so you do not use Call. You assign value to txtBoxVal2 property by myForm.txtBoxVal2 = "new value"
b) txtBoxVal2 is a Get property so you can only retrieve txtBoxVal2 value, in order to allow txtBoxVal2 be assigned a value, you have to change to Let:
Public Property Let txtBoxVal2(passedStr As String)
inString = passedStr
MyMessageBox = inString
End Property
=====================
I personally prefer to use Let property approach in this case so your code will look something like this:
Sheet1
Private Sub userformTest()
Dim testString As String
testString = "This is a test string"
Dim myForm As MyUserForm
Set myForm = New MyUserForm
myForm.MessageText = testString
End Sub
MyUserForm
Private inString As String
Public Property Let MessageText(passedStr As String)
inString = passedStr
MyMessageBox.Text = inString
End Property
Try this:
Make a simple UserForm called 'MyMsgBox' with a textbox called 'TextBox1' and a command button called 'CmdOK'.
Put this code into the UserForm:
Option Explicit
''' On form show:
Private Sub UserForm_Initialize()
''' Put text into textbox
TextBox1 = pstMessage
'''BEEP (optional)
Beep
End Sub
''' Close form on OK
Private Sub CmdOK_Click(): Unload Me
End Sub
Put this code in any Standard Module:
Option Explicit
Public pstMessage$
Sub ShowMsg()
''' Put the message into pstMessage
pstMessage = "This is a test string"
''' Run MyMsgBox
MyMsgBox.Show
End Sub
I am using this excellent tutorial as a base to create a simple "Hello World" Excel VBA project leveraging on Mathieu Guindon's concept of writing Object-Oriented Programming VBA code, discussed in a series of articles on the https://rubberduckvba.wordpress.com/ blog.
I have created a "bare bones" project without any Model containing an Excel worksheet (HelloSheet), a View, a ViewAdapter (including ViewCommands and ViewEvents interfaces) and a Controller. The VBA project compiles without errors but when I try to run the "application entry" macro I get the dreaded "Run-time error 438: Object doesn't support this property or method". This happens inside the Class_Initialize() sub of my View class where I have declared "Private WithEvents sheetUI As HelloSheet" and try to set "sheetUI = HelloSheet".
Here is an overview of my project tree, as seen in the RubberDuck VBIDE.
I have tried updating the VBA project references to exactly match those of the "Battleship" sample project. I also tried the two different approaches to implementing the Lazy Object / Weak Reference in the View class - the one in the "Battleship (WorksheetView).xlsm" linked in the original article vs the approach used in the latest version on GitHub, more specifically:
Private adapter As ***IWeakReference***
Private WithEvents sheetUI As HelloSheet
Private Sub Class_Initialize()
sheetUI = HelloSheet
End Sub
Private Property Get ViewEvents() As ISheetViewEvents
Set ViewEvents = adapter ***.Object***
End Property
VS
Private adapter As ***SheetViewAdapter***
Private WithEvents sheetUI As HelloSheet
Private Sub Class_Initialize()
sheetUI = HelloSheet
End Sub
Private Property Get ViewEvents() As ISheetViewEvents
Set ViewEvents = ***adapter***
End Property
..but the "Run-time error 438: Object doesn't support this property or method" persisted.
Below is all relevant code split in sheets, classes, interfaces etc.:
1) HelloSheet (regular Excel sheet code-behind):
'#Folder("HelloWorld.View.Worksheet")
Option Explicit
Public Event DoubleClick(ByVal clickedRow As Integer)
Public Sub HideShape(shapeName As String)
Dim currentShape As Shape
Set currentShape = Me.Shapes(shapeName)
currentShape.Visible = msoFalse
End Sub
Public Sub ShowShape(shapeName As String)
Dim currentShape As Shape
Set currentShape = Me.Shapes(shapeName)
currentShape.Visible = msoTrue
End Sub
Public Sub OnLaunchCommand()
ShowShape ("WarningTriangle")
End Sub
Public Sub TempManualHide()
HideShape ("WarningTriangle")
End Sub
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Cancel = True
RaiseEvent DoubleClick(Target.Row)
End Sub
Public Sub PreviewSelectedRecord(ByVal selectedRow As Integer)
Me.Cells(1, 1).Value2 = "Row is " & CStr(selectedRow)
End Sub
2) SheetView class:
'#Folder("HelloWorld.View.Worksheet")
Option Explicit
Implements ISheetViewCommands
Private adapter As SheetViewAdapter ' IWeakReference
Private WithEvents sheetUI As HelloSheet
Private Sub Class_Initialize()
sheetUI = HelloSheet
End Sub
Private Property Get ViewEvents() As ISheetViewEvents
Set ViewEvents = adapter '.Object
End Property
':GameSheet event handlers
':Messages sent from the view
':***************************
Private Sub sheetUI_DoubleClick(ByVal clickedRow As Integer)
ViewEvents.PreviewSelectedRecord clickedRow
End Sub
':IGridViewCommands
':Messages sent from the controller
':*********************************
Private Property Set ISheetViewCommands_Events(ByVal value As ISheetViewEvents)
Set adapter = value ' WeakReference.Create(Value)
End Property
Private Property Get ISheetViewCommands_Events() As ISheetViewEvents
Set ISheetViewCommands_Events = adapter '.Object
End Property
Private Sub ISheetViewCommands_OnLaunchCommand()
sheetUI.OnLaunchCommand
End Sub
Private Sub ISheetViewCommands_OnPreviewSelectedRecord(ByVal selectedRow As Integer)
sheetUI.PreviewSelectedRecord selectedRow
End Sub
3) ISheetViewEvents interface:
'#Folder("HelloWorld.View")
'#Interface
Option Explicit
Public Sub PreviewSelectedRecord(ByVal selectedRow As Integer)
End Sub
4) ISheetViewCommands interface:
'#Folder("HelloWorld.View")
'#Interface
Option Explicit
'#Description("Gets/sets a weak refererence to the view events.")
Public Property Get Events() As ISheetViewEvents
End Property
Public Property Set Events(ByVal value As ISheetViewEvents)
End Property
Public Sub OnLaunchCommand()
End Sub
Public Sub OnPreviewSelectedRecord(ByVal selectedRow As Integer)
End Sub
5) SheetViewAdapter class (PredeclaredId / has default instance):
'#Folder("HelloWorld.View")
Option Explicit
'#PredeclaredId
Implements ISheetViewCommands
Implements ISheetViewEvents
Public Event OnPreviewCurrentSelectedRecord(ByVal selectedRow As Integer)
Private Type TAdapter
SheetViewCommands As ISheetViewCommands
End Type
Private this As TAdapter
Public Function Create(ByVal view As ISheetViewCommands) As SheetViewAdapter
With New SheetViewAdapter
Set .SheetViewCommands = view
Set view.Events = .Self
Set Create = .Self
End With
End Function
Public Property Get Self() As SheetViewAdapter
Set Self = Me
End Property
'#Description("Gets/sets a reference that exposes commands to send to the view.")
Public Property Get SheetViewCommands() As ISheetViewCommands
Set SheetViewCommands = this.SheetViewCommands
End Property
Public Property Set SheetViewCommands(ByVal value As ISheetViewCommands)
Set this.SheetViewCommands = value
End Property
':IGridViewEvents
':Messages sent from the view
':***************************
Private Sub ISheetViewEvents_PreviewSelectedRecord(ByVal selectedRow As Integer)
RaiseEvent OnPreviewCurrentSelectedRecord(selectedRow)
End Sub
':IGridViewCommands
':Messages sent from the controller
':*********************************
Private Property Set ISheetViewCommands_Events(ByVal value As ISheetViewEvents)
Err.Raise 5, TypeName(Me), "Invalid use of property"
End Property
Private Property Get ISheetViewCommands_Events() As ISheetViewEvents
Set ISheetViewCommands_Events = Me
End Property
Private Sub ISheetViewCommands_OnLaunchCommand()
this.SheetViewCommands.OnLaunchCommand
End Sub
Private Sub ISheetViewCommands_OnPreviewSelectedRecord(ByVal selectedRow As Integer)
this.SheetViewCommands.OnPreviewSelectedRecord selectedRow
End Sub
6) HelloController class:
'#Folder("HelloWorld")
Option Explicit
Private viewCommands As ISheetViewCommands
Private WithEvents viewAdapter As SheetViewAdapter
Public Sub Launch(ByVal adapter As SheetViewAdapter)
Set viewAdapter = adapter
Set viewCommands = adapter
viewCommands.OnLaunchCommand
End Sub
Private Sub viewAdapter_OnPreviewCurrentSelectedRecord(ByVal selectedRow As Integer)
viewCommands.OnPreviewSelectedRecord selectedRow
End Sub
7) And finally the "Macros" standard module which serves as an entry point. This is where I encounter the error (the "Set view = New SheetView" line):
'#Folder("HelloWorld")
'#Description("Application entry points.")
Option Explicit
'#Ignore MoveFieldCloserToUsage
Private controller As HelloController
Public Sub LaunchWorksheetInterface()
Dim view As SheetView
Set view = New SheetView
Set controller = New HelloController
controller.Launch SheetViewAdapter.Create(view)
End Sub
Supposing I could get around the entry-level error, I would expect a very simple functionality:
1) A hidden Excel shape is made visible on the HelloSheet (the OnLaunchCommand);
2) When double-clicking on a cell, the row it is located on would be reported in cell A1 of the same worksheet (the Worksheet_BeforeDoubleClick event).
Obviously this amount of code for such simple tasks is overkill - my idea is once I get these basics working to add Model classes to the project and map them to certain areas (i.e. Tables/ListObjects) inside the Workbook.
Any help will be greatly appreciated! And kudos to anyone who has made it to the end of this rather long post :)
Private WithEvents sheetUI As HelloSheet
Private Sub Class_Initialize()
sheetUI = HelloSheet
End Sub
sheetUI is an object reference, assigning it requires the Set keyword:
Private WithEvents sheetUI As HelloSheet
Private Sub Class_Initialize()
Set sheetUI = HelloSheet
End Sub
Error 438 is thrown whenever you try to access the default member of a Worksheet class, since Worksheet has no default member - this code reproduces the error from the immediate pane:
?Sheet1
Or:
foo = Sheet1
Rubberduck inspections should have warned about this, under "Code Quality Issues":
Object variable 'sheetUI' is assigned without the 'Set' keyword.
I couldn't quite find what I'm looking for but maybe you can help me anyway.
My problem is that I have a userform where the user has to make an input. I want to store that input and use it later in a different module i.e. paste it into a cell. The simple solution should be to just make it a public variable, but for some reason it won't work. Here is the code I tried to use:
Userform:
Option Explicit
Public VarBezeichnungReifenliste As String
Private Sub CommandButton3_Click()
VarBezeichnungReifenliste = TextBox1.Value
Call Übertragen
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
Module:
Option Explicit
Public Sub Übertragen()
Worksheets("XY").Cells(1, 1).Value = VarBezeichnungReifenliste
End Sub
The error message says the variable is not declared (VarBezeichnungReifenliste) so i guess I didn't declare it publicly enough?
The userform itself is opened via a simple button on the worksheet using Userform1.Show. So nothing fancy here.
Publicly Enough
Solution1
UserForm1:
Option Explicit
Private Sub CommandButton3_Click()
VarBezeichnungReifenliste = TextBox1.Value
Module1.Übertragen
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
Module1:
Option Explicit
Public VarBezeichnungReifenliste As String
Sub Übertragen()
Worksheets("XY").Cells(1, 1).Value = VarBezeichnungReifenliste
End Sub
Conclusion
Just move the variable declaration
Public VarBezeichnungReifenliste As String
to a 'normal' module.
Solution2
UserForm1:
Option Explicit
Public VarBezeichnungReifenliste As String
Private Sub CommandButton3_Click()
VarBezeichnungReifenliste = TextBox1.Value
Module1.Übertragen
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
Module1:
Option Explicit
Sub Übertragen()
Worksheets("XY").Cells(1, 1).Value = UserForm1.VarBezeichnungReifenliste
Worksheets("XY").Cells(1, 1).Select
End Sub
Conclusion
Just use
Worksheets("XY").Cells(1, 1).Value = UserForm1.VarBezeichnungReifenliste
instead of
Worksheets("XY").Cells(1, 1).Value = VarBezeichnungReifenliste
in Module1.
Solution3
UserForm1:
Option Explicit
Public VarBezeichnungReifenliste As String
Private Sub CommandButton3_Click()
VarBezeichnungReifenliste = TextBox1.Value
Übertragen
End Sub
Private Sub CommandButton2_Click()
Unload Me
End Sub
Sub Übertragen()
Worksheets("XY").Cells(1, 1).Value = VarBezeichnungReifenliste
End Sub
Conclusion
Move everything into UserForm1.
VBA is weird about storing variables over the long-term. As a general rule of thumb, if you're able to interact with Excel workbooks in between a variable being saved and when you need to get the value, you can't count on that variable still holding its value.
The safest way to get around this is to just store your value in a cell of a hidden worksheet, and read it from there when you need it.
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.
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)