Checkbox in UserForm on startup not working - vba

I have a userform with 2 dropdowns which I normally not need so I build a checkbox and hide the dropdown menu and the text label. The checkbox is checked by default. But the dropdown and label isn't hidden when I start the UserForm. When I manually uncheck and check the checkbox after UserForm is started it's working. So I dont know why the checkbox seems to work but I need to uncheck/check it manually after every start of the UserForm.
I think I have to do something at the initial start of the userform?
Private Sub SortCheckBox(blnChecked As Boolean)
Private Sub CheckBox1_Click()
ActiveDocument.Bookmarks("KurbeitragKinder").Range.Font.Hidden =
CheckBox1.Value
If CheckBox1.Value = True Then
Label8.Enabled = False
Label8.Visible = False
Label9.Enabled = False
Label9.Visible = False
ComboBox6.Enabled = False
ComboBox6.Visible = False
ComboBox7.Enabled = False
ComboBox7.Visible = False
Else
Label8.Enabled = True
Label8.Visible = True
Label9.Enabled = True
Label9.Visible = True
ComboBox6.Enabled = True
ComboBox6.Visible = True
ComboBox7.Enabled = True
ComboBox7.Visible = True
End If
End Sub

The state of the controls on a UserForm when it is first displayed will be, by default, whatever the design-time state is; let's call this the "default state".
You can define what that default state is by configuring each control's individual properties, using the designer's properties toolwindow (F4).
If the form's default instance is shown, then its state will be preserved between calls:
UserForm1.Show
...unless the instance gets reset - which can easily happen if you Unload that form, or if the user clicks the red "X" button to close it: the instance is destroyed, and since forms have a predeclared ID (aka default instance), the object is automatically re-created the next time it's referenced - with whatever the default (design-time) state is. If you handle the QueryClose event and programmatically Hide the form when CloseMode is VbQueryClose.vbFormControlMenu (and set the Cancel parameter to True, to prevent destroying the form instance and its state), then the state will be preserved ...and this can lead to unexpected or inconsistent behavior.
The solution is to make sure you always display a fresh new instance of the form, instead of the default one:
With New UserForm1
.Show
End With
That way the form's state is guaranteed to always be the default/intended design-time state every time it's displayed, and you can access the form's state between .Show and End With. All you need to do is to handle QueryClose and cancel the form's destruction when the user clicks the "form control menu" aka "the X button".
Initializing a form will raise the Initialize event; if you're using the form's default instance (i.e. UserForm1.Show), then you can't really control exactly when this happens, but if you show a fresh new instance every time (i.e. With New UserForm1), then you are certain that this event will be raised exactly once, every time you need to show the form.
The Initialize event is raised as soon as the object is created, and that happens before the first member call is made against it (i.e. when the New UserForm1 returns, the event has already executed). If you need to check a box and then initialize the form accordingly, then you might want to handle the Activate event instead, which will be raised when the form is actually displayed (i.e. when the .Show method is called):
With New UserForm1 'UserForm_Initialize runs
.CheckBox1.Value = foo 'form state is accessible here
.Show 'UserForm_Activate runs
'UserForm_QueryClose runs
foo = .CheckBox1.Value 'form state is accessible here
End With 'UserForm_Terminate runs
Looks like you want to run that CheckBox1_Click handler before the form is shown - problem is, event handlers aren't Public, and you don't want them to be.
The solution is to pull the logic into a Public Sub, invoke that procedure from the client code, and invoke it from the checkbox':
Public Sub InitializeFormState()
Dim isChecked As Boolean
isChecked = CheckBox1.Value
ActiveDocument.Bookmarks("KurbeitragKinder") _
.Range.Font.Hidden = isChecked
Label8.Enabled = isChecked
Label8.Visible = isChecked
Label9.Enabled = isChecked
Label9.Visible = isChecked
ComboBox6.Enabled = isChecked
ComboBox6.Visible = isChecked
ComboBox7.Enabled = isChecked
ComboBox7.Visible = isChecked
End Sub
Private Sub CheckBox1_Click()
InitializeFormState
End Sub
And now your client code can look like this:
With New UserForm1
.InitializeFormState
.Show
'consume form state here
End With
Or, you can invoke InitializeFormState from the form's Initialize or Activate handler, as needed:
Private Sub UserForm_Initialize()
InitializeFormState
End Sub
Or
Private Sub UserForm_Activate()
InitializeFormState
End Sub
In which case the procedure should probably be made Private, and there's no need to invoke it from the client code before the .Show method.

Related

How to declare an object as a parameter to update a userform?

