Formatting Text Boxes in a Userform - vba

I have a Userform that includes Text Boxes with multiple formats. I have the Initialize as blank ("") and then format them using afterupdate(). This all works fine, my issues come from the possibility of the user miss keying the data the first go around or just clicking aimlessly on there screen. After you input a value it formats it correctly when you move from the text box. But if you reselect the text box then move away again, it clears the value. And if you do this with the text box that is formatted as a percent it actually bugs out with a mismatch error.
Here is a slice of my current code:
Private Sub UserForm_Initialize()
ValueAnalysisTextBox.Value = ""
CapRateTextBox.Value = ""
End Sub
Private Sub ValueAnalysisTextBox_AfterUpdate()
ValueAnalysisTextBox.Value = Format(Val(ValueAnalysisTextBox.Value), "$#,###")
End Sub
Private Sub CapRateTextBox_AfterUpdate()
CapRateTextBox.Value = Format(Val(CapRateTextBox.Value) / 100, "Percent")
End Sub
Any thoughts on how to clean this up would be great.

Is this what you are trying?
Private Sub ValueAnalysisTextBox_AfterUpdate()
Dim amt As Double
amt = Val(Replace(ValueAnalysisTextBox.Value, "$", ""))
ValueAnalysisTextBox.Value = Format(amt, "$#,###")
End Sub
Private Sub CapRateTextBox_AfterUpdate()
Dim Perct As Double
Perct = Val(Replace(CapRateTextBox.Value, "%", "")) / 100
CapRateTextBox.Value = Format(Perct, "Percent")
End Sub
Note: I am not doing any other error handling. For example, user typing "Blah Blah" or pasting something else in the textbox. I am sure you can handle that.

I'd store the underlying values in the .Tag property of the TextBox, then use it to change the formatting back and forth in the Enter and Exit events:
Private Sub UserForm_Initialize()
ValueAnalysisTextBox.Value = vbNullString
ValueAnalysisTextBox.Tag = vbNullString
End Sub
Private Sub ValueAnalysisTextBox_Enter()
ValueAnalysisTextBox.Value = ValueAnalysisTextBox.Tag
End Sub
Private Sub ValueAnalysisTextBox_Exit(ByVal Cancel As MSForms.ReturnBoolean)
If IsNumeric(ValueAnalysisTextBox.Value) Then
ValueAnalysisTextBox.Tag = Val(ValueAnalysisTextBox.Value)
ValueAnalysisTextBox.Value = Format$(ValueAnalysisTextBox.Tag, "$#,###")
Else
ValueAnalysisTextBox.Tag = vbNullString
End If
End Sub

Related

Userform closes after "End Sub" without ever calling "Unload Me"

