Nested IF statements for a User Form - vba

My User-Form gives the user an option of 3 colors[red, green, blue] for "High Values" and "Low Values". However, the user must choose one color for each and not the same color for both High and Low Values of course. The colors highlight low and high values within the data in order to differentiate them. I have attached the picture of my User Form and the part of my code where I'm unable to assign different options different colors in order for it work. Any help on how to correct my IF Logic would be greatly appreciated.
Public Function ShowInputsDialog(LowColor As Long, HighValue As Single, HighColor As Long, LowValue As Single)
Call Initialize
Me.Show
If Not Cancel Then
If optRed1.Value Then '<-- Assigning the 3 colors to the Low Values
LowColor = vbRed
ElseIf optGreen1.Value Then
LowColor = vbGreen
Else
LowColor = vbBlue
End If
If optRed2.Value Then HighColor = vbRed '<-- Assigning the 3 colors to the High Values
ElseIf optGreen2.Value Then HighColor = vbGreen
Else
HighColor = vbBlue
End If
End If
HighValue = txtHigher.Value
LowValue = txtLower.Value
ShowInputsDialog = Not Cancel
Unload Me
End Function

I'd go as follows:
add a BeforeUpdate() event for every radio button
have that event handler let the control assume the user input value if compatible with its "counterpart" control one
this, by means of a sub that check the active control value against its "counterpart" one
for instance you could add in your userform code pane the following code:
Private Sub optBlue1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
CrossCheck
End Sub
Private Sub optBlue2_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
CrossCheck
End Sub
Private Sub OptGreen1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
CrossCheck
End Sub
Private Sub OptGreen2_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
CrossCheck
End Sub
Private Sub OptRed1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
CrossCheck
End Sub
Private Sub OptRed2_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
CrossCheck
End Sub
Private Sub CrossCheck()
Dim optNr As String, optName As String
With Me
If .ActiveControl.ActiveControl.Value Then
optName = .ActiveControl.ActiveControl.name
optNr = Mid(optName, Len(optName), 1)
.ActiveControl.ActiveControl.Value = Not (.ActiveControl.ActiveControl.Value = .Controls(Replace(optName, optNr, IIf(optNr = "1", "2", "1"))).Value)
End If
End With
End Sub
of course a Class approach could relieve the burden of writing all those Private Sub optXXXX_BeforeUpdate() event handlers and give you more flexibility for both current coding and future code enhancements, but if you are sticking to only have three radio buttons it could be a little overkill
BTW all what above means that your ShowInputsDialog() sub must not care about option buttons compatibility and can do its plain value assigning work, for which I'd use a Select Case syntax instead of the If Then - Else If Then - End If one:
If Not Cancel Then
Select Case True
Case OptRed1.Value
LowColor = vbRed
Case OptGreen1.Value
LowColor = vbGreen
Case Else
LowColor = vbBlue
End Select
Select Case True
Case OptRed2.Value
HighColor = vbRed
Case OptGreen2.Value
HighColor = vbGreen
Case Else
HighColor = vbBlue
End Select
End If
or you could use a helper function:
Function GetColor(opt1 As MSForms.OptionButton, opt2 As MSForms.OptionButton) As Long
Select Case True
Case opt1.Value
GetColor = vbRed
Case opt2.Value
GetColor = vbGreen
Case Else
GetColor = vbBlue
End Select
End Function
and simply write
If Not Cancel Then
LowColor = GetColor(OptRed1, OptGreen1)
HighColor = GetColor(OptRed2, OptGreen2)
End If

Related

VBA if in text is in textbox then do something