I have a userform which contain checkboxes and labels.
My goal is to enable or disable specific labels if checkboxes are true or false.
I have:
a module in which I store my functions and subs
a main module
the userform
I could write in the userform:
Private Sub Checkbox1 ()
If Userform.Checbox1 = true then
Userform.label.enable = true
End if
However I have a few checkboxes and labels and I'd like to create a sub or function to simplify my code.
In the module in which I store my function I wrote:
Sub EnableMyLabels(Checkbox as object , Label as object)
If Userform.Checkbox = true then
Userform.label.enable = true
End If
and in my userform I tried to use it like this:
Call EnableMyLabels (Checkbox1 , Label1)
Your Sub should be like
Sub EnableMyLabels(cb As MSForms.CheckBox, lbl As MSForms.Label)
If cb.Value = True Then
lbl.Enabled = True
End If
End Sub
And the call
EnableMyLabels Checkbox1, Label1
You might want to call it from an Event on the Checkbox, eg
Private Sub CheckBox1_Change()
EnableMyLabels CheckBox1, Label1
End Sub
You should try the same principle but using Events.
On your VBE, at the Userform module select "Checkbox1" on the top dropdown.
Then select the method "Click" on the right top dropdown.
A method called Checkbox1_Click should have been created.
That code will run whenever the user clicks the checkbox.
Include all your "label logic" (or logic for other controls too) there.
When you are directly passing the objects (passed byRef by default), then there is no need of the Userform word in your code. Simply drop that and your code should work perfectly.
Sub EnableMyLabels(Checkbox as object , Label as object)
If Checkbox = true then
label.Enabled = true 'fyi - Enabled not Enable
End If
End if

How do I prevent control passing to the next Userform textbox?