I have a userform (baseUF) that has multiple pages and buttons that all do different things. I have this baseUF being modeless because I want the user to be able to play with the sheet without closing the userform and losing all of the data they input. However, I started having a problem that might be due to the modeless nature of the baseUF.
There are other userforms that can be called from the baseUF. One executes with no issue by double clicking a textbox. However, the other userform is loaded after a button click. Once that button click sub is finished, the baseUF closes after the Exit Sub OR End Sub line. I don't remember this happening in the past and it doesn't happen with any other button click subs.
Does anybody have an idea what the issue could be? I'm pretty lost because I don't have a command to close the baseUF anywhere in that sub. Below is some code to show what is happening:
This sub is connected to a button on the spreadsheet to open the baseUF (the code is in a module).
Sub Button1_Click()
' show the userform
baseUF.Show vbModeless
End Sub
And this is the sub in the baseUF that calls an additional userform (LoadBox) which seems to be the issue.
Private Sub LoadQuery_Click()
' I Dim a bunch of stuff here
' if there are no saved queries, alert the user
If saveSht.Range("B3").Value = "" Then
MsgBox "No saved queries!"
Exit Sub
' if there is only one saved query, add it to the array and pop up the userform that allows for the user to select which save to load
ElseIf saveSht.Range("B4").Value = "" Then
save_names = saveSht.Range("B3").Value
LoadBox.Show
' otherwise, add all of the save names to the array and pop up that userform
Else
save_names = saveSht.Range(saveSht.Range("B3"),saveSht.Range("B3").End(xlDown)).Value
LoadBox.Show
End If
' if the user didn't select a save to load, stop trying to make stuff happen
If load_name = "" Then
' the userform will also close here if this turns out to be true
Exit Sub
End If
' do a bunch of stuff with the selected name here
' and after this line, the userform that contains this code closes
End Sub
EDIT: here is some code showing the two other userforms
This one is the userform with no issue that is called after a textbox is double clicked
Private Sub UserForm_Initialize()
' On start up of this form, populate the listbox with the relevant column names
' Set position
Me.StartUpPosition = 0
Me.Top = baseUF.Top + 0.5 * baseUF.Height - 0.5 * Me.Height
Me.Left = baseUF.Left + 0.5 * baseUF.Width - 0.5 * Me.Width
With FilterSelectionBox
' First grab all of the column names from the main selected table
For i = 0 To baseUF.SelectionBox.ListCount - 1
.AddItem baseUF.SelectionBox.List(i)
Next i
' Then grab all of the column names from the additional tables to be joined
If Not IsVariantEmpty(join_table_cols) Then
For n = 0 To UBound(join_table_cols)
If Not IsEmpty(join_table_cols(n)) Then
For Each col_name In join_table_cols(n)
.AddItem col_name
Next
End If
Next n
End If
End With
End Sub
Private Sub OkButton_Click()
' Initialize the variables
Dim tb As MSForms.TextBox
Dim arr() As String
Dim str As String
' tb is the textbox object that the column names will be pasted in to
Set tb = baseUF.MultiPage1.Pages(baseUF.MultiPage1.Value).Controls(Me.Tag)
' sets the str according to some logic
' This is actually where it gets sent
tb.Value = str
' And close the form
Unload Me
End Sub
And this is the code in the userform with an issue
Private Sub UserForm_Initialize()
' On initialization, populate the combobox with all of the save names present in the spreadsheet
' Set position
Me.StartUpPosition = 0
Me.Top = baseUF.Top + 0.5 * baseUF.Height - 0.5 * Me.Height
Me.Left = baseUF.Left + 0.5 * baseUF.Width - 0.5 * Me.Width
With LoadComb
' If there is more than one save present, go through the array and add each one
If IsArray(save_names) Then
For Each saved_name In save_names
.AddItem saved_name
Next
' Otherwise just add the one
Else
.AddItem save_names
End If
End With
End Sub
Private Sub LoadButton_Click()
' When the user hits the load button, first check if they actually selected anything
If LoadComb.Value = "" Then
' If they didn't, yell at them
MsgBox "No saved query selected!"
Else
' Otherwise, save the name to a global variable
load_name = LoadComb.Value
End If
' Close the form
Unload Me
End Sub
Whenever something unexpected happens with forms, consider writing End in the immediate window and pressing enter. It will kill all the unkilled instances of a form and generally any variable, thus it would be like a cold restart to the VBA program.
After doing this, it is a good idea to consider a cleaner solution, concerning VBA & UserForms, using some OOP. (Disclaimer - the first article is mine):
http://www.vitoshacademy.com/vba-the-perfect-userform-in-vba/
https://rubberduckvba.wordpress.com/2017/10/25/userform1-show/
https://codereview.stackexchange.com/questions/154401/handling-dialog-closure-in-a-vba-user-form
Although it may seem that you are achieving the same results with more code, the benefits of using this approach are quite a lot in the long term.
This is a small example of the OOP model. Imagine you have a user form like this:
It has only the following controls:
btnRun
btnExit
lblInfo
frmMain (the class)
The code withing the form is the following:
Option Explicit
Public Event OnRunReport()
Public Event OnExit()
Public Property Get InformationText() As String
InformationText = lblInfo.Caption
End Property
Public Property Let InformationText(ByVal value As String)
lblInfo.Caption = value
End Property
Public Property Get InformationCaption() As String
InformationCaption = Caption
End Property
Public Property Let InformationCaption(ByVal value As String)
Caption = value
End Property
Private Sub btnRun_Click()
RaiseEvent OnRunReport
End Sub
Private Sub btnExit_Click()
RaiseEvent OnExit
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
Hide
End If
End Sub
The form is with two events, getting caught by the clsSummaryPresenter. The clsSummaryPresenter looks like this:
Option Explicit
Private WithEvents objSummaryForm As frmMain
Private Sub Class_Initialize()
Set objSummaryForm = New frmMain
End Sub
Private Sub Class_Terminate()
Set objSummaryForm = Nothing
End Sub
Public Sub Show()
If Not objSummaryForm.Visible Then
objSummaryForm.Show vbModeless
Call ChangeLabelAndCaption("Press Run to Start", "Starting")
End If
With objSummaryForm
.Top = CLng((Application.Height / 2 + Application.Top) - .Height / 2)
.Left = CLng((Application.Width / 2 + Application.Left) - .Width / 2)
End With
End Sub
Private Sub Hide()
If objSummaryForm.Visible Then objSummaryForm.Hide
End Sub
Public Sub ChangeLabelAndCaption(strLabelInfo As String, strCaption As String)
objSummaryForm.InformationText = strLabelInfo
objSummaryForm.InformationCaption = strCaption
objSummaryForm.Repaint
End Sub
Private Sub objSummaryForm_OnRunReport()
MainGenerateReport
Refresh
End Sub
Private Sub objSummaryForm_OnExit()
Hide
End Sub
Public Sub Refresh()
With objSummaryForm
.lblInfo = "Ready"
.Caption = "Task performed"
End With
End Sub
Finally, we have the modMain, which is the so-called business logic of the form:
Option Explicit
Private objPresenter As clsSummaryPresenter
Public Sub MainGenerateReport()
objPresenter.ChangeLabelAndCaption "Starting and running...", "Running..."
GenerateNumbers
End Sub
Public Sub GenerateNumbers()
Dim lngLong As Long
Dim lngLong2 As Long
tblMain.Cells.Clear
For lngLong = 1 To 10
For lngLong2 = 1 To 10
tblMain.Cells(lngLong, lngLong2) = lngLong * lngLong2
Next lngLong2
Next lngLong
End Sub
Public Sub ShowMainForm()
If (objPresenter Is Nothing) Then
Set objPresenter = New clsSummaryPresenter
End If
objPresenter.Show
End Sub

