vba unloads all userforms on unload in sub userform - vba

I have a userform that loads another userform
Private Sub CommandButtonAddRule_Click()
UserFormAddRule.Show
End Sub
If the UserFormAddRule either using "x" or a button with
Unload Me
the code return after the .show command, but then closes the first userform as well. This is what must not happen. How can I prevent the first userform to close as well?

Dim UForm As Object
Dim i As Integer
i = 0
For Each UForm In VBA.UserForms
Debug.Print UForm.Name
If UForm.Name <> "FormNameThatMustBeLeftOpen"
UForm.Hide
Unload VBA.UserForms(i)
i = i + 1
End If
Next
Hi the above has been tried, tested and should work. Your contrl structure can be an if statement, select case, or do while. The logic does not differ. if you need the same implementation using a different control structure or the full example, give me a shout.
Or you can
Dim i As Long
For i = VBA.UserForms.Count - 1 To 0 Step -1
If UForm.Name <> "FormNameThatMustBeLeftOpen"
Unload VBA.UserForms(i)
End If
Next i

Showing and Unloading the Child with or without the VB Editor Open
In the parent form, to create the child, and assuming the child is modal such that you cannot interact with the parent while the child is visible, which is the default, use:
dim frm as frmChild
set frm = new frmChild
... then call a public method of frmChild to initialize whatever variables it needs from the parent form in order to perform whatever task the parent is delegating to the child.
In the child form the last line of the init method should be:
me.show
Then use within the child:
me.hide
... when you are done inside the child.
Then in the parent form, immediately after calling the child's public method that initializes and shows the child, use the following line to unload the child and free memory:
set frm = nothing
The parent form will remain open.

This might have been an isolated issue, but in my case it is because the UserForm that I wanted to keep open was set to ShowModal False when it needed to be True

Related

Hide a Form tab depending on the value of a field

pretty simple question here in scope.
Question:
Wondering If I would be able to hide the tabs of a form based off the values of a table's fields.
I have been reading the 2019 Access Bible and so far it is still unclear to me how I would write the VBA module to constantly be running. Im asking the question a little early in my attempts, but hoping I can ask this well enough to get a head start.
I dont quite understand the execution model of VBA for access yet. I have prior expierence coding but this will be my 1st time in access. Just looking for a little help on how to write the function scope. You see below that I think it should be "Main" something, as I want it to run whenever in form view. Like I know how to write Private sub functions to respond to a button click. But not for a function that just runs in the background of the form.
I guess the real question is when to execute? Right? Any suggestions?
I was thinking something along the line of this below.
Main function()
If Me.FieldProcess_Water = "1" Then
Me.TabProcess_Water.Visible = True
Else
Me.TabProcess_Water.Visible = False
End If
End Sub
This requires some setup to reduce redundant code but should do what you want.
First you'll need your checkbox names and page names to be similar. In my example I have the page names as Tab_Name and the checkboxes as just Name. eg. Tab_Process Water and Process Water.
Then Create a sub called Form_Current() in the form's code-behind.
Private Sub Form_Current()
Dim chk As Control
For Each chk In Me.Controls
If chk.ControlType = acCheckBox Then
If chk = False Then
'Change TabCtl0 to whatever your's is called
Me.TabCtl0.Pages("Tab_" & chk.Name).Visible = False
Else
Me.TabCtl0.Pages("Tab_" & chk.Name).Visible = True
End If
End If
Next
End Sub
This will iterate through the current record's checkboxes and toggle it's respective tab's visibility.
To have the CheckBox update the tabs as they are clicked:
Private Sub Form_Load()
Dim chk As Control
Dim prop As Variant
For Each chk In Me.Controls
If chk.ControlType = acCheckBox Then
chk.OnClick = "=Check_Click()"
End If
Next
End Sub
This will assign a command to each checkbox.
Dim caller As String
caller = Me.ActiveControl.Name
If Me.ActiveControl.Value = False Then
Me.TabCtl0.Pages("Tab_" & caller).Visible = False
Else
Me.TabCtl0.Pages("Tab_" & caller).Visible = True
End If
This will hide the relevant tabs.

How do I hide/unhide a subform based on the input from two comboboxes?

