I'm trying to implement a TextBox validation for user input of dates. Long story short, I'm building a new application and the users are accustomed to entering dates this way, so I want to try and validate the input while not making them "learn" something new. I have the following event handler code I hook up to date fields to test the input:
Dim DateText As String = String.Empty
Dim ValidDates As New List(Of Date)
Dim DateFormats() As String = {"Mdyy", "Mddyy", "MMdyy", "MMddyy", "Mdyyyy", "Mddyyyy", "MMdyyyy", "MMddyyyy"}
If TypeOf sender Is System.Windows.Forms.TextBox Then
Dim CurrentField As System.Windows.Forms.TextBox = CType(sender, System.Windows.Forms.TextBox)
If CurrentField.Text IsNot Nothing AndAlso Not String.IsNullOrEmpty(CurrentField.Text.Trim) Then
DateText = CurrentField.Text.Trim.ReplaceCharacters(CharacterType.Punctuation)
End If
For Each ValidFormat As String In DateFormats
Dim DateBuff As Date
If Date.TryParseExact(DateText, ValidFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, DateBuff) Then
If Not ValidDates.Contains(DateBuff) Then
ValidDates.Add(DateBuff)
End If
End If
Next ValidFormat
If ValidDates.Count > 1 Then
CurrentField.SelectAll()
CurrentField.HideSelection = False
MessageBox.Show("The date you entered is ambiguous." & vbCrLf & vbCrLf &
"Please enter two digits for the month, two digits for the day and" & vbCrLf &
"two digits for the year." & vbCrLf & vbCrLf &
"For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
" or " & Now.ToString("MM/dd/yy") & ".",
"AMBIGUOUS DATE ENTERED", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
CurrentField.HideSelection = True
e.Cancel = True
ElseIf ValidDates.Count < 1 Then
CurrentField.SelectAll()
CurrentField.HideSelection = False
MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
"Please enter two digits for the month, two digits for the day and" & vbCrLf &
"two digits for the year." & vbCrLf & vbCrLf &
"For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
" or " & Now.ToString("MM/dd/yy") & ".",
"INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
CurrentField.HideSelection = True
e.Cancel = True
Else
CurrentField.ForeColor = SystemColors.WindowText
CurrentField.BackColor = SystemColors.Window
End If
End If
This validation method only seems to work correctly if the format includes a two-digit month and two-digit day. If I try to use any of the single-digit formats (e.g., Mddyy, MMdyyyy, etc.), TryParseExact always returns False, and the date is never added to the List(Of Date).
Here are some "hard-coded" tests I went through trying to get to the source of the problem. I've used some intentionally ambiguous dates, as well as some definitively unambiguous ones:
If Date.TryParseExact("1223", "Mdyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 1223 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (1223)")
End If
'failed (1223)
If Date.TryParseExact("12123", "Mddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 12123 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (12123)")
End If
'failed (12123)
If Date.TryParseExact("012123", "MMddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 012123 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (012123)")
End If
'success: 012123 -> 1/21/2023
If Date.TryParseExact("1122023", "MMdyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 1122023 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (1122023)")
End If
'failed (1122023)
If Date.TryParseExact("72521", "Mddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 72521 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (72521)")
End If
'failed (72521)
If Date.TryParseExact("072521", "MMddyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 072521 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (072521)")
End If
'success: 072521 -> 7/25/2021
If Date.TryParseExact("3312019", "Mddyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 3312019 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (3312019)")
End If
'failed (3312019)
If Date.TryParseExact("05201975", "MMddyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 05201975 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (05201975)")
End If
'success: 05201975 -> 5/20/1975
If Date.TryParseExact("432013", "Mdyyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, TempDate) Then
Console.WriteLine($"success: 432013 -> {TempDate.ToString("M/d/yyyy")}")
Else
Console.WriteLine("failed (432013)")
End If
'failed (432013)
I've seen several posts complaining of "unusual behavior" with the TryParseExact() method, but I've not been able to find anything that explains why this is actually happening. I know that I've used some of these parsing methods in the past, but I don't recall ever having this much trouble getting a simple parse to work.
I thought the whole point of using the TryParseExact() method was so that I could tell the parser specifically where the data elements were in the string and get a valid value back. Am I missing or overlooking something here?
MY "SOLUTION":
Based on the explanation from the accepted answer as well as the additional details in the accepted answer from How to convert datetime string in format MMMdyyyyhhmmtt to datetime object?, I believe I've come up with a sort of "work-around" solution that enables me to achieve my goal of allowing my users to continue doing things the way they are used to while still providing the validation I'm looking for.
Added a new List(Of String) variable where I store the possible formats for a given input string (I already limit input to numeric, -, or / only)
Added a Select Case to inject separators (/) into the string at specific positions based on the string's length
Changed the DateFormats() array to use format strings to use in TryParseExact() that include separators (/)
With this, I can test each of those values for valid dates and make my determination from there.
Here's the updated method:
Public Sub ValidateDateField(ByVal sender As Object, ByVal e As CancelEventArgs)
Dim DateText As String = String.Empty
Dim ValidDates As New List(Of Date)
Dim DateFormats() As String = {"M/d/yy", "M/dd/yy", "MM/d/yy", "MM/dd/yy", "M/d/yyyy", "M/dd/yyyy", "MM/d/yyyy", "MM/dd/yyyy"}
Dim FormattedDates As New List(Of String)
If TypeOf sender Is System.Windows.Forms.TextBox Then
Dim CurrentField As System.Windows.Forms.TextBox = CType(sender, System.Windows.Forms.TextBox)
If CurrentField.Text IsNot Nothing AndAlso Not String.IsNullOrEmpty(CurrentField.Text.Trim) Then
'ReplaceCharacters() is a custom extension method
DateText = CurrentField.Text.Trim.ReplaceCharacters(CharacterType.Punctuation)
Select Case DateText.Length
Case < 4
CurrentField.SelectAll()
CurrentField.HideSelection = False
MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
"Please enter two digits for the month, two digits for the day and" & vbCrLf &
"two digits for the year." & vbCrLf & vbCrLf &
"For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
" or " & Now.ToString("MM/dd/yy") & ".",
"INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
CurrentField.HideSelection = True
e.Cancel = True
Exit Sub
Case 4
FormattedDates.Add(DateText.Insert(1, "/"c).Insert(3, "/"c))
Case 5
FormattedDates.Add(DateText.Insert(1, "/"c).Insert(4, "/"c))
FormattedDates.Add(DateText.Insert(2, "/"c).Insert(4, "/"c))
Case 6
FormattedDates.Add(DateText.Insert(1, "/"c).Insert(3, "/"c))
FormattedDates.Add(DateText.Insert(2, "/"c).Insert(5, "/"c))
Case 7
FormattedDates.Add(DateText.Insert(1, "/"c).Insert(4, "/"c))
FormattedDates.Add(DateText.Insert(2, "/"c).Insert(4, "/"c))
Case 8
FormattedDates.Add(DateText.Insert(2, "/"c).Insert(5, "/"c))
Case Else
CurrentField.SelectAll()
CurrentField.HideSelection = False
MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
"Please enter two digits for the month, two digits for the day and" & vbCrLf &
"two digits for the year." & vbCrLf & vbCrLf &
"For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
" or " & Now.ToString("MM/dd/yy") & ".",
"INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
CurrentField.HideSelection = True
e.Cancel = True
Exit Sub
End Select
For Each TempDate As String In FormattedDates
For Each ValidFormat As String In DateFormats
Dim DateBuff As Date
If DateTime.TryParseExact(TempDate, ValidFormat, System.Globalization.CultureInfo.CurrentCulture, DateTimeStyles.None, DateBuff) Then
If Not ValidDates.Contains(DateBuff) Then
ValidDates.Add(DateBuff)
End If
End If
Next ValidFormat
Next TempDate
If DateText.Trim.Length > 0 Then
If ValidDates.Count > 1 Then
CurrentField.SelectAll()
CurrentField.HideSelection = False
MessageBox.Show("The date you entered is ambiguous." & vbCrLf & vbCrLf &
"Please enter two digits for the month, two digits for the day and" & vbCrLf &
"two digits for the year." & vbCrLf & vbCrLf &
"For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
" or " & Now.ToString("MM/dd/yy") & ".",
"AMBIGUOUS DATE ENTERED", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
CurrentField.HideSelection = True
e.Cancel = True
ElseIf ValidDates.Count < 1 Then
CurrentField.SelectAll()
CurrentField.HideSelection = False
MessageBox.Show("The date you entered was not valid." & vbCrLf & vbCrLf &
"Please enter two digits for the month, two digits for the day and" & vbCrLf &
"two digits for the year." & vbCrLf & vbCrLf &
"For example, today's date should be entered as either " & Now.ToString("MMddyy") & vbCrLf &
" or " & Now.ToString("MM/dd/yy") & ".",
"INVALID INPUT FORMAT", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
CurrentField.HideSelection = True
e.Cancel = True
Else
CurrentField.ForeColor = SystemColors.WindowText
CurrentField.BackColor = SystemColors.Window
End If
End If
End If
End If
End Sub
The documentation is clear about this,
If you do not use date or time separators in a custom format pattern,
use the invariant culture for the provider parameter and the widest
form of each custom format specifier. For example, if you want to
specify hours in the pattern, specify the wider form, "HH", instead of
the narrower form, "H".
I think I am over engineering this a bit. Any assistance is greatly appreciated on how I can fix this little problem. I have an app that searches multiple sites. But if a user types a custom parameter of g=, then it will search google. The problem is I have right now is it strips the G from the first word of my search term. For example, If I type g=golf game, google pops up with olf game. With the other searches, the = character is getting stripped. Should I use contains instead of this custom firstChars function?
Here is my code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If TextBox1.Text = "" Then
MsgBox("Enter text to search on." & vbCrLf & Err.Description, MsgBoxStyle.Information, "Need search term to search on.")
TextBox1.Focus()
End If
Try
'CHECK FOR GOOGLE.COM SEARCHING
Dim MyString As String = TextBox1.Text
Dim MyChar() As Char = {"g", "G", "="}
Dim NewString As String = MyString.TrimStart(MyChar)
Dim myUri As New Uri("http://google.com/search?hl=en&q=" & NewString)
Dim first2Chars As String
Dim first3Chars As String
Dim first4Chars As String
first2Chars = TextBox1.Text.Substring(0, 2)
first3Chars = TextBox1.Text.Substring(0, 3)
first4Chars = TextBox1.Text.Substring(0, 4)
MsgBox(first2Chars)
If first2Chars = "G=" Or first2Chars = "g=" Then
System.Diagnostics.Process.Start(myUri.AbsoluteUri)
ElseIf first3Chars = "TS=" Or first3Chars = "ts=" Then
System.Diagnostics.Process.Start("https://localhost/search.do?query=" & Net.WebUtility.UrlEncode(TextBox1.Text))
ElseIf first4Chars = "PIC=" Or first4Chars = "pic=" Then
System.Diagnostics.Process.Start("https://localhost/search.do?query_pic=" & Net.WebUtility.UrlEncode(TextBox1.Text))
End If
Catch ex As Exception
MsgBox("Error running search. The error was: " & vbCrLf & Err.Description, MsgBoxStyle.Exclamation, "Error")
End Try
End Sub
Well the code itself works. The problem occurs when there is a sub items without text, the program will crash. I'm looking for a method that will bypass this annoying error.
My Code:
If ComboBox1.Text = "Everything" Then
Dim SetSave As SaveFileDialog = New SaveFileDialog
SetSave.Title = ".txt"
SetSave.Filter = ".txt File (*.txt)|*.txt"
If SetSave.ShowDialog = Windows.Forms.DialogResult.OK Then
Dim s As New IO.StreamWriter(SetSave.FileName, False)
For Each myItem As ListViewItem In Form1.ListView1.Items
s.WriteLine(myItem.Text & TextBox1.Text & myItem.SubItems(1).Text & TextBox1.Text & myItem.SubItems(2).Text & TextBox1.Text & myItem.SubItems(3).Text & TextBox1.Text & myItem.SubItems(4).Text & TextBox1.Text & myItem.SubItems(5).Text & TextBox1.Text & myItem.SubItems(6).Text & TextBox1.Text & myItem.SubItems(7).Text) '// write Item and SubItem.
Next
s.Close()
End If
Error:(this indicates the the listview item without text it can range from number 1 up to 7, the one below is 5)
InvalidArgument=Value of '5' is not valid for 'index'.
Parameter name: index
Your indexing is starting at 1. VB indexing starts at 0 so for 5 items you whould have index values of 0 to 4
I have this code:
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim num As String
Dim message As String
Dim name As String
message = txtMessage.Text
Dim count As Integer = Me.TblContactsBindingSource.Count
If i < TblContactsDataGridView.Rows.Count - 1 Then 'stay within bounds
i = i + 1 ' for all rows except Row0
TblContactsDataGridView.Rows(i - 1).DefaultCellStyle.BackColor = Color.White ' restore previous highlight
TblContactsDataGridView.Rows(i).DefaultCellStyle.BackColor = Color.Bisque 'new highlight
num = Me.TblContactsDataGridView.Rows(i).Cells(1).Value.ToString()
name = Me.TblContactsDataGridView.Rows(i).Cells(0).Value.ToString()
If SerialPort1.IsOpen() Then
SerialPort1.Write("AT" & vbCrLf)
SerialPort1.Write("AT+CMGF=1" & vbCrLf)
SerialPort1.Write("AT+CMGS=" & Chr(34) & num & Chr(34) & vbCrLf)
SerialPort1.Write(message & Chr(26))
MessageBox.Show("Message has been successfully sent to " & vbNewLine & name & " (" & num & ") ", "Message Sent", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
Else 'next row is off the bottom so
'i = 0 'reset index
'TblSmsDataGridView.Rows(TblSmsDataGridView.Rows.Count - 1).DefaultCellStyle.BackColor = Color.White 'restore bottom row
'TblSmsDataGridView.Rows(i).DefaultCellStyle.BackColor = Color.Bisque 'highlight top row
End If
In a command button I have this:
Timer1.Interval = 2000
Timer1.Enabled = True 'no need to enable it and start it; one or t'other
What happen is, the message box appears over and over. How can i trigger message box to automatically close once it is finished? I commented the code in the "else" because the it repeats over and over.
You have to use a custom message box. Normal message box wont do the thing you wanted. It will pop up every 2 second. best choice is to make a new form and show it as a message box. :)
You need to set timer1.enabled = false in the timer1.tick handler.
I've got 3 or 4 patterns that I'm comparing user input against and I need to figure out if the user input matches one of the patters and to return the match if it does.
Since the input is multiline, I'm passing each individual line like so:
Dim strRawInput() As String = Split(txtInput.Text, vbCrLf)
Dim strInput As String
txtOutput.Text = ""
For Each strInput In strRawInput
strInput.Trim(vbCr, vbLf, Chr(32))
Validation(strInput)
Next
Then I have this to find matches:
Dim m As Match
For i = 0 To strValidator.Length - 1
Dim r As New Regex(strValidator(i))
m = r.Match(strInput)
If m.Success Then
txtOutput.Text = txtOutput.Text & "Success: " & m.ToString & vbCrLf
Exit Sub
Else
End If
Next
txtOutput.Text = txtOutput.Text & "Not this time" & vbCrLf
How can I do this more efficiently? Also, I added the Exit Sub there to avoid showing the "Not this time" message even after a match is found (if the match is with one of the later patterns in the array), but I'd like to find a better way of doing that too.
Thanks in advance.
Rather than generating your Regexs in the loop, generate them one time at the startup of the application. So maybe something like:
Private Shared m_regexes As New List(Of Regex)
Shared Sub New()
For Each v As String In strValidator
m_regexes.Add(New Regex(v))
Next
End Sub
And then you can change your other code to:
For Each r As Regex In m_regexes
Dim m As Match = r.Match(strInput)
If m.Success Then
txtOutput.Text = txtOutput.Text & "Success: " & m.ToString & vbCrLf
Exit Sub
Else
End If
Next
Regarding the Exit Sub, I think it's fine, You've discovered that it matches at least one pattern, so why continue to evaluate the rest. But if you don't like methods that can "return" in multiple places, you could just replace it with a Boolean and a Exit For as:
Dim found as Boolean = false
For Each ...
If IsMatch Then
found = True
Exit For
End If
Next
If Found Then
....
Else
.....
End If
Hmm if that's the case then wouldn't this be even better?
For i = 0 To strValidator.Length - 1
Dim r As New Regex(strValidator(i))
Dim m As Match = r.Match(strInput)
If m.Success Then
txtOutput.Text = txtOutput.Text & "Success: " & m.ToString & vbCrLf
Exit Sub
End If
Next