Runtime Error 1004 - BeforePrint - Avoid printing if cells are empty

I am trying to create a macro to prevent users from printing a form if they don't fill out all cells. However, I am getting an error message:
Private Sub Workbook_BeforePrint(Cancel As Boolean)
If Application.Sheets("Form").Range("B4,C4,D4,E4,F4,G4,H4,I4,J4,K4,L4,M4,B5,C5,D5,E5,F5,G5,H5,I5,J5,K5,L5,M5,B6,C6,D6,E6,F6,G6,H6,I6,J6,K6,L6,M6,B7,C7,D7,E7,F7,G7,H7,I7,J7,K7,L7,M7,B8,C8,D8,E8,F8,G8,H8,I8,B9,C9,D9,E9,F9,G9,H9,I9,B11,C11,D11,E11,F11,G11,H11,I11,B12,C12,D12,E12,F12,G12,H12,I12,B13,C13,D13,E13,F13,G13,H13,I13,B14,C14,D14,E14,F14,G14,H14,I14,B16,C16,D16,E16,F16,G16,H16,I16,B17,C17,D17,E17,F17,G17,H17,I17").Value = "" Then
Cancel = True
MsgBox "Fill out all the cells"
End If
End Sub
I have another macro for another form that has fewer cells and that one works just fine:
Private Sub Workbook_BeforePrint(Cancel As Boolean)
If Application.Sheets("Form 2").Range("C4,C5,C6,D4,D5,D6,F4,F5,F6,B8,B9,B10,B11,C8,C9,C10,C11,D8,D9,D10,D11,E8,E9,E10,E11,C13,D13,C16,C17,C18,F16,F17,F18,C22,D22").Value = "" Then
Cancel = True
MsgBox "Fill out all the cells"
End If
End Sub
What can I do to solve this problem?
It seems that there is a limit to line length or to how long of a string you can put into a range(). But you can consolidate it like this. Also I think that the return value from a range() with more than one cell is an array so I am not sure if it is valid to compare it to a string like you did.
Try this:
Private Sub Workbook_BeforePrint(Cancel As Boolean)
For Each cell In Application.Sheets("Form").Range("B4:M7,B8:I9,B11:I14,B16:I17")
If cell.Value = "" Then
Cancel = True
MsgBox "Fill out all the cells"
Exit Sub
End If
Next
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

