VBA How To Set Form In Function - vba

I add Form1.vb in my project but ı couldn't show it. Form1.show or Form1.ShowDialog didn't worked beacuse it type is class.
Here is the error message;
bc30109 'form1' is a class type and cannot be used as an expression
Solution must be some think like that.
Private Sub Form1Func()
Dim f1 As Object
f1 = Form1
f1.ShowDialog
End Sub
Private Sub OnButtonClick(cmd As Commands_e)
Select Case cmd
Case Commands_e.Export_As_SheetMetal
Form1Func()
Case Commands_e.AboutUs
Application.ShowMessageBox("AboutUS")
End Select
End Sub

Without knowing more about other actions in your program, I cannot pinpoint why you're being asked to provide an argument for show (possibly a named function, etc.), but this is a way to ensure a form is called as an object:
Dim obj As Object: Set obj = VBA.UserForms.Add("userform1")
obj.Show vbModeless
Specific to your error, the above code directly generates the userform object through vba.

Private Sub Form1Func()
Dim f1 As New Form1
f1.Show()
End Sub

Related

userform.show problems

I am making a word document to fill out a form upon clicking a button. I have made a basic form so far named 'NewSite' I have then made a button to click to open the form with the code:
Private Sub NewSite_Click()
NewSiteUserForm.Show
End Sub
This then pops up the error 'Object Required'
Any help will be appreciated!
edit**
I have figured it out...
I set the name of the form back to its original name of 'UserForm1' for ease of use. The code:
Private Sub NewSite_Click()
Dim Form As Object
Set Form = UserForm1
Form.Show
End Sub
semed to work.
If you named it NewSite, you will have to declare your variable as an object of that type and the initialize it. Do it like this:
Private Sub NewSite_Click()
Dim NewSiteUserForm As NewSite
Set NewSiteUserForm = New NewSite
NewSiteUserForm.Show
End Sub

Set values on User Form using form properties

Appologies if this has been asked already, but I cannot seem to find an answer. I want to set a do something on a UserForm as it is loaded up based on the value passed to it from a property. What I have is a button on an Excel worksheet which loads the user form as follows:
Sub button()
Dim fm As New UserForm1
fm.ValueToPass = "Hello"
fm.Show
End Sub
Behind the form is the following code:
Private myString As String
Public Property Let ValueToPass(ByVal x As String)
myString = x
End Property
Private Sub UserForm_Initialize()
If myString = "Hello" Then
'Do something on my form
else
'Do something else on my form
end if
End Sub
The problem is that when the form is loaded up, the myString is empty. I believe that the reason is that the form is initialised before the property ValueToPass is set. What is the best solution to this?
You probably think that when you call:
fm.ValueToPass = "Hello"
in Private Sub UserForm_Initialize() it is possible that the myString value is passed before initializing the form. E.g., you are somehow expecting that myString can be something different than "" (thus you have the condition). This is not the case - first the _Initialize constructor is executed and then anything else is carried out:
With your code, you need somehow to tell the form, that it should update its label. Consider this inside the form:
Public Sub ShowForm()
Me.Label1 = myString
Me.Show
End Sub
Then in the module, call it like fm.ShowForm instead of fm.Show.
Actually, it is a good idea, if you work with userforms, following the Model-View-Controller pattern. For this you would need a separate class.
Userform closes after "End Sub" without ever calling "Unload Me"
Easiest way:
Sub button()
Dim fm As New UserForm1
fm.Label1.Caption = "Hello"
fm.Show
End Sub
you should be using the initialize event for this:
Sub OpenForm()
MyForm.show
end sub
Then in the initialize event of the userform, put this:
Me.LabelName.Caption = "Your label text"
(double-click the userform to view its code, then select from the top right drop-down "Initialize".)

Passing a parameter to an event handler procedure

