Treat Userform as a Function - vba

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.

Related

Anyway to populate a userform textbox with a public variable?

I am trying to populate a textbox on a userform with a public variable to allow the user to copy and paste into an external program. I can pass basic strings into the textbox but cannot seem to pass a variable. Here is my current code:
VBA Button-click
Option Explicit
Public PathConfig As String
Public BuildableLand As String
Public Sub Import_Click()
PathConfig = "TestConfig"
BuildableLand = "TestBuildable"
CopyPaste.Show
CopyPaste.ConfigText.Text = PathConfig
CopyPaste.BuildableLandText.Text = BuildableLand
End Sub
Userform
Textboxes are named 'ConfigText' and 'BuildableLandText' respectively
Userform Code
Private Sub ExitForm_Click()
Unload Me
End Sub
Private Sub UserForm_Initialize()
CopyPaste.ConfigText.Text = PathConfig
CopyPaste.BuildableLandText.Text = BuildableLand
End Sub
When I step through the code it doesn't seem like the PathConfig/BuildableLand variables are holding their value over to the userform. Is Public variable not sufficient?
A slightly improved version of the code above could look like that
Option Explicit
Private Sub Import_Click()
Dim frm As CopyPaste
Set frm = New CopyPaste
With frm
.ConfigText.Text = PathConfig
.BuildableLandText.Text = BuildableLand
.Show
End With
End Sub
Additionally create an extra sheet with the codename shConf for the configuration values you need and a module with the following functions
Option Explicit
Function PathConfig() As String
'Using Names instead of a direct cell reference would probably better
PathConfig = shConf.Range("A1")
End Function
Function BuildableLand() As String
BuildableLand = shConf.Range("A2")
End Function
There is no code in the userform initialize event needed.
You should also reconsider using Unload in the exit event of the userform as this destroys the class. Use Hide instead! With Unload the userform no longer exists and you cannot retrieve the user input.

Access VBA put a form element name as an argument

Let's see if anyone knows how to solve this problem:
I have a form with several elements: Some of them are textboxes called A1, A2, A3, A4...
Now, their AfterUpdate SubProcedure is extremely long but barely similar for each of them: A1_AfterUpdate, A2_AfterUpdate, A3_AfterUpdate...etc... are very similar but for the names of the textboxes they change.
My idea was to gather all that was equal in a subprocedure defined this way:
Private Sub Update(Box As String, Menu As Boolean)
If Menu=True{
Me!Box.Text = "This is the text that is going to change"
}
End Sub
So, the only thing I must do is to call it this way, for instance:
Update(A1, True)
But it doesn't seems to work. Any idea on how to reach this objective?
Add a class module - I've called it clsTextBoxEvents.
Add this code to the class:
Public WithEvents txt As Access.TextBox
Private Sub txt_AfterUpdate()
MsgBox txt.Name & " has been updated."
End Sub
In your form module add this code:
Public MyTextBoxes As New Collection
Private Sub Form_Open(Cancel As Integer)
Dim ctl As Control
Dim txtBoxEvent As clsTextBoxEvents
For Each ctl In Me.Controls
If TypeName(ctl) = "TextBox" Then
Set txtBoxEvent = New clsTextBoxEvents
Set txtBoxEvent.txt = ctl
txtBoxEvent.txt.AfterUpdate = "[Event Procedure]"
MyTextBoxes.Add txtBoxEvent
End If
Next ctl
End Sub
The MyTextBoxes declaration must be at the very top of the module.
This just adds the AfterUpdate event to all textboxes on the form. You'll probably want to refine that a bit to textboxes with specific text in the name, or controls that are in a specific frame on the form.
If you use a function instead of a sub:
Private Function UpdateCtl(Menu As Boolean)
If Menu Then
activecontrol = "This is the text that is going to change"
End If
End Sub
then you can call it directly from the control's AfterUpdate property: =UpdateCtl(True).
Simple and fast

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".)

How to make userform in Excel VBA remember password when 'red x' is pressed

I have a userform that calls a function with username/password prompt.
The user usually has to run it more times and each time is prompted for the login credentials so I want the userform to remember the user/password once it has been given until the workbook has been closed.
It is working fine until the user presses the 'process' button or closes the userform from the 'close' button, but when he/she closes it with the 'red x', the user/pass disappears on the next run.
I know that the 'red x' can be disabled, but I am curious if there is another way to make the userform remember even when the 'red x' is pressed?
Here is my code so far:
Private Sub cbCancel_Click()
maplogin.Hide
End Sub
Sub cbProcess_Click()
Dim drivetomap As String
Static user As String
Static pass As String
pcname = ActiveCell.Value
user = Me.txtUser.Value
pass = Me.txtPass.Value
...
You can redirect the X to run the cancel button code instead:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
cbCancel_Click
End If
End Sub
There area few points here. Let's deal with your specific question first: you can use the QueryClose event to handle a Userform being closed, and in your case you'd set the Cancel parameter to True (or basically any integer not 0) so that you could Hide the form rather than Unload it. Like so:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode <> vbFormCode Then
Cancel = True
Me.Hide
End If
End Sub
But there are other issues.
If you use this Userform instance to store the password then you'd need to scope the variables at module level so that you could access the variables outside of your cbProcess_Click routine. As below:
Option Explicit
Private mPcName As String
Private mUser As String
Private mPass As String
Private Sub cbCancel_Click()
maplogin.Hide
End Sub
Sub cbProcess_Click()
mPcName = ActiveCell.Value
mUser = Me.txtUser.Value
mPass = Me.txtPass.Value
'...
End Sub
The Static keyword in VBA is only useable at procedure level. Yes, it will retain the values of the variables when the procedure ends, but you still wouldn't be able to access those variables outside of the procedure. Hence the use of Private for module-level declarations.
Life's tricky if you store variable relating to the application in a Userform. It basically means you can never Unload the Userform for the duration of the programme. Far better would be to pass those values into some other storage method. Examples might be to pass them into a Module, to pass them into a Class, to write them into a hidden Worksheet, etc. An example of the module would be like so:
Module
Option Explicit
Private mUser As String
Private mPass As String
Public Sub SetLogin(user As String, pass As String)
mUser = user
mPass = pass
End Sub
Userform
SetLogin Me.txtUser.Value, Me.txtPass.Value

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