I have a Userform with multiple Textboxes. All the Textboxes are in tab-order.
Each subsequent Textbox is back colored yellow to indicate to the user which is the next Textbox to complete.
If the Textbox Value is determined to be invalid I want the control to SetFocus back on that particular Textbox. However, control is automatically handed over to the next Textbox in the tab-order.
When I try to focus back on the required Textbox with the mouse, this fires an event on the next Textbox, which follows the rules of my program and requests the user to enter a valid value.
Below is a sample of two Textboxes, if the user fails to enter a first name I want the control to return to the tbxCustomerFirstName Textbox, however, the control is handed over to the tbxCustomerSurName Textbox, even though I've "tbxCustomerFirstName.SetFocus".
AstFlag = 2 means there has to be a valid value in the Textbox.
AstFlag = 1 means the Textbox value can be blank.
I stepped through the program and AstFlag does indeed = 2, and the set focus code is executed.
'====================================================================================
'
' Customer First Name
'
Private Sub tbxCustomerFirstName_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call tbxValues(3)
If tbxCancel = True Then
Cancel = True
End If
If AstFlag = 2 Then
tbxCustomerFirstName.SetFocus
End If
End Sub
'====================================================================================
'
' Customer Surname
'
Private Sub tbxCustomerSurName_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call tbxValues(4)
If tbxCancel = True Then
Cancel = True
End If
If AstFlag = 2 Then
tbxCustomerSurName.SetFocus
End If
End Sub
You want to set the Cancel parameter to True when you mean to cancel exiting the control.
I don't know what your tbxCancel flag logic is, but...
If AstFlag = 2 Then
theControlYouAreExiting.SetFocus
End If
Should be
If AstFlag = 2 Then
Cancel.Value = True
End If
That will prevent exiting the control, so there's no need to SetFocus on anything. Now again I don't know what tbxCancel is supposed to be doing, but it doesn't look right.
In fact I would scrap all this painfully duplicated conditional logic, and implement some Validate method that returns True if the field is valid (and thus can be exited) and False if the field is invalid (and thus Cancel.Value must be True and focus remains on the control).
Cancel.Value = Not Validate(args)
Where args could be theControlYouAreExiting.Tag, and then you could use the controls' Tag property to hold the metadata you're likely currently hard-coding in tbxValues.
Ultimately the problem you're solving is going to create a mess no matter how you put it, because everything is happening in the form's code-behind: you want to separate the View (the form/UI) from the Model (the data it's manipulating).
The solution is to create a class module that represents your model. This model exposes a property that each control on your form manipulates, and then the model itself knows how to validate each property.
Option Explicit
Private model As CustomerModel
Private Sub UserForm_Initialize()
Set model = New CustomerModel
End Sub
Public Property Get CustomerModel() As CustomerModel
Set CustomerModel = model
End Property
Public Property Set CustomerModel(ByVal value As CustomerModel)
Set model = value
End Property
Private Sub tbxCustomerSurName_Change()
model.Surname = tbxCustomerSurName.Text
End Sub
Private Sub tbxCustomerSurName_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Cancel.Value = Not model.IsValidSurname
End Sub
And with that you don't need to maintain flags and state switches to keep track of what the metadata is for the control you're in.

Make Excel visible when closing userform

I have a button in an Excel worksheet that opens a userform when clicked. It also hides the worksheet, making it so you can only see the userform. Here's what I have to make that happen. This works fine.
Sub Button2_Click()
Application.Visible = False
frmDataColl.Show
End Sub
Then I have this code that isn't working the way I want it to. Upon clicking the X to close the userform, I want the userform to close and also make Excel visible again. But this only closes the userform. It doesn't bring the workbook back into view.
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Application.Visible = True
End If
End Sub
Get rid of the Queryclose event and change your code to:
Sub Button2_Click()
Application.Visible = False
frmDataColl.Show
Application.Visible = True
End Sub
The form has no business knowing about Application.Visible state. As this answer shows, Button2 set it to False, it's Button2's job to set it back to True.
Your code works perfectly fine here - the problem is likely in code you haven't posted. Keep procedures' responsibilities well defined and you won't have these issues.
Another common trap you've fallen into (as did the other answer), is to work with a stateful default instance. Forms are objects - treat them as such!
Application.Visible = False
With New frmDataCol1
.Show
' here you still have access to the form's state!
End With
Application.Visible = True
Now, if you don't handle QueryClose, that red "X" button effectively destroys the object and, normally, you absolutely don't want that.
What you want, is to control the object's lifetime yourself - that's what you have that With block for!
So you do handle QueryClose, but only to say "Hide the form instance, don't destroy it!" - like this:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
Me.Hide
End If
End Sub
That way when your dialog gets closed, the calling code can still access the public state and act accordingly. If you worked off the default instance, letting QueryClose destroy the instance causes the state to be lost. And since it's a default instance, a brand new one gets created as soon as you query that state, making the bug extremely difficult to find... if you don't know how objects with a default instance work.

Disable a button via a function?

Currently using vba with excel 2007...
I am currently testing the capabilities of functions and am a little stuck using buttons. I have two buttons, named ONE and TWO. Pressing either calls up a function Calc with each button passing the variable of the others name. As follows:
Private Sub ONE_Click()
Calc TWO
End Sub
Private Sub TWO_Click()
Calc ONE
End Sub
Function Calc(B As CommandButton)
B.Enabled = False
End Function
My understanding is that pressing button ONE passes the variable TWO to the Function Calc, and then disables button TWO.
I also have a button labeled Reset which serves as follows:
Private Sub Reset_Click()
ONE.Enabled = True
TWO.Enabled = True
End Sub
The results of this, if I press button ONE, button TWO greys out and appears "disabled". However when I hit the reset button, it stays grey. Investigating the properties of the button reveals that it doesn't actually get disabled. I installed another button that directly disables button two, i.e. TWO.Enabled = False, instead of using a variable to do so.
When the button is directly disabled using the direct button, resetting enables the button as it should, and the properties reflect that the button is disabled.
Would anyone know why using a variable to disable a button like this results in an illusionary disabling?
And better yet, how to overcome the issue?
If you have trouble calling through an indirrect reference, then use a static parameter.
Private Sub ONE_Click()
Calc "TWO"
End Sub
Private Sub TWO_Click()
Calc "ONE"
End Sub
Now you can use that parameter to act on the object.
Function Calc(B As String)
If B = "ONE" Then
ONE.Enabled = False
ElseIf B = "TWO" Then
TWO.Enabled = False
Else
Exit Function
End If
End Function

VBA EnableEvents fire textbox_change

I have a textbox with code on change, and if I press a button with the following code
Private Sub CommandButton1_Click()
Application.EnableEvents = False
TextBox1.Text = "new text"
Application.EnableEvents = True
End Sub
but this still fires the on change event of the textbox.
This happens because Application.EnableEvents allows enabling/disabling events fired from the Application (i.e. Excel).
In your case, the parent firing the TextBox1 change is not the Application but rather the UserForm. This is an example from cpearson about how to create your own EnableEvents property on your Userform. I report the content of the link here (to avoid "link-only answer"):
To suppress events in a form, you can create a variable at the form's
module level called "EnableEvents" and set that to False before
changing a property that will cause an event to be raised.
Public EnableEvents As Boolean
Private Sub UserForm_Initialize()
Me.EnableEvents = True
End Sub
Sub Something()
Me.EnableEvents = False
' some code that would cause an event to run
Me.EnableEvents = True
End Sub
Then, all of the controls on form should have a test if that variable
as their order of business in any event code. For example,
Private Sub ListBox1_Change()
If Me.EnableEvents = False Then
Exit Sub
End If
MsgBox "List Box Change"
End Sub
You can declare the EnableEvents as Private if only procedures with
that form need to suppress events. However, if you have forms that are
programmatically linked together, such UserForm2 adding an item to a
ListBox on UserForm1, you should declare the variable as Public and
set it for another form with code like the following:
UserForm1.EnableEvents = False
'
' change something on UserForm1
'
UserForm1.EnableEvents = True