I am generating a scripting dictionary using one button on a userform, using it to populate a listbox, and then need to use that same dictionary using a second button on the form. I have declared my dictionary either using early binding as so:
Dim ISINDict As New Scripting.Dictionary
or late binding as so
Dim ISINDict as Object
...
Set ISINDict = CreateObject("Scripting.Dictionary")
When I try to pass the dictionary to the other button like so:
Private Sub OKButton_Click(ISINDict as Scripting.Dictionary) 'if early binding
Private Sub OKButton_Click(ISINDict as Object) 'if late binding
I get the following error: "Procedure declaration does not match description of event or procedure having the same name" on that line.
Any ideas what I'm doing wrong?
An event handler has a specific signature, owned by a specific interface: you can't change the signature, otherwise the member won't match the interface-defined signature and that won't compile - as you've observed.
Why is that?
Say you have a CommandButton class, which handles native Win32 messages and dispatches them - might look something like this:
Public Event Click()
Private Sub HandleNativeWin32Click()
RaiseEvent Click
End Sub
Now somewhere else in the code, you want to use that class and handle its Click event:
Private WithEvents MyButton As CommandButton
Private Sub MyButton_Click()
'button was clicked
End Sub
Notice the handler method is named [EventSource]_[EventName] - that's something hard-wired in VBA, and you can't change that. And if you try to make an interface with public members that have underscores in their names, you'll run into problems. That's why everything is PascalCase (without underscores) no matter where you look in the standard libraries.
So the compiler knows you're handling the MyButton.Click event, because there's a method named MyButton_Click. Then it looks at the parameters - if there's a mismatch, something is wrong: that parameter isn't on the interface, so how is the event provider going to supply that parameter?. So it throws a compile-time error, telling you you need to either make the signature match, or rename the procedure so that it doesn't look like it's handling MyButton.Click anymore.
When you drop a control onto a form, you're basically getting a Public WithEvents Button1 As CommandButton module-level variable, for free: that's how you can use Button1 in your code to refer to that specific button, and also how its Click handler procedure is named Button1_Click. Note that if you rename the button but not the handler, the procedure will no longer handle the button's Click event. You can use Rubberduck's refactor/rename tool on the form designer to correctly rename a control without breaking the code.
Variables in VBA can be in one of three scopes: global, module, or procedure level.
When you do:
Sub DoSomething()
Dim foo
End Sub
You're declaring a local-scope variable.
Every module has a declarations section at the top, where you can declare module-scope variables (and other things).
Option Explicit
Private foo
Sub DoSomething()
End Sub
Here foo is a module-scope variable: every single procedure in that module can access it - read and write.
So if you have data you want to pass between procedures and you can't alter their signatures, your next best option is to declare a module-scope variable.
[ignores global scope on purpose]
About As New - consider this:
Public Sub Test()
Dim foo As Collection
Set foo = New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code blows up with run-time error 91 "object variable not set", because when foo.Add executes, foo's reference is Nothing, which means there's no valid object pointer to work with. Now consider this:
Public Sub Test()
Dim foo As New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code outputs 1, because As New keeps the object alive in a weird, unintuitive and confusing way. Avoid As New where possible.
Declare the dictionary at the module level and fill it in button-1-click event handler. Then it can be simply re-used in button-2-click event handler. So there is no need to pass the dictionary to event handlers which is not possible either. HTH
Form module
Option Explicit
' Declare dictionary at the user form module level
Private ISINDict As Scripting.Dictionary
Private Sub CommandButton1_Click()
FillDictionary
End Sub
Private Sub CommandButton2_Click()
' Use the dictionary filled in event handler of CommandButton-1
End Sub
Private Sub FillDictionary()
With ISINDict
.Add "Key-1", "Itm-1"
.Add "Key-2", "Itm-2"
.Add "Key-3", "Itm-3"
End With
End Sub
Private Sub UserForm_Initialize()
Set ISINDict = New Scripting.Dictionary
End Sub

Treat Userform as a Function