I've written the following code so that if a certain text exists in my listbox and "ok" button is clicked a certain thing is done.
Private Sub CommandButton3_Click()
If (Me.ListBox2.Text) <> ("PA") Then
Call macro1
ElseIf (Me.ListBox2.Text) <> "menu" Then
Sheets("menu").Visible = xlSheetVisible
Worksheets("menu").Activate
Else
MsgBox "Nothing is selected"
End If
End Sub
The problem is that when "ok" is clicked all events are still carried out even if the specified text isn't in the textbox.
You probably want to use = operator, and not <> operator. Also note that ListBox.List(i) is the correct way of getting selected item for single selection mode:
Private Sub CommandButton3_Click()
Dim SelectedItem = ListBox1.List(ListBox1.ListIndex)
If SelectedItem = "PA" Then
Call macro1
ElseIf SelectedItem = "menu" Then
Sheets("menu").Visible = xlSheetVisible
Worksheets("menu").Activate
Else
MsgBox "Nothing is selected"
End If
End Sub
Edit
Following your comment, you can create a function that looks for the existence of that item:
Private Function TextExists(text as String) as Boolean
Dim i as Long
For i = 0 To ListBox1.ListCount - 1
If ListBox1.List(i) = text Then
TextExists = True
Exit Function
End If
Next
TextExists = False
End Function
And then use this function in the main code like this:
Private Sub CommandButton3_Click()
If TextExists("PA") Then
Call macro1
ElseIf TextExists("menu") Then
Sheets("menu").Visible = xlSheetVisible
Worksheets("menu").Activate
Else
MsgBox "Nothing is selected"
End If
End Sub
N.B. I have written this manually here, without an IDE. Please check for indexes and other little things.

Multiple toggle buttons on worksheet to trigger a macro

I have 20 toggle buttons set-up like
Public Sub ToggleButton1_Click()
Check_All
If ToggleButton1.value = True Then
ToggleButton1.BackColor = vbGreen
Else:
ToggleButton1.BackColor = vbRed
End If
End Sub
Public Sub ToggleButton2_Click()
Check_All
If ToggleButton2.value = True Then
ToggleButton2.BackColor = vbGreen
Else:
ToggleButton2.BackColor = vbRed
End If
End Sub
I'm attempting to get them triggered by application.onkey like:
Application.OnKey "%a", "Sheet1.ToggleButton1_Click"
Application.OnKey "%b", "Sheet1.ToggleButton2_Click"
And using the Check_all for when all the buttons are "True" it'll do the following that is supposed to revert the toggle buttons back to the "False" state before it triggers another "macro".
Sub Check_All()
Dim tb As Object
For Each tb In Me.OLEObjects
If tb.ProgId = "Forms.ToggleButton.1" Then
If tb.Object.value <> True Then Exit Sub
End If
Next
CommandButton0_Click
End Sub
Private Sub CommandButton0_Click()
Dim x As Integer
For x = 1 To 20
Worksheets("Sheet1").OLEObjects("ToggleButton" & x).Object.value = False
Next x
CommandButton3_Click
End Sub
But I can't seem to get the application.onkey to work and I'm completely lost now.
This is a system that is hooked up to programmable USB buttons and I'm really relying on the onkey to function, unless there's a better way???
You can assign macros to shortcut keys in excel. You need to simulate the toggle-button click like this:
Sub CtrlShiftA()
ToggleButton1.Value = Not ToggleButton1.Value
End Sub
Then have your toggle-button sub as you like:
Private Sub ToggleButton1_Click()
If ToggleButton1.Value = True Then
ToggleButton1.BackColor = vbGreen
Else
ToggleButton1.BackColor = vbRed
End If
End Sub
Then under View -> Macros -> View Macros click Options..:
Press Shift + A to have Ctrl + Shift + A as shortcut key:
Now click OK, and enjoy your shurtcut key.

Makeshift Cue Banners

