I have a form with a subform that are linked by an ID. The problem is the subform has a combo box that needs a parameter from the main form for the row source.
The form is about sales calls and they need to be able to link many proposals and contracts as they want. The combo box for the list of proposals and contracts needs to be in relation with the mills or clients of this sales call.
I know I could do a Forms!FormName!ControlName to get the mill list but does this mean I have a design problem?
Or should I do a list filled when a user click on an element in a combo box in the main form? but then I would have to handle the save and delete myself.
Thank you
If a subform requires a main form to function properly you can check for a parent. For example:
Private Sub Form_Open(Cancel As Integer)
Dim strParent As String
Dim strSubname As String
GetParent Me, strParent, strSubname
If strParent = "None" Then
MsgBox "This is not a stand-alone form.", , "Form Open Error"
Cancel = True
End If
End Sub
Function GetParent(frm, ByRef strParent, ByRef strSubname)
Dim ctl
On Error Resume Next
strParent = frm.Parent.Name
If Err.Number = 2452 Then
Err.Clear
strParent = "None"
Else
For Each ctl In frm.Parent.Controls
If ctl.ControlType = acSubform Then
If ctl.SourceObject = frm.Name Then
strSubname = ctl.Name
End If
End If
Next
End If
End Function
Related
I am working on a userform which contains numerous (an unknown number) of textbox controls. The number of textboxes on the form is based on certain parameters being met elsewhere within the project.
The number of textboxes on the form could get up to 100 or so and my users are expected to fill in each of them. However, in many cases the value for one textbox will be the same as the textbox positioned directly above it.
I therefore wish to implement functionality to allow the user to enter a "." followed by tabbing out of the field in order to copy the value from the textbox above it.
There are a couple of ways I could go about this initial requirement:
Private Sub TextBox2_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If TextBox2.Value = "." Then
TextBox2.Value = TextBox1.Value
End If
End Sub
This works absolutely fine.. however given that there is no indication here that the textbox2 is below textbox1 (Other than me knowing where I put it) we could do something like:
Private Sub TextBox2_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If TextBox2.Left = TextBox1.Left And TextBox2.Value = "." Then
TextBox2.Value = TextBox1.Value
End If
End Sub
And again this works absolutely fine.
My problem is this, with up to 100 textbox controls I do not particularly want to write and maintain an Exit event for each possible textbox control.
I will probably phrase this incorrectly, but is there a way to trigger the exit event dynamically by passing the name of the textbox the user has tabbed out of?
Or am I lumbered within dozens of identical subs handling the exit of each textbox specifically?
EDIT:
Reading the post linked from Word Nerd, I have the following included with the userform initialize event
Dim tbCollection As Collection
Private Sub UserForm_Initialize()
Dim Ctrl As MSForms.Control
Dim obj As clsTextBox
Set tbCollection = New Collection
For Each Ctrl In usfEnterTime.Controls
If TypeOf Ctrl Is MSForms.TextBox Then
Set obj = New clsTextBox
Set obj.Control = Ctrl
tbCollection.Add obj
End If
Next Ctrl
Set obj = Nothing
Then we have the class module clsTextBox
Private WithEvents MyTextBox As MSForms.TextBox
Public Property Set Control(tb As MSForms.TextBox)
Set MyTextBox = tb
End Property
Private Sub MyTextBox_Change()
If MyTextBox.Value = ". " Then
If MyTextBox.Top = 24 Then
MsgBox "Nothing to copy from"
MyTextBox.Value = ""
Exit Sub
Else
For Each Ctrl In usfEnterTime.Controls
If Ctrl.Left = MyTextBox.Left And Ctrl.Top = MyTextBox.Top - 18 Then
MyTextBox.Value = Ctrl.Value
Application.SendKeys "{TAB}"
Exit Sub
End If
Next Ctrl
End If
Else
Exit Sub
End If
End Sub
My initial requirement was to use the EXIT event to implement a "." + TAB out of textbox to copy from above, however it appears the TextBox control does not have the EXIT event available. So I am using the change event, application.sendkeys and expecting my users to use ". " (Note blank space) to mimic the same functionality and tab to the next textbox in the tab order index using the spacebar instead. As you can see I am simply looping through all controls and checking the relative TOP and LEFT positions to determine which textbox to copy from. 24 is the position of the top most textbox control and so I am capturing this and displaying a msgbox to advise the user there will be nothing to copy from. -18 represents the gap between the tops of two textbox controls.
Working perfectly, thanks to Word Nerd for linking through to the other post.
I have a userform with many comboboxes and an "Ok" button. What I need is that when pressing that "Ok" button, VBA to check if there's no empty comboboxes. If all comboboxes have some value selected - close the userform otherwise return a message box and clicking "Ok" on that messagebox returns me to the userform with no filled values lost.
I've tried all the methods I could think of:
If PackageOuterRadius = null Then
if PackageOuterRadius is nothing Then
If PackageOuterRadius.value = 0 Then
If IsNull(PackageOuterRadius) = True Then
If IsNull(PackageOuterRadius.value) Then
What I've been trying to do is:
Private Sub Rigid_Filme_Ok_Button_Click()
If PackageOuterRadius.ListCount = 0 Then
MsgBox "Select a ""Package Outer Radius!"
End If
And absolutelly nothing actually checks if the combobox is empty and keeps returning a positive (That there's a value selected)
What could be the solution to this problem? Could someone, please, help me?
If you have few ComboBoxes only, you may insert lines underneath the OK button to check if all the ComboBoxes are filled but if you have too many ComboBoxes on UserForm, you may achieve this with the help of a Class Module and to do so, follow these steps...
Insert a Class Module and rename it to clsUserForm and the place the following code on Class Module...
Code for Class Module:
Public WithEvents mCMB As msforms.ComboBox
Public Sub CheckComboBoxes()
Dim cCtl As msforms.Control
Dim cntCBX As Long
Dim cnt As Long
For Each cCtl In frmMyUserForm.Controls
If TypeName(cCtl) = "ComboBox" Then
cntCBX = cntCBX + 1
If cCtl.Value <> "" Then
cnt = cnt + 1
End If
End If
Next cCtl
If cnt = cntCBX Then
Unload frmMyUserForm
Else
MsgBox "All the ComboBoxes are mandatory.", vbExclamation
End If
End Sub
Then place the following code on UserForm Module. The code assumes that the name of the userForm is frmMyUserForm and the name of the CommandButton is cmdOK.
Code for UserForm Module:
Dim GroupCBX() As New clsUserForm
Dim frm As New clsUserForm
Private Sub cmdOK_Click()
frm.CheckComboBoxes
End Sub
Private Sub UserForm_Initialize()
Dim i As Long
Dim ctl As Control
For Each ctl In Me.Controls
If TypeName(ctl) = "ComboBox" Then
i = i + 1
ReDim Preserve GroupCBX(1 To i)
Set GroupCBX(i).mCMB = ctl
End If
Next ctl
End Sub
And you will be good to go.
I have a subform with a button that opens another form.
On the secondary form, the user can select an address.
The selected address should be applied to the calling form.
I pass the window handle when opening the child form.
But when it tries to find the calling form in the Forms collection, it isn't there.
I suspect that is because the calling form is actually a subform.
I don't know where to go from here.
Calling the form, passing the windows handle
OpenCCCustAddr [CustFID], "CCInt", Me.hWnd
In the Form Close event, I try to set the address values on the calling form,
but GetFormByHWND returns null.
Set frm = GetFormByHWND(Me!txtCallingHWND)
// Me!txtCallingHWND here is populated and looks reasonable
frm!BillStreet = strAddr // This blows up since frm is null
frm!HolderZipCode = strZip
frm!AddressUpdated = -1
Set frm = Nothing
Public Function GetFormByHWND(lngHWND As Long) As Form
Dim frm As Form
Dim nm As String
Select Case lngHWND
Case 0
Case Else
For Each frm In Forms
nm = frm.NAME // the name of the parent form shows, but not my calling subform
If frm.hWnd = lngHWND Then
Set GetFormByHWND = frm
Exit For
End If
Next
End Select
End Function
For Each and For I=0 to Count-1 both give the same results. The form just isn't in Forms. It's possible that it is because it is a subform.
I tried searching the subforms, but this blows up when I check ctl.hWnd with "Object doesn't support this property"
Public Function GetFormByHWND(lngHWND As Long) As Form
Dim frm As Form
Dim ctl As Access.Control
Dim nm As String
Select Case lngHWND
Case 0
Case Else
For Each frm In Forms
nm = frm.NAME
If frm.hWnd = lngHWND Then
Set GetFormByHWND = frm
Exit For
End If
Next
Rem If we didn't find the form, check for a subform
If GetFormByHWND Is Nothing Then
For Each frm In Forms
nm = frm.NAME
For Each ctl In frm.Controls
If ctl.Properties("ControlType") = acSubform Then
nm = ctl.NAME
If ctl.hWnd = lngHWND Then // Error: "Object doesn't support this property"
Set GetFormByHWND = ctl
Exit For
End If
End If
Next
Next
End If
End Select
End Function
As #June7 pointed out, my mistake was assuming that the control was the form. Instead is has a form.
So the proper solution is
Rem If we didn't find the form, check for a subform
If GetFormByHWND Is Nothing Then
For Each frm In Forms
For Each ctl In frm.Controls
If ctl.Properties("ControlType") = acSubform Then
If ctl.Form.hWnd = lngHWND Then // note the change here
Set GetFormByHWND = ctl.Form
Exit For
End If
End If
Next
Next
End If
First, it not at all clear why all that code and hwn stuff is required?
We assume that you have a form.
On that form, you have a button, and it launches the 2nd form.
so, in first form, we have this:
' write data to table before launching form
If Me.Dirty = True Then Me.Dirty = False
DoCmd.OpenForm "formB"
Ok, now in formB on-load event, we have this:
Option Compare Database
Option Explicit
Dim frmPrevious As Form
Dim frmPreviousSub As Form
Private Sub Form_Load()
Set frmPrevious = Screen.ActiveForm
Set frmPreviousSub = frmPrevious.MySubFormControl.Form
' do whatever
End Sub
So now we have both a reference to the previous form, and also the sub form.
Say the user selects some address and hits the ok button.
The code then does this:
frmPreviousSub!AddressID = me!ID ' get/set the PK address ID
docmd.Close acForm, me.name
So no need for all that world poverty, grabbing and looping hwnd or any such hand stands.
Just a few nice clean lines of code.
Now, I DO HAVE a recursive loop that will return the form handle ALWAYS as a object reference, and you thus don't even have to hard code the form(s) name.
So, say that main form had 2 sub forms, and on those to sub forms, you have
A Company address, and a ship to address. So, you want to launch form B from EITHER of these two sub forms, and when you select a address, you return that value, and thus two or even potential 3 sub forms could in fact call this way call pop up address selector form.
The way you do this is similar to the above code, but we do NOT need to hard-code the sub form.
The code will now look like this:
Private Sub Form_Load()
Dim f As Form
Set f = Screen.ActiveForm ' pick this up RIGHT away -
' previous active form only valid
' in open/load event
' we have the previous active form, get the sub form.
Set frmPrevous = GetSubForm(f)
End Sub
Note that the sub form can be 3 or even 5 levels deep. This routine is "recursive". It grabs the previous form, then checks if a sub form has focus. If the sub form has focus, then it gets that control, and if that control is a sub form, then it just keeps on going until such time we drill down this rabbit hole and NO MORE drilling down can occur.
This routine should be placed outside of the form and placed in your standard "global" module of routines.
Public Function GetSubForm(f As Form) As Form
Static fs As Form
If f.ActiveControl.Controltype = acSubform Then
GetSubForm f.ActiveControl.Form
Else
Set fs = f
End If
Set GetSubForm = fs
End Function
So note how if it find that a sub form has the focus? Well then it just calls itself again with that form and keeps on drilling down. As a result, it don't matter if the form is 1 or 5 levels deep. The resulting "frmPrevous" will be a valid reference to that sub form, and thus after you select or do something in the supposed popup form? You can set the value of some PK or whatever and then close the form.
There is no hwnd, very clean code, and the recursion trick means that even for nested sub forms more then one deep, your frmPrevious is in fact a reference to the form that launch the form we pop up for the user to select whatever.
If you don't have a common Address ID column? Then our popup form should ASSUME that you always have a public variable defined in the calling form.
Say ReturnAddressID as long
Make sure you dim the value as public, say like this:
Public ReturnAddressID as long
So, now in our popup form, we can do this:
frmPrevious.ReturnAddressID = me!PD
frmPrevous.MyUpdate
(we assume that all forms that call the popup also have a public function names MyUpdate.
Thus, now we have a generalized approach, and 2 or 10 different address forms, even as sub forms can now call the one address picker. As long as any of those forms adopts that public ReturnAddressID and a public function MyUpdate, then we can pass back the values, and MyUpdate will shove/take/set the ReturnAddressID into whatever column and value you use for the Address ID in that sub form.
And of course if there are no sub forms, the routine will just return the top most form that called the pop up form.
There is a sub, it creates a CourtForm userform and then takes a data from it. The problem appears when said form is closed prematurely, by pressing "X" window button and I get a runtime error somewhere later. For reference, this is what I'm talking about:
In my code I tried to make a check to exit sub:
Private Sub test()
'Create an exemplar of a form
Dim CourtForm As New FormSelectCourt
CourtForm.Show
'The form is terminated at this point
'Checking if the form is terminated. The check always fails. Form exists but without any data.
If CourtForm Is Nothing Then
Exit Sub
End If
'This code executes when the form proceeds as usual, recieves
'.CourtName and .CourtType variable data and then .hide itself.
CourtName = CourtForm.CourtName
CourtType = CourtForm.CourtType
Unload CourtForm
'Rest of the code, with the form closed a runtime error occurs here
End Sub
Apparently the exemplar of the form exists, but without any data. Here's a screenshot of the watch:
How do I make a proper check for the form if it's closed prematurely?
Add the following code to your userform
Private m_Cancelled As Boolean
' Returns the cancelled value to the calling procedure
Public Property Get Cancelled() As Boolean
Cancelled = m_Cancelled
End Property
Private Sub UserForm_QueryClose(Cancel As Integer _
, CloseMode As Integer)
' Prevent the form being unloaded
If CloseMode = vbFormControlMenu Then Cancel = True
' Hide the Userform and set cancelled to true
Hide
m_Cancelled = True
End Sub
Code taken from here. I would really recommend to have a read there as you will find a pretty good basic explanation how to use a userform.
One of the possible solutions is to pass a dictionary to the user form, and store all entered data into it. Here is the example:
User form module code:
' Add reference to Microsoft Scripting Runtime
' Assumed the userform with 2 listbox and button
Option Explicit
Public data As New Dictionary
Private Sub UserForm_Initialize()
Me.ListBox1.List = Array("item1", "item2", "item3")
Me.ListBox2.List = Array("item1", "item2", "item3")
End Sub
Private Sub CommandButton1_Click()
data("quit") = False
data("courtName") = Me.ListBox1.Value
data("courtType") = Me.ListBox2.Value
Unload Me
End Sub
Standard module code:
Option Explicit
Sub test()
Dim data As New Dictionary
data("quit") = True
Load UserForm1
Set UserForm1.data = data
UserForm1.Show
If data("quit") Then
MsgBox "Ввод данных отменен пользователем"
Exit Sub
End If
MsgBox data("courtName")
MsgBox data("courtType")
End Sub
Note the user form in that case can be closed (i. e. unloaded) right after all data is filled in and action button is clicked by user.
Another way is to check if the user form actually loaded:
Sub test()
UserForm1.Show
If Not isUserFormLoaded("UserForm1") Then
MsgBox "Ввод данных отменен пользователем"
Exit Sub
End If
End Sub
Function isUserFormLoaded(userFormName As String) As Boolean
Dim uf As Object
For Each uf In UserForms
If LCase(uf.Name) = LCase(userFormName) Then
isUserFormLoaded = True
Exit Function
End If
Next
End Function
I have 2 userforms, Userform_1 contains many TextBoxes (TextBox1, TextBox2, TextBox3, .....) & in Userform_2 I have 1 TextBox where user can enter value. Now I need to pass the User entered value in Userform_2 to be shown/stored in its respective triggering TextBox event in Userform_1. So when User want to pass in TextBox3 (Userform_1) then he/she will just use double_click trigger (activate Userform_2) & pass value which should be stored in TextBox3 only.
I tried this:
In Userform_1
Private Sub TextBox3_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
On Error Resume Next
UserForm2.Show
End Sub
In Userform_2
Private Sub CommandButton1_Click()
On Error Resume Next
If UserForm1.TextBox1.Value = "" Then UserForm1.TextBox1.Value = ComboBox1.Value
If UserForm1.TextBox2.Value = "" Then UserForm1.TextBox2.Value = ComboBox1.Value
If UserForm1.TextBox3.Value = "" Then UserForm1.TextBox3.Value = ComboBox1.Value
Unload Me
End Sub
Problem is that it will display entered value in any TextBox which is empty & not specifically in TextBox3. Any insight would be helpful.
Your code is doing exactly what you have asked it to do (not what you want it to do). Based on your description above, I offer the following (not tested).
In Userform_1
Private Sub TextBox3_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
UserForm2.Show
TextBox3.Value = UserForm2.ComboBox1.Value
End Sub
In Userform_2
Private Sub CommandButton1_Click()
Me.Hide
'Note not unloading, this means that the userform is still in memory and you can access the values after it is hidden.
' Also, hiding the form will return control back to the UserForm1 for further processing.
End Sub
And avoid using On Error, especially On Error Resume Next, all you are doing is hiding the bugs, not dealing with them. If you can anticipate that your code is going to cause an error, deal with it before that piece of code.
Thanks ADJ...
Your concept is good but it doesn't quite fulfill the requirement, just hiding the Userform doesn't remove/clear the entered value for next entry & reopening it again shows last entered value. I can write a code to blank it but then I have too many textboxes.
I used your concept of only calling required Userform from a specific TextBox...
Wrote below code, fits my requirement:
UserForm_1:
created hidden TextBox1 in UserForm_2 for identifier
UserForm2.TextBox1.Value = "<random identifier value>"
UserForm_2:
If TextBox1.Value = "<random identifier value>" Then
UserForm1.TextBox3.Value = "<User Input>"
End If
So this way I get to assign & call only identified TextBoxes.