I have a mainform, called TrainingsSU
In it I am calling subform qry_TrainingSU (built from the query of the same name)
I have it set up so that the records auto populate the subform based on the two comboboxes in the main form.
But what I need to do is hide the subform, and have it display only when the two comboboxes are populated and records are loaded.
Here's the current VBA (of which I am not really even a novice at)
Private Sub cbo_EmployeeLookup_AfterUpdate()
Me!qry_TrainingsSU.Requery
If Me.cbo_EmployeeLookup.Value = "" Then
Forms!qry_TrainingsSU.Visible = False
Else
Forms!qry_TrainingsSU = True
End If
End Sub
Private Sub cbo_TrainingName_AfterUpdate()
Me!qry_TrainingsSU.Requery
If Me.cbo_TrainingName.Value = "" Then
Forms!qry_TrainingsSU = False
Else
Forms!qry_TrainingsSU.Visible = True
End If
End Sub
I found the general form of this code in another answer, here: MS Access: Hide and Unhide subform based on drop down in Main Form
However the code doesn't seem to be working for me.
Currently the subform is set to Visible:No
So nothing shows up at all.
If I change that, it doesn't disappear and the empty subform is still visible.
This wouldn't be a problem, except I need to use this form for another query and want to layer them over eachother when the second subform is ready to be used.
Later this form will be used to push an UPDATE SET to a table based on the different subforms.
Is there something that is obviously wrong with the code, or did I miss a setting somewhere?
You can try this:
Private Sub updateStates()
Me!qry_TrainingsSU.Form.Requery
If (Me.cbo_EmployeeLookup.Value <> "" AND Me.cbo_TrainingName.Value <> "") Then
Me!qry_TrainingsSU.Visible = True
Else
Me!qry_TrainingsSU.Visible = False
End If
End Sub
Private Sub cbo_EmployeeLookup_AfterUpdate()
updateStates
End Sub
Private Sub cbo_TrainingName_AfterUpdate()
updateStates
End Sub
Forms!qry_TrainingsSU searches form opened as main form, not a subform.

Question on multiple checkboxes launching code

I have a user form and a frame with 35 checkboxes in it, numbered 1 to 35. They represent 35 Named Ranges. I test to see if any of the name ranges are not set, if set correctly the checkbox value is set to TRUE.
I found some code that would allow me to trigger a sub if one of the checkboxes is clicked. That code seems to work, but my check code above also triggers the checkbox events, which I do not want. I only want the sub to run when the checkbox is clicked with the mouse? I can post the code I'm using, but though I'd first ask the question to see if what I would like to do is possible.
Thanks,
Jim
Code in class module:
Public WithEvents ChkBox As MSForms.CheckBox
Public Sub AssignClicks(ctrl As Control)
Set ChkBox = ctrl
End Sub
Public Sub chkBox_Click()
If chkBoxProcess = "Y" Then
'ThisWorkbook.Worksheets("Sheet1").Range(ChkBox.Name).Value = Format(Now, "dd.mm.yyyy")
'MsgBox ("check box number = " & ChkBox.Name & " " & ChkBox.Value)
' Else
End If
End Sub
Code in Forms:
Public Sub UserForm_Initialize()
Dim SheetCount, i As Integer
Dim sh As Worksheet
'Public SheetName, SheetName2, StartOldNewTimeA, OldNewTimeAdd As String
'Initialize the form frmChgNameRng
'Set array values of the day options
'Set array values for 12:00 timeframes
'Set array values for 12:30 timeframes
'Set colors used in Checkboxes
'Set array for Checkboxes (boxes are numbered accross the page, 1 corressponds to Mon_1200/Mon_1230, 8 corresponds to Mon_200/Mon_230, etc.)
'Formulas are placed in the time cells on the left of the page, the macro will add the appropriate value into the Mon_1200 time slot and all other cells update off that cell
chkBoxProcess = "N"
Dim ChkBoxes As cls_ChkBox
Dim ctrl As Control
Set colTickBoxes = New Collection
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" Then
Set ChkBoxes = New cls_ChkBox
ChkBoxes.AssignClicks ctrl
colTickBoxes.Add ChkBoxes
End If
Next ctrl
'..... lots of code for Range Name Checks, etc.
End Sub
Your code is conflating control state with model data, and so the only way to tell it "named range 32 is ON", or "named range 13 is OFF", is to alter a checkbox' state, which fires that control's Change event.
There's no way around that, it's just how controls work: they fire a Change event whenever their value changes, regardless of how that's done.
Instead of having controls' state be the data, make the controls' state alter the data.
This requires conceptualizing this data, first: looks like you need to associate a number/index to some Boolean value. An array can do this.
Private namedRangeStates(1 To 35) As Boolean
Note that depending on what you're doing, initializing the state should be feasible by iterating the workbook's Names collection in the UserForm_Initialize handler. Or better, the form could expose a method that takes an array of Boolean values, and copies that state into namedRangeStates.
Now, when a checkbox is modified, make it alter the state:
Private Sub Checkbox31_Change()
namedRangeStates(31) = Checkbox31.Value
End Sub
Your form can expose that state as a property:
Public Property Get NamedRangeState(ByVal index As Long) As Boolean
NamedRangeState = namedRangeStates(index)
End Property
Public Property Let NamedRangeState(ByVal index As Long, ByVal value As Boolean)
namedRangeStates(index) = value
End Property
And now you can modify the enapsulated state independently of the combobox values.