I'm trying to create some cue banners for my user form in Word. I got about half way through before I become stuck. I have it where the cue banner will disappear and clear the textbox once it has focus. If the user types their own text, that will be retained as well.
However, if the user doesn't type anything in the textbox once it has been cleared, I want to replace the cue banner along with its attributes (gray text and italicized). I can't seem to get it to work. Here's the code with everything related to this textbox below. The trouble is with the Leave event I think.
Private Sub UserForm_Initialize()
Me.txbShipToName1.Text = "name"
Me.txbShipToName1.Font.Italic = True
Me.txbShipToName1.ForeColor = &H80000006
End Sub
Private Sub txbShipToName1_Enter()
If Me.ActiveControl Is Me.txbShipToName1 And Me.txbShipToName1.Text = "name" Then
txbShipToName1.Font.Italic = False
txbShipToName1.ForeColor = &H80000008
txbShipToName1.Text = ""
End If
End Sub
Private Sub txbShipToName1_Leave()
If Me.ActiveControl Is Not txbShipToName1 And Me.txbShipToName1.Text = "" Then
txbShipToName1.Font.Italic = True
txbShipToName1.ForeColor = &H80000006
txbShipToName1.Text = LCase(txbShipToName1.Text)
txbShipToName1.Text = "name"
End If
End Sub
Private Sub txbShipToName1_Change()
If Me.ActiveControl Is Me.txbShipToName1 And Me.txbShipToName1 <> "name" Then
txbShipToName1.Text = UCase(txbShipToName1.Text)
End If
End Sub
I'm putting this here for anyone else who would like to know the solution. After a few days of messing with it, I finally realize I was over complicating things when I was messing with another piece of code. I eliminated the ActiveControl test because this is essentially already built into the Enter and Exit events. Here's the updated and working code.
Private Sub UserForm_Initialize()
'Populates cue banners
Me.txbShipToName1.Text = "Name"
Me.txbShipToName1.Font.Italic = True
Me.txbShipToName1.ForeColor = &H80000011
End Sub
Private Sub txbShipToName1_Enter()
'Removes cue banner upon entering textbox
If Me.txbShipToName1.Text = "Name" Then
txbShipToName1.Font.Italic = False
txbShipToName1.ForeColor = &H80000012
txbShipToName1.Text = ""
End If
End Sub 'txbShipToName1_Enter()
Private Sub txbShipToName1_Change()
'Converts textbox to uppercase
If Me.txbShipToName1.Text <> "Name" Then
txbShipToName1.Text = UCase(txbShipToName1.Text)
End If
End Sub 'txbShipToName1_Change()
Private Sub txbShipToName1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
'Replaces cue banner upon exiting textbox
If Me.txbShipToName1.Text = "" Then
txbShipToName1.Font.Italic = True
txbShipToName1.ForeColor = &H80000011
txbShipToName1.Text = "Name"
End If
End Sub 'txbShipToName1_Exit(ByVal Cancel As MSForms.ReturnBoolean)

VBA Excel Toggle Button "Latching"

