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.
Related
I have a userForm (mappingGuide) that allows user to pick a smartyTag from a list of more user-friendly names.
I have a second user-form (conditionalBuilder) that I would like to call this userForm upon double-clicking a text field so that a user can lookup which smartyTag to apply (in case they don't know).
So logic, is:
open conditionalBuilder
double-click Field text box
mappingGuide opens
pick a smartytag from listbox
fill smartytag value into field text-box in conditionalBuilder
unload mappingGuide
The issue I think I having with completing the requirement is that when I load the forms themselves I cannot find a way to set the text of the fieldName textbox of the loaded instance of conditionalBuilder (see last code block below). I've been searching around, but cannot figure it out.
Here is relevant code:
conditionalBuilder loads from Custom UI ribbon
Sub RunCode(ByVal Control As IRibbonControl)
Select Case Control.ID
Case Is = "mapper": LoadMappingGuide
Case Is = "conditional": LoadConditionalBuilder
End Select
End Sub
Sub LoadConditionalBuilder()
Dim conditionalForm As New conditionalBuilder
conditionalForm.Show False
End Sub
double-click event of fieldName then loads mappingGuide
Private Sub fieldName_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.hide
Dim pickField As New mappingGuide
pickField.Show False
End Sub
smartTag listbox click event then attempts to place selection into fieldName (or selection if form not loaded)
Private Sub smartTagList_Click()
If smartTagList.ListIndex > -1 And smartTagList.Selected(smartTagList.ListIndex) Then
Dim smartyTag As String
smartyTag = smartTagList.List(smartTagList.ListIndex, 2)
If isUserFormLoaded(conditionalBuilder.Name) Then
'*** ---> below is my issue how to reference instance of form
conditionalBuilder.fieldName.Text = smartyTag
conditionalBuilder.Show
Else
Selection.Range.Text = smartyTag
End If
End If
Unload Me
End Sub
If there is a better set-up that would be great to know too. I have the forms separate because there's a couple of levels a user can create tags with.
This is how I would do it, a bit of overkill but in case of multiple forms it will be beneficial.
Module 1:
Option Explicit
Sub test()
frmMaster.Show False
End Sub
Form 1 : frmMaster:
Option Explicit
'/ Declare with events
Dim WithEvents frmCh As frmChild
Private Sub TextBox1_DblClick(ByVal cancel As MSForms.ReturnBoolean)
handleDoubleClick
End Sub
Sub handleDoubleClick()
If frmCh Is Nothing Then
Set frmCh = New frmChild
End If
frmCh.Show False
End Sub
'/ Handle the event
Private Sub frmCh_cClicked(cancel As Boolean)
Me.TextBox1.Text = frmCh.bChecked
End Sub
Form 2: frmChild:
Option Explicit
Event cClicked(cancel As Boolean)
Private m_bbChecked As Boolean
Public Property Get bChecked() As Boolean
bChecked = m_bbChecked
End Property
Public Property Let bChecked(ByVal bNewValue As Boolean)
m_bbChecked = bNewValue
End Property
Private Sub CheckBox1_Click()
Me.bChecked = Me.CheckBox1.Value
'/ Raise an event when something happens.
'/ Caller will handle it.
RaiseEvent cClicked(False)
End Sub
You can do this with a presenter class which controls userform instances and pass values between them. I mocked up something similar to give you an idea.
Presenter. This is a class module which creates the userforms, controls their scope, and catches the event thrown by the
ConditionalBuilder. It makes it super easy to pass values between
userforms.
Private WithEvents CB As ConditionalBuilder
Private MG As MappingGuide
Public Sub ShowCB()
Set CB = New ConditionalBuilder
CB.Show vbModal
End Sub
Private Sub CB_ShowMappingGuide()
Set MG = New MappingGuide
MG.Show vbModal
CB.UpdateTB1 Value:=MG.SmartTag
End Sub
ConditionalBuilder.
This has a simple function to update your textbox and also an event which raises actions in the presenter.
Public Event ShowMappingGuide()
Public Function UpdateTB1(Value As String)
TextBox1.Value = Value
End Function
Private Sub TextBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
RaiseEvent ShowMappingGuide
End Sub
MappingGuide.
The Type and Property could be overkill since we just want one value from the mapping guide but it's still good practice.
Private Type TView
Tag As String
End Type
Private this As TView
Public Property Get SmartTag() As String
SmartTag = this.Tag
End Property
Private Sub UserForm_Initialize()
Tags.List = Array("a", "b", "c")
End Sub
Private Sub Tags_Click()
this.Tag = Tags.List(Tags.ListIndex, 0)
Me.Hide
End Sub
I have one final Standard Module which creates the Presenter. This is what you'd hook up to your ribbon.
Public Sub ShowProject()
With New Presenter
.ShowCB
End With
End Sub
Step 1 (double click text field)
Step 2 (selecting "b")
Step 3 (result)
I actually solved it by placing the below block inside the IF where I check for the form being loaded and I will leave open for better answers, if there are any.
Dim uForm As Object
For Each uForm In VBA.UserForms
If uForm.Name = conditionalBuilder.Name Then
uForm.fieldName.Text = smartyTag
uForm.Show
End If
Next
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.
Another newbie question but I cannot find my answer anywhere so far...
I have a workbook with several sheets, lets call them S1, S2 etc., I have a userform that does an operation that can be activated from any of the sheet.
My problem here is that I have parameters passed to the userform from the sub
Public c As Integer, lf As Integer, ld As Integer
Sub Tri()
ld = 8
lf = 128
Application.ScreenUpdating = False
UsForm.Show
End Sub
Now my workbook is growing in size and differences appear from S1 to S2 etc requiring me to change parameters depending on the sheet it is launched from.
So i removed my code from "module" and put it in the "Microsoft excel object" part. But it now seems it does not have access to my public variables and as soon as I request ld or lf, it is shown as empty (even if it was implemented in the previous userform).
Please can someone tell me what I'm missing ? How can I do otherwise (I do not want to put the data in the sheets themselves)?
You need to take advantage of the fact that a userform is a class. So as an example add the following code to the "form". Let's assume you have a button with the name CommandButton1
Option Explicit
Dim mVar1 As Long
Dim mVar2 As String
Property Let Var1(nVal As Long)
mVar1 = nVal
End Property
Property Let Var2(nVal As String)
mVar2 = nVal
End Property
Private Sub CommandButton1_Click()
MsgBox mVar1 & " - " & mVar2
Me.Hide
End Sub
Then you can add in a normal Module
Sub TestForm()
Dim frm As UserForm1
Set frm = New UserForm1
Load frm
frm.Var1 = 42
frm.Var2 = "Test"
frm.Show
Unload frm
End Sub
In such a way you can pass variables to a form without using global variables.
Here is a widely accepted answer about Variable Scopes. https://stackoverflow.com/a/3815797/3961708
If you have decalred your variable inside thisworkbook, you need to access it by fully qualifying it. Like ThisWorkbook.VariableName
But with UserForms I recommend to use Properties for data flow. Thats the clean and robust way to do it. Get in the habit of using properties and you will find it highly beneficial for UserForms.
Example:
Add this code in the ThisWorkbook
Option Explicit
'/ As this variable is defined in ThisWorkBook, you need to qualify it to access anywher else.
'/ Example ThisWorkbook.x
Public x As Integer
Sub test()
Dim uf As New UserForm1
x = 10
'/ Set the propertyvalue
uf.TestSquare = 5
'/Show the form
uf.Show
'/ Get the property value
MsgBox "Square is (by property) : " & uf.TestSquare
'/Get Variable
MsgBox "Square is (by variable) : " & x
Unload uf
End Sub
Now add a UserForm, called UserForm1 and add this code
Option Explicit
Private m_lTestSquare As Long
Public Property Get TestSquare() As Long
TestSquare = m_lTestSquare
End Property
Public Property Let TestSquare(ByVal lNewValue As Long)
m_lTestSquare = lNewValue
End Property
Private Sub UserForm_Click()
'/ Accessing the Variable Defined inside ThisWorkkbook
ThisWorkbook.x = ThisWorkbook.x * ThisWorkbook.x
'/ Changing Property Value
Me.TestSquare = Me.TestSquare * Me.TestSquare
Me.Hide
End Sub
Now when you run the Test sub from ThisWorkbook you will see how you can access variables and properties across the code.
I have the following problem:
Private Sub TextBox1_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox2_Change()
Control (this) <<<<----- this is Empty
End Sub
Private Sub TextBox3_Change()
Control (this) <<<<----- this is Empty
End Sub
Public Sub Control(asdf As MSForms.TextBox)
asdf.Font.Size = 11
asdf.Font.Bold = True
End Sub
The compiler says that 'this' is empty. What should I put there to recognize the TextBox?
Thx
When you use parentheses in your code you're evaluating Me.TextBox1, which ends up passing a String to Control. If you drop the parens it will work.
Private Sub TextBox1_Change()
Control Me.TextBox1 'without the parens
End Sub
Typically you don't use () when calling a Sub, unless you're using Call
If you end up having a lot of textboxes, it may be easier to customize the event handler in a custom class.
In a standard module, put a globally scoped collection variable
Public gcolTextboxes As Collection
Create a custom class module called CTbxEvents
Private WithEvents mtb As MSForms.TextBox
Public Property Get tb() As MSForms.TextBox
Set tb = mtb
End Property
Public Property Set tb(otb As MSForms.TextBox)
Set mtb = otb
End Property
Private Sub mtb_Change()
tb.Font.Size = 11
tb.Font.Bold = True
End Sub
Finally, in ThisDocument, load up all the textboxes when the document opens.
Private Sub Document_Open()
Dim f As Field
Dim clsTbxEvents As CTbxEvents
'Globally scoped collection to hold the classes
Set gcolTextboxes = New Collection
'Loop throught the fields
For Each f In Me.Fields
'Only fields that are activex controls
If f.Type = wdFieldOCX Then
'only activex controsl that are textboxes
If TypeOf f.OLEFormat.Object Is MSForms.TextBox Then
'create a new class, add the textbox, add to collection
Set clsTbxEvents = New CTbxEvents
Set clsTbxEvents.tb = f.OLEFormat.Object
gcolTextboxes.Add clsTbxEvents
End If
End If
Next f
End Sub
Now any textbox you add will use the same event handler and you don't keep having to call a separate sub. If you truly only have three textboxes, it's probably overkill.
The this keyword is specific to C++/C#/Java, the corresponding keyword for VB/VB.NET is Me
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