How to call the DateTimePicker from a textbox in UserForm

I have created a TextBox1 within my UserForm.
I have create a Sub called
Sub TextBox1_Enter()
End Sub
where by clicking the textbox I want to open the DateTimePicker control to choose a date, and after the date is chosen the chosen date should be the TextBox1.Value
I have the DateTimePicker from mscomct2.ocx
I really don't figure out how to call the control; any tips with code and a good explanation anyone?
I'm not sure if this is exactly what you are looking for but you could take a look as this. It is very well explained.
My goal was to have an empty DatePicker at form startup, and was told to use a textbox to load the DTPicker upon clicking.
But I found this solution that keep the DTPicker Value empty as form startup
Private Sub DTPicker1_CloseUp()
FormatDTPicker
End Sub
Private Sub DTPicker1_Format(ByVal CallbackField As String, FormattedString As String)
If CallbackField = "X" Then FormattedString = ""
End Sub
Private Sub DTPicker1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal_
x As stdole.OLE_XPOS_PIXELS, ByValy As stdole.OLE_YPOS_PIXELS)
With DTPicker1
If .Value = vbNull Then
.Value = Now
End If
End With
End Sub
Private Sub UserForm_Initialize()
DTPicker1.Value = vbNull
FormatDTPicker
End Sub
Private Sub FormatDTPicker()
With DTPicker1
If .Value = vbNull Then
.Format = dtpCustom
.CustomFormat = "X"
Else
.Format = dtpShortDate
End If
End With
End Sub
http://www.mrexcel.com/forum/excel-questions/666685-dtpicker-value-null.html

Global Variable in Userform

I search about this in the forum and found some answers but did not work for me.
I have two UserForms.
In the first one, I give a value to a variable called Word.
In the second one, I have a Label that I need the caption to become the variable Word.
Example:
Public Word as String
Private Sub Userform1_Activate
Word = "Today Is Saturday"
End Sub
Private Sub Userform2_Activate
Label1.Caption = Word
End Sub
But this does not work. The Label caption gets Zero for value.
Could anybody help me on this?
Thanks.
First Form
Private Sub CommandButton5_Click()
Db = "C:\Users\Desktop\db.txt"
Set File1 = CreateObject("Scripting.FileSystemObject")
Set File2 = File1.OpenTextFile(Db, 1)
Do Until File2.AtEndOfStream
If (File2.Readline = TextBox1) Then Exit Do
If File2.AtEndOfStream Then WordNotFound.Show
If File2.AtEndOfStream Then TextBox1.Value = ""
If File2.AtEndOfStream Then Exit Sub
Loop
Word = File2.Readline
MsgBox Word
TextBox1.Value = ""
End Sub
Second Form
Private Sub UserForm_Click()
Label1.Caption = Word
End Sub
As I said in my comment, that your method should work. Here is the test code that I tried
1- In Module1
Public Word As String
2- Create 2 user forms - UserForm1 and UserForm2
2a- In UserForm1
Private Sub UserForm_Activate()
Word = "This is Saturday"
End Sub
2b- In UserForm2
Private Sub UserForm_Activate()
Label1.Caption = Word
End Sub
3- Then in ThisWorkbook
Private Sub Workbook_Open()
UserForm1.Show
UserForm2.Show
End Sub
So when you close UserForm1, the UserForm2 would be displayed as below