Call a sub after this one has finished

I have a userform looping through a range with 2 settings; manual and automatic.
When I have an option button on my form set to manual, and click a next command button, I check the next cell in the range, change the contents of the form accordingly, then wait for the next button press.
However if I find the option button is set to automatic then instead of finishing up my code and waiting for the next button press, I have to call the next button press programmatically. That means that the previous subs each calling the next code slowly build up in the call stack, and I worry this will have some memory implications if I'm looping over a large range.
In code:
Private Sub nextItem()
Dim willShow As Boolean
returnResults 'return details from the form to the sheet
clearMemory 'clear out previous items on form
itemNo = itemNo + 1 'iterate over the range
SetupParts 'place new items on form
'do what next
Select Case displaySetting 'this variable holds the result from my option button in a custom enum "dispMode"
Case dispMode.Manual 'always show form
willShow = True
Case dispMode.SemiAutomatic 'show form based on condition
willShow = (Data.Count = 0) 'if SetupParts returns no data, display form, otherwise keep hidden
Case dispMode.Automatic 'don't show form
willShow = False
End Select 'there are actually a few more options here, but I've simplified
If willShow = False Then
If Me.Visible = True Then 'if needs to hide, and currently visible, then hide the form
Me.Hide
nextItem 'this is the problem, I call this code again, so the call stack grows
Else
'form is already hidden, do nothing
End If
ElseIf Me.Visible = False Then 'willShow must be True
Me.Show 'then wait for button click
End If
End Sub
Private Sub commandBtnNext_Click()
nextItem
End Sub
To avoid this problem, is there any way of getting nextItem to run immediately after the previous call of nextItem has run; ie. to tell a sub to run immediately after this one has finished (without introducing time delays). Or maybe this isn't an issue; if so, please explain why.
UPDATE
There is also a SemiAutomatic check to see which mode to use based on the contents of the userform. This is fine when calling recursively, but I can't see how to incorporate it into a looping approach.

Forcing the unloading forms from memory

I am writing a solution in Excel that uses a number of linked data entry forms. To move between he sequence of forms, the user can click a "Previous" or "Next button. The current form is unloaded and the new one loaded and opened.
Sub NextForm(curForm As MSForms.UserForm, strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
Dim newForm As Object
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
Debug.Print "No"
Else
Unload curForm
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
Set newForm = VBA.UserForms.Add(strNewForm)
newForm.Show
End Sub
The code as is allows new forms to be added into the sequence at any time through the editing of the range "SYS.formlist".
One problem I have noticed is that even after the current form is unloaded, it still remains in the VBA.Userforms collection. I would presume this is because this code has been called from that userform.
Is there a way to force the removal of that form from the VBA.Userforms collection? What is occuring is that if the user moves forward and then back, two copies of the form appear in memory and and excel throws exceptions about two modal forms being open.
Cheers,
Nick
The answer was (sadly) quite simple and inspired by bugtussle's answer.
The subroutine was passing the curForm variable as an MSForms.Userform object, but the form is held in memory as its own object type. (As an example, you can access a form through Set form = new formName)
So by changing the curForm paramater type to Variant, it will pass the actual object through rather than a copy of the object. Unload was only unloading the copy, not the actual object.
Thanks bugtussle!
So, corrected code is:
Sub NextForm(curForm As Variant, strFormName As String)
Dim intCurPos As Integer
Dim strNewForm As String
Dim newForm As Object
intCurPos = WorksheetFunction.Match(strFormName, Range("SYS.formlist"), 0)
If intCurPos = WorksheetFunction.CountA(Range("SYS.formlist")) Then
Debug.Print "No"
Else
Unload curForm
strNewForm = WorksheetFunction.Index(Range("SYS.formlist"), intCurPos + 1)
Set newForm = VBA.UserForms.Add(strNewForm)
newForm.Show
End Sub
I'm thinking that unloading from the collection object instead of the variable will really get rid of it. Try something like this:
For i = VBA.UserForms.Count - 1 To 0 Step -1
if VBA.UserForms(i).Name = curForm.name
Unload VBA.UserForms(i)
end if
Next i