Add userform to a different workbook at runtime - vba

I have an addin and a workbook open. The addin is a .xlam file and in the workbook I've added a reference to it. The addin is password protected.
It is possible to run public methods of the addin from my workbook. However one method in the addin makes use of VBA.UserForms.Add to open a userform that was created at runtime like this
Let's say the workbook which holds a reference to myAddin has this:
Private Sub callAddin()
myAddin.ShowForm ThisWorkbook
End Sub
Ordinarily, the code in my addin looks like this:
Public Sub ShowForm(CallerWorkbook As Workbook)
Const vbext_ct_MSForm As Long = 3
'This is to stop screen flashing while creating form
Application.VBE.MainWindow.Visible = False
'Add to ThisWorkbook, not supplied workbook or VBE will crash - ignore CallerWorkbook
Dim myForm As Object
Set myForm = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
'Create the User Form
With myForm
.Properties("Caption") = "Select"
.Properties("Width") = 300
.Properties("Height") = 270
End With
'Show the form
Dim finalForm As Object
Set finalForm = VBA.UserForms.Add(myForm.Name)
finalForm.Show
'Remove form
ThisWorkbook.VBProject.VBComponents.Remove myForm
End Sub
Which works fine. However when my addin is password protected, trying to add a temporary userform to it is not allowed. No problem, I just add the temporary userform to the workbook that called the code instead, as this will not be password protected
Sub ShowForm(CallerWorkbook As Workbook)
Const vbext_ct_MSForm As Long = 3
'This is to stop screen flashing while creating form
Application.VBE.MainWindow.Visible = False
'Add to CallerWorkbook instead
Dim myForm As Object
Set myForm = CallerWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
'Create the User Form
With myForm
.Properties("Caption") = "Select"
.Properties("Width") = 300
.Properties("Height") = 270
End With
'Show the form
Dim finalForm As Object
'Now myForm cannot be found and added
Set finalForm = VBA.UserForms.Add(myForm.Name)
finalForm.Show
'Remove form
CallerWorkbook.VBProject.VBComponents.Remove myForm
End Sub
However VBA can't seem to see where myForm.Name points to now, so the Add method fails with "Run time error 424: Object required"
Is there any way to display a form created at runtime in another workbook?

The problem that you're encountering is that UserForms are Privately instanced by default. That means that a project cannot refer to a UserForm in another project, and if you can't refer to the form, you can't call it's Show method.
Your Set myForm = CallerWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm) statement returns a VbComponent, not a UserForm, so that's why you can't then use VBA.UserForms.Add(myForm.Name)
There are 2 ways around this:
1 - Create a PublicNotCreatable template UserForm in your add-in
A UserForm is like a class, so it can have its Instancing property set, just like a class. However, the VBE doesn't expose the Instancing property in the Properties Window for UserForms, so to set the instancing, you need to export the form, and then edit the Attribute VB_Exposed attribute in the FRM file in a text editor, before importing the form again. Here are the steps:
Create a UserForm named TemplateForm in your add-in project
Remove TemplateForm and choose to Export the form before removing it
Open the TemplateForm.frm file in a text editor
Edit the line Attribute VB_Exposed = False so that is reads Attribute VB_Exposed = True
Save the changes to TemplateForm.frm
Import TemplateForm.frm into your add-in
Add a public function that returns a new instance of TemplateForm to your add-in. I've made this function accept a workbook reference so that the add-in can configure any workbook specific properties on the form:
Public Function GetTemplateForm(CallerWorkbook As Workbook) As TemplateForm
Dim frm As TemplateForm
Set frm = New TemplateForm
'Set early-bound properties with intellisense
frm.Caption = "Select"
frm.Width = 300
frm.Height = 270
'Configure CallerWorkbook specific form properties here
'...
Set GetTemplateForm = frm
End Function
In your user's workbook, you can then show an instance of the TemplateForm, without ever having to dynamically add a form, or deal with screen-flickering, or hard-to-debug code:
Sub ShowAddinForm()
With MyAddin.GetTemplateForm(ThisWorkbook)
'Do more workbook specific propery setting here...
'...
.Show
End With
End Sub
** Note - The Rubberduck VBA add-in will soon have the ability to add a PublicNotCreatable UserForm.
2 - Have the add-in create the UserForm component, but have the user's workbook manage it
This approach isn't nearly as elegant. There's a lot more code for the user to manage, and there's screen flickering, and hard to debug code. Here are the steps:
Add this code to the add-in:
Public Function GetTempFormName(CallerWorkbook As Workbook) As String
Const vbext_ct_MSForm As Long = 3
'This is to stop screen flashing while creating form
Application.VBE.MainWindow.Visible = False
'Add to CallerWorkbook instead
With CallerWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
.Properties("Caption") = "Select"
.Properties("Width") = 300
.Properties("Height") = 270
GetTempFormName = .Name
End With
End Function
Public Sub RemoveTempForm(CallerWorkbook As Workbook, FormName As String)
With CallerWorkbook.VBProject.VBComponents
Dim comp As Object
Set comp = .Item(FormName)
.Remove .Item(FormName)
End With
End Sub
Then, in the user's workbook, add this code:
Sub GetAddinToCreateForm()
Dim FormName As String
FormName = MyAddin.GetTempFormName(ThisWorkbook)
With VBA.UserForms.Add(FormName)
.Show
End With
MyAddin.RemoveTempForm ThisWorkbook, FormName
End Sub

Related

Handing over variable storing selected list item