I have the following code corresponding to three ToggleButtons in a VBA Program in Excel. When one button is clicked, it is supposed to stay "pressed" and the other two buttons are supposed to "release". However, with the code I have, I have to click on one button TWICE, once to release the other buttons and the other to keep the original one pressed.
However, if I add "ToggleButtonX.Value = True" to each of the Subs, when one button is clicked, it cannot release even after clicking another button. How could one configure this so that when one clicks on one button, that button stays pressed AND the other buttons get released?
EDIT: I would like to KEEP TOGGLEBUTTONS.
Private Sub ToggleButton1_Click()
ToggleButton2.Value = False
ToggleButton3.Value = False
End Sub
Private Sub ToggleButton2_Click()
ToggleButton1.Value = False
ToggleButton3.Value = False
End Sub
Private Sub ToggleButton3_Click()
ToggleButton1.Value = False
ToggleButton2.Value = False
End Sub
In addition to the OptionButton answer, which is probably easier and cleaner to implement, there's another way to do it as well by using logic that will only change the values of the other two buttons when one button is clicked. The code for the other buttons Click event will fire, but the variable sButton will determine that the code will only fire on the button that was physically pressed by the user.
Also note the use of Not Me.ToggleButton1.Value. This will ensure that button 2 and 3 are the opposite of button 1 with each click. The way your code was written, it would always revert the other buttons to False no matter what if the clicked button was True or False.
Option Explicit
Public sButton As String
Private Sub ToggleButton1_Click()
ButtonLoad 1
End Sub
Private Sub ToggleButton2_Click()
ButtonLoad 2
End Sub
Private Sub ToggleButton3_Click()
ButtonLoad 3
End Sub
Sub ButtonLoad(iButton As Integer)
Select Case iButton
Case 1
If sButton = "" Then
sButton = "1" 'set so that other buttons don't trigger
Me.ToggleButton2.Value = Not Me.ToggleButton1.Value
Me.ToggleButton3.Value = Not Me.ToggleButton1.Value
sButton = "" 'reset for next button click
End If
Case 2
If sButton = "" Then
sButton = "2"
Me.ToggleButton1.Value = Not Me.ToggleButton2.Value
Me.ToggleButton3.Value = Not Me.ToggleButton2.Value
sButton = ""
End If
Case 3
If sButton = "" Then
sButton = "3"
Me.ToggleButton2.Value = Not Me.ToggleButton3.Value
Me.ToggleButton1.Value = Not Me.ToggleButton3.Value
sButton = ""
End If
End Select
End Sub
By calling ToggleButtonx.Value = False you are simulating a click on that button and so it own code will run, and set the value of the button you just clicked to false.
Use instead the MouseDown event:
Private Sub ToggleButton1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
ToggleButton2.Value = False
ToggleButton3.Value = False
End Sub
Private Sub ToggleButton2_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
ToggleButton1.Value = False
ToggleButton3.Value = False
End Sub
Private Sub ToggleButton3_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
ToggleButton1.Value = False
ToggleButton2.Value = False
End Sub
This is easily done with Option Buttons (If that works with the rest of your implementation)
Private Sub OptionButton1_Click()
OptionButton1.Value = True
OptionButton2.Value = False
OptionButton3.Value = False
End Sub
Private Sub OptionButton2_Click()
OptionButton1.Value = False
OptionButton2.Value = True
OptionButton3.Value = False
End Sub
Private Sub OptionButton3_Click()
OptionButton1.Value = False
OptionButton2.Value = False
OptionButton3.Value = True
End Sub

Close UserForm if All Data Captured