I have created a VBA UserForm which is essentially a glorified input box
Just like an input box can be employed like this
Dim returnVal As String
returnVal = InputBox("Write some string")
I would like my userform to run like this
Dim returnVal As customClass
Set returnVal = MyUserForm([some arguments])
ie. the MyUserForm() code passes some arguments to the userform, and when the userform is closed, it gets some arguments back (in the form of a custom class rather than a plain string)
What's the best way of structuring my userform to allow this functionality?
Currently, I'm just declaring some variables and the custom class publicly. I'm catching command button clicks and Query_close() events to hide the form, then I read the outputVal and close the form completely. I don't like this because I'd like my form to be totally self contained, and I think the capturing of events is messy.
In simplified code (read/return a string):
Function myUf(inVal As String) As String
Dim frm As New frmTest
frm.inputval = inVal
frm.Init 'sets caption. We cannot rely on userform initialize as this runs before inputval is set
'We could pass a variable here to save writing to the public variable
frm.Show
myUf = frm.outputVal
Set frm = Nothing
End Function
And in my Userform called frmTest with a textbox called tb1
Public inputval As String
Public outputVal As String
Public Sub Init()
Me.Caption = inputval 'setting caption, but could pass this anywhere
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode <> 1 Then Cancel = 1
outputVal = tb1 'reading value from textbox, but could return anything here
Me.Hide
End Sub
You need to find a way to initiate the UserForm from a ClassObject. Then, you can use a simple factory pattern to create the UserForm exactly the way you want.
In general, I have copied a bit of the code of Mat's Mug somewhere in StackOverflow and I wrote an article about the User Forms. If you take a look here (http://www.vitoshacademy.com/vba-the-perfect-userform-in-vba/) you will find a way to initialize the form with Public Sub ShowMainForm() It's possible to add a parameter to the ShowMainForm, then pass it to the initializer of the class.
In general, take the code from the article, make sure it works, and change the ShowMainForm initializer to the following:
Public Sub ShowMainForm(strText As String, strText2 As String)
If (objPresenter Is Nothing) Then
Set objPresenter = New clsSummaryPresenter
End If
objPresenter.Show
Call objPresenter.ChangeLabelAndCaption(strText, strText2)
End Sub
Then, if you call like this in the immediate window:
call ShowMainForm("Just","testing")
You will get this:
Which is quite what you need. :)
The basic idea is:
Create a Function in witch you combine your arguments to a string like:
strOpenArg = "param1:=value1;param2:=value2;"
than open the form with the OpenArgs
DoCmd.OpenForm "UserForm", acNormal, , , , acDialog, strOpenArgs
get your value and close the Form
Value= Form_UserForm.Value
DoCmd.Close acForm, "UserForm", acSaveNo
in the UserForm set Form_open. Here you can get your parameters.you can devide this by string splitting.
Set also an OK Button, where you make the form just invisible and set the return value
Private Sub Form_Open(Cancel As Integer)
Dim strParameter as String
strParameter = Me.OpenArgs 'Here are your parmeters
End Sub
Private Sub ok_Click()
m_Value = "Your ReturnValue"
Me.Visible = False
End Sub
Private m_Value As String
Public Property Get Value() As String
Value = m_msgBoxResult
End Property
There's no way to one-liner the code like you want to, unfortunately. If all your userform code is self-contained then the only way for it to pass values out is to change the values of public variables. Mat's Mug's answer here is the layout I usually use when trying to simulate functions like 'InputBox' but you still can't get it in one line without writing a separate function. Using userform properties allows you to contain more of your code within the form itself.

Having an MS Office UserForm detect which subroutine called it

In a VBA project of mine I am/will be using a series of reasonably complex userforms, many of which are visually identical but have different subroutines attached to the buttons. As a result I'm not overly keen on the idea of duplicating them multiple times in order to get different functionality out of the same layout. Is it possible to have a userform detect which subroutine called it and use this in flow control? I would like to be able to do something like this:
Private Sub UserForm_Initialize()
If [the sub that called the userform is called "foo"] then
Call fooSub
else
Call barSub
End If
End Sub
My backup plan is to have the calling subroutine set a global variable flag and have the userform check that, but that seems like a rather crude and clumsy solution.
Thanks everyone,
Louis
You can use the tag property of the form. Load the form, set the property, then show the form:
Sub PassCallerToForm()
Load UserForm1
UserForm1.Tag = "foo"
UserForm1.Show
End Sub
Now that the property is set, you can determine what to do in the form:
Private Sub UserForm_Activate()
If Me.Tag = "foo" Then
Call fooSub
Else
Call barSub
End If
End Sub
You can also use public variables:
' in userform
Public Caller As String
Private Sub UserForm_Click()
MsgBox Caller
Caller = Now()
Me.Hide
End Sub
' in caller
Sub callUF()
Dim frm As New UserForm1
frm.Caller = "Test Caller"
frm.Show
MsgBox frm.Caller ' valid after Me.Hide
Set frm = Nothing
End Sub
Personally, I would not have one userform doing two disparate activities. The code would get hard to read pretty quickly, I think. Copying the layout of a userform is pretty trivial.
To copy a userform: Open a blank workbook. In the Project Explorer, drag the userform to the new workbook. Rename the userform in the new workbook. Now drag it back to the original workbook. Change the code in the userform copy.
If you absolutely don't want separate userforms, I recommend setting up a property of the userform. Userforms are just classes except they have a user interface component. In the userform module
Private mbIsFoo As Boolean
Public Property Let IsFoo(ByVal bIsFoo As Boolean): mbIsFoo = bIsFoo: End Property
Public Property Get IsFoo() As Boolean: IsFoo = mbIsFoo: End Property
Public Sub Initialize()
If Me.IsFoo Then
FooSub
Else
BarSub
End If
End Sub
I always write my own Initialize procedure. In a standard module:
Sub OpenForm()
Dim ufFooBar As UFooBar
Set ufFooBar = New UFooBar
ufFooBar.IsFoo = True
ufFooBar.Initialize
ufFooBar.Show
End Sub