I might miss just a stupid small detail but I don't get a hang on it.
I've created a userform with a listbox where I want the user to select one item. This is working so far as my variable "termin" has the right value before I close the user form
Private Sub OKButton_click()
termin = Eventlist.List(Eventlist.ListIndex)
MsgBox termin 'Just for testing purposes. It gives me the selected item
Unload Eventabfrage
End Sub
And this is a part of what I have in 'ThisOutlookSession':
Option Explicit
Dim termin As String
Public Sub MailMerge()
Eventabfrage.Show
MsgBox termin 'and there it is empty but shouldn't be empty
enter code here
End Sub
What do I have to do to hand over the value to my MailMerge Sub?
It is a macro in Outlook so storing it in any Excel cell is not an option.
Other than using Public variable (which I would only as the last chance since it's prone to many drawbacks), you can use UserForm class Tag property to pass info in and out an instance of a Userform and a more safe Userform instantiating and terminating scheme, as follows:
in the calling module:
Public Sub MailMerge()
Dim termin As String ' declare 'termin' as a Sub scoped variable
With New Eventabfrage ' get a new instance of the wanted userform class and reference it
.Show
termin = .Tag ' retrieve referenced userform 'Tag' property and store it in 'termin' variable
'enter code here
End With ' <-- this will unload the userform instance
End Sub
in the Eventabfrage class module
Private Sub OKButton_Click()
With Me ' reference the Userform class current instance
.Tag = .Eventlist.List(.Eventlist.ListIndex)
.Hide ' hide the userform instead of unloading it, so as to have it "alive", along with its properties (and methods) in its calling sub
End With
End Sub

Document_Open() does not work in other files

I am writing a VBA project containing 1 module (m1) and 1 userform (uf).
In "ThisDocument" I am calling a public sub from "m1" that initializes a collection I then refer to in the userform. This works perfectly fine until I deploy this project to other files.
I save the file as a .dotm in the %Appdata%/Microsoft/word/startup folder so I can use the project in all of my word files. But as soon as I try to use my project in other files the userform opens itself as designed but the collection is empty.
What could be the problem here?
Manually calling the initialization method from the userform works fine.
'----------------------------------------------ThisDocument
Private Sub Document_Open()
initBetaCollection
End Sub
'----------------------------------------------m1
Option Explicit
Public beta As Collection
Sub initBetaCollection()
Set beta = New Collection
beta.Add Array("0041", "A"), Key:="0041"
'...
End Sub
'----------------------------------------------uf
Option Explicit
Private Sub txtSearch_Change()
Dim arr As Variant
Dim search As String
'Defining the textinput as "search"
search = txtSearch.Value
For Each arr In beta
If search <> "" Then 'No empty search field
If arr(1) Like "*" & search & "*" Then 'Match found
lbResults.AddItem arr(0)
End If
End If
Next
End Sub
I get the: Run Time Error '424' object required
The problem with using Document_Open in the ThisDocument class or a macro named AutoOpen in a regular module is that both execute specifically for this document (or documents created from the template), only.
In order to have an application-level event, that fires for all documents opened, it's necessary to work with application-level events.
For this, first a class module is required. In the class module, the following code is needed:
'Class module named: Class1
Public WithEvents app As Word.Application
Private Sub app_DocumentOpen(ByVal Doc As Document)
'MsgBox Doc.FullName & ": on Open"
'Code here that should fire when a document is opened
'If something needs to be done with this document, use
'the Doc parameter passed to the event, don't try to use ActiveDocument
End Sub
Then, in a regular module, an AutoExec macro can be used to initialize the class with event handling. (AutoExec: fires when Word starts and loads a template with a macro of this name.)
Option Explicit
Dim theApp As New Class1
Sub AutoExec()
'MsgBox "AutoExec"
Set theApp.app = Word.Application
End Sub
'Sub AutoOpen()
' MsgBox "Open in AutoOpen" - fires only for this document
'End Sub

Calling a userform from a specific sheet sub

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.

Userform to open when sheet opens

A little stuck with VBA, im trying to get my userform "ParcelDataEntry" to open when i select a sheet from my combo box which i have on my front page which will have various other options when i cross this hurdle.
All 54 worksheets have the following code:
Private Sub Worksheet_Open()
ParcelDataEntry.Show
End Sub
I don't think there is a Worksheet_Open event. Instead use the Workbook_SheetActivate event. You should place this code in the ThisWorkbook code module.
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Call OpenDataEntryForm
End Sub
Treat your userform like an object and declare and instantiate it accordingly.
Public Sub OpenDataEntryForm()
Dim dataEntryForm As ParcelDataEntry
' Create an instance of the form
Set dataEntryForm = New ParcelDataEntry
' Show the form
dataEntryForm.Show
' Do something here
' If the form was opened as Modal, then the code here will only run
' once the form has been hidden/closed
' Now destroy the object
Set dataEntryForm = Nothing
End Sub

Disconnect VBA UserForm from parent application

I'm using a UserForm spawned by Excel that modifies a PowerPoint presentation (it's a roundabout way to avoid needing a macro-enabled spreadsheet). The form works just fine, but every time I focus to it the Excel application takes focus (since Excel is the parent window).
Is there any way to stop this from happening? I'd like to prevent Excel from taking focus when the UserForm is used.
Would something like this work? This will hide/make invisible the parent Excel Application while the UserForm is displayed. Or at least get you started:
Example subroutine that "Shows" the userform:
Sub Test()
Dim ppt As Object
Dim xl As Object
Set ppt = GetObject(, "PowerPoint.Application")
Application.Visible = False
UserForm1.Show vbModeless
End Sub
Use this in the form's Terminate event:
Private Sub UserForm_Terminate()
'Ensures the Excel Application is visible after the form closes
Application.Visible = True
End Sub
You could add a button/etc on the form, if you want to allow the user to unhide the Excel Application
Private Sub CommandButton1_Click()
'Displays the Excel Application:
Application.Visible = True
End Sub