Say you have aUserForm with TextBox1, TextBox3, TextBox3 and an OK Button.
To only allow the UserForm to close if all three TextBox have data I would use the following script assigned to the OK Button:
Private Sub CommandButton1_Click()
If Len(TextBox1.Value) >= 1 And _
Len(TextBox2.Value) >= 1 And _
Len(TextBox3.Value) >= 1 Then
Me.Hide
Else
MsgBox "Please Complete All Fields!"
End If
End Sub
Is there another way to do this besides an If statement?
Direct User Before Errors Are Made
Preferable to informing a user after an invalid action has been made is to prevent the user from performing that invalid action in the first place[1]. One way to do this is to use the Textbox_AfterUpdate event to call a shared validation routine that controls the Enabled property of your OK button, and also controls the display of a status label. The result is a more informative interface that only allows valid actions, thereby limiting the nuisance of msgbox popups. Here's some example code and screenshots.
Private Sub TextBox1_AfterUpdate()
RunValidation
End Sub
Private Sub TextBox2_AfterUpdate()
RunValidation
End Sub
Private Sub TextBox3_AfterUpdate()
RunValidation
End Sub
Private Sub RunValidation()
If Len(TextBox1.Value) = 0 Or Len(TextBox2.Value) = 0 Or Len(TextBox3.Value) = 0 Then
CommandButton1.Enabled = False
Label1.Visible = True
Else
CommandButton1.Enabled = True
Label1.Visible = False
End If
End Sub
Private Sub CommandButton1_Click()
Me.Hide
End Sub
The If Statement
As far as the If statement is concerned, there are a ton of ways that can be done, but I think anything other than directly evaluating TextBox.Value leads to unnecessary plumbing and code complexity, so I think it's hard to argue for anything other than the If statement in the OP. That being said, this particular If statement can be slightly condensed by capitalizing on its numeric nature, which allows for
Len(TextBox1.Value) = 0 Or Len(TextBox2.Value) = 0 Or Len(TextBox3.Value) = 0
to be replaced with
Len(TextBox1.Value) * Len(TextBox2.Value) * Len(TextBox3.Value) = 0
Although that doesn't gain you much and is arguably less readable code, it does allow for a condensed one liner, especially if the textboxes are renamed...
If Len(TB1.Value) * Len(TB2.Value) * Len(TB3.Value) = 0 Then
.Value vs .Text
Lastly, in this case, I think .Value should be used instead of .Text. .Text is more suited for validating a textbox entry while its being typed, but in this case, you're looking to validate a textbox's saved data, which is what you get from .Value.
More User feedback - Colorization
I almost forgot, I wanted to include this example of how to include even more user feedback. There is a balance between providing useful feedback and overwhelming with too much. This is especially true if the overall form is complicated, or if the intended user has preferences, but color indication for key fields is usually beneficial. A lot of applications may present the form without color at first and then colorize it if the user is having trouble.
Private InvalidColor
Private ValidColor
Private Sub UserForm_Initialize()
InvalidColor = RGB(255, 180, 180)
ValidColor = RGB(180, 255, 180)
TextBox1.BackColor = InvalidColor
TextBox2.BackColor = InvalidColor
TextBox3.BackColor = InvalidColor
End Sub
Private Sub TextBox1_AfterUpdate()
RunValidation Me.ActiveControl
End Sub
Private Sub TextBox2_AfterUpdate()
RunValidation Me.ActiveControl
End Sub
Private Sub TextBox3_AfterUpdate()
RunValidation Me.ActiveControl
End Sub
Private Sub RunValidation(ByRef tb As MSForms.TextBox)
If Len(tb.Value) > 0 Then
tb.BackColor = ValidColor
Else
tb.BackColor = InvalidColor
End If
If Len(TextBox1.Value) * Len(TextBox2.Value) * Len(TextBox3.Value) = 0 Then
CommandButton1.Enabled = False
Label1.Visible = True
Else
CommandButton1.Enabled = True
Label1.Visible = False
End If
End Sub
Private Sub CommandButton1_Click()
Me.Hide
End Sub
As I said in my comment, that is an ok way to do it. But i'll post this just so you have an example of another way. This would allow you to evaluate what is going into the text boxes as they are set.
Option Explicit
Dim bBox1Value As Boolean
Dim bBox2Value As Boolean
Dim bBox3Value As Boolean
Private Sub TextBox1_Change()
If Trim(TextBox1.Text) <> "" Then
bBox1Value = True
End If
End Sub
Private Sub TextBox2_Change()
If Trim(TextBox2.Text) <> "" Then
bBox2Value = True
End If
End Sub
Private Sub TextBox3_Change()
If Trim(TextBox3.Text) <> "" Then
bBox3Value = True
End If
End Sub
Private Sub CommandButton1_Click()
If bBox1Value = True And bBox2Value = True And bBox3Value = True Then
Me.Hide
Else
MsgBox "Please Complete All Fields!"
End If
End Sub
You can use a loop:
Private Sub CommandButton1_Click()
Dim n as long
For n = 1 to 3
If Len(Trim(Me.Controls("TextBox" & n).Value)) = 0 Then
MsgBox "Please Complete All Fields!"
Exit Sub
End If
Next n
Me.Hide
End Sub
You can use the below code
Private Sub CommandButton1_Click()
If Trim(TextBox1.Value & vbNullString) = vbNullString And _
Trim(TextBox2.Value & vbNullString) = vbNullString And _
Trim(TextBox3.Value & vbNullString) = vbNullString Then
Me.Hide
Else
MsgBox "Please Complete All Fields!"
End If
End Sub
I got the answer from this question
VBA to verify if text exists in a textbox, then check if date is in the correct format