I'd like to create a Textbox, whose text changes color dynamically from black to red when a value larger than a number is typed, and vice versa. I managed to do this but when I erase all the content of the textbox, my debugging application crashes with the error System.InvalidCastException: 'Conversion from string "" to type 'Double' is not valid.' Here is the code I'm using:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
If CDbl(TextBox1.Text) > CDbl(Label2.Text) Then
TextBox1.ForeColor = Color.Red
End If
If CDbl(TextBox1.Text) = CDbl(Label2.Text) Then
TextBox1.ForeColor = Color.Black
End If
If CDbl(TextBox1.Text) < CDbl(Label2.Text) Then
TextBox1.ForeColor = Color.Black
End If
If TextBox1.Text = "" Then
TextBox1.ForeColor = Color.Black
End If
End Sub
Which feature do I have to add in order to prevent the crashing of my application? Thanks in advance. Best regards.
Your code has multiple issues that may require attention.
The VB casting functions like CInt, CDbl etc. will throw an exception if casting fails. An empty value does not represent a numerical value, so it will fail. A solution might be to check for empty string values first.
You also might want to use an If ... ElseIf ... ElseIf ... ElseIf ... EndIf construct to avoid execution of subsequent if-blocks once you executed a matching if-block.
But then the code would crash if you would type a non-numeric value, like "A"...
So I would propose to use the Double.TryParse method instead of CDbl here.
Also notice that you normally want to use black as the default foreground color, but only want to switch to red if the value of the textbox is larger than the value of the label. Why not just express that literally in code?
So here are my two cents, using a variable NewForeColor that is initially set to Color.Black. It is then only set to Color.Red if both the textbox and the label hold a numerical value and the textbox value is larger than the label value. Eventually the textbox's foreground color is set to the value of the NewForeColor variable:
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
Dim NewForeColor As Color = Color.Black
Dim TextBox1Value As Double
Dim Label2Value As Double
If Double.TryParse(TextBox1.Text, TextBox1Value) AndAlso Double.TryParse(Label2.Text, Label2Value) Then
If TextBox1Value > Label2Value Then
NewForeColor = Color.Red
End If
End If
TextBox1.ForeColor = NewForeColor
End Sub
Related
I have a problem, with DataGridView's CellFormatting. The cells are colored by the search result from a TextBox. When I search for 2 numbers together, they are no longer colored. What should I do?
I state that I am using CONCAT_WS to load the table in DataGridView. What can I do?
Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
Try
If e.ColumnIndex = 3 And e.Value IsNot Nothing Or e.ColumnIndex = 4 And e.Value IsNot Nothing Or e.ColumnIndex = 5 And e.Value IsNot Nothing Or e.ColumnIndex = 6 And e.Value IsNot Nothing Or e.ColumnIndex = 7 And e.Value IsNot Nothing Then
If String.IsNullOrEmpty(txtRefreshFiltra.Text) Then
txtRefreshFiltra.Text = ""
End If
Dim sum6 As String = Convert.ToInt32(e.Value)
If sum6 = txtRefreshFiltra.Text Then
e.CellStyle.BackColor = Color.Gold
e.CellStyle.ForeColor = Color.Black
End If
End If
Catch ex As Exception
MsgBox(ex.Message) 'show error msg'
End Try
End Sub
My connection
Public Sub FilterData(ValueToSearch As String)
Try
Dim SearchQyery As String = "SELECT * FROM LottoDeaBendata WHERE CONCAT_WS([Estratto1],[Estratto2],[Estratto3],[Estratto4],[Estratto5])LIKE'%" & ValueToSearch & "%'"
Dim command As New SqlCommand(SearchQyery, connection)
connection.Open()
Dim table As New DataTable()
Dim adapter As New SqlDataAdapter(command)
adapter.Fill(table)
DataGridView1.DataSource = table
connection.Close()
Catch ex As Exception
MsgBox(ex.Message) 'show error msg'
End Try
End Sub
Upload by button
Private Sub btnFiltraDati_Click(sender As Object, e As EventArgs) Handles btnFiltraDati.Click
FilterData(txtRefreshFiltra.Text)
End Sub
There are a few things you may want to consider to color the cells as you describe. First, using the grids CellFormatting event may not necessarily be the best choice. This event will fire once for each cell when the data is loaded into the grid and this is fine and colors the cells as we want when the data is loaded, however, it also may fire if the user simply moves the cursor over a cell or the user scrolls the grid.
In both the cases of the user moving the cursor over a cell or scrolling the grid, clearly demonstrates that the cells will get re-colored unnecessarily. In other words, if the text in the text box has not changed or a cells value has not changed, then, re-coloring the cell(s) is superfluous.
Given this, the only drawback to NOT using the grids CellFormatting event is that our code will have to color the cells AFTER the grid is loaded with data. This means we will need a method to loop through all the rows of the grid to check and color the cells. This method to color all the cells is also going to be needed if the text in the text box changes. So, making this method makes sense so we can call it when the data is loaded and also when the text box text changes.
So given all this, to simplify things, I suggest you create a method that takes a single DataGridViewCell. The method will get the comma separated values from the text box and compare the cells value to the values in the text box and if one matches, then we simply color the cell, otherwise do not color the cell.
This method is below. First, we check if the cell is not null and actually has some value. Then, we take the string in the text box and split it on commas. Then we start a loop through all the values in the split string from the text box and if a match is found, then we simply color the cell and exit the for each loop.
Private Sub ColorCell(cell As DataGridViewCell)
If (cell.Value IsNot Nothing) Then
Dim target = cell.Value.ToString()
If (Not String.IsNullOrEmpty(target)) Then
cell.Style.BackColor = Color.White
cell.Style.ForeColor = Color.Black
Dim split = txtRefreshFiltra.Text.Trim().Split(",")
For Each s As String In split
If (target = s.Trim()) Then
cell.Style.BackColor = Color.Gold
cell.Style.ForeColor = Color.Black
Exit For
End If
Next
End If
End If
End Sub
The method above should simplify looping through all the rows in the grid to color the proper cells. This method may look something like below and we would call this method once after the data is loaded into the grid and also when the text in the text box changes.
Private Sub ColorAllCells()
For Each row As DataGridViewRow In DataGridView1.Rows
ColorCell(row.Cells(3))
ColorCell(row.Cells(4))
ColorCell(row.Cells(5))
ColorCell(row.Cells(6))
ColorCell(row.Cells(7))
Next
End Sub
Lastly, the two event methods that we need to capture when the user changes a cells value in the grid in addition to when the user changes the text in the text box.
Private Sub txtRefreshFiltra_TextChanged(sender As Object, e As EventArgs) Handles txtRefreshFiltra.TextChanged
ColorAllCells()
End Sub
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
If (e.RowIndex >= 0) Then
If (e.ColumnIndex = 3 Or e.ColumnIndex = 4 Or e.ColumnIndex = 5 Or e.ColumnIndex = 6 Or e.ColumnIndex = 7) Then
ColorCell(DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex))
End If
End If
End Sub
Edit per OP comment…
There are definitely a couple of things we can do to speed up the current code above, like take out the call to ColorAllCells in the text boxes TextChanged event.
The TextChanged event will fire when the user types a single character. Example, if the user wants to color the cells that are “55”, then, when the user types the first “5” into the text box… then the TextChanged event will fire and the code will color all the cells with “5”. Then when the user types the second “5”, the cells will be un-colored/colored again.
So, one way we can prevent the unnecessary coloring as described above is to NOT call the ColorAllCells method in the text boxes TextChanged event and simply put the ColorAllCells method into a button click. In other words, the user types what they want into the text box… THEN clicks a button to color the cells.
In addition, if you look at the ColorCell method, you may note that each time the method is called, the code is splitting the same string over and over with … Dim split = txtRefreshFiltra.Text.Trim().Split(",") … this is potentially redundant in a sense that the text … txtRefreshFiltra.Text may not have changed.
Therefore, to remedy this and only split the txtRefreshFiltra.Text when needed, we will use a global variable called something like currentSplit that holds the current split of the text box. Then we would “update” the currentSplit variable only when needed… like in its TextChanged event.
This should somewhat speed things up. In my small tests, it took approximately 10 seconds to color the cells the FIRST time. Subsequent coloring of the cells when the text box value was changed took less than 1 second.
First make a global variable to hold the current text boxes split values…
Dim currentSplit As String()
Then change the other methods as shown below…
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim dt = GetDT()
DataGridView1.DataSource = dt
currentSplit = txtRefreshFiltra.Text.Trim().Split(",")
End Sub
Private Sub ColorCell(cell As DataGridViewCell)
If (cell.Value IsNot Nothing) Then
Dim target = cell.Value.ToString()
If (Not String.IsNullOrEmpty(target)) Then
cell.Style.BackColor = Color.White
cell.Style.ForeColor = Color.Black
For Each s As String In currentSplit
If (target = s.Trim()) Then
cell.Style.BackColor = Color.Gold
cell.Style.ForeColor = Color.Black
Exit For
End If
Next
End If
End If
End Sub
Private Sub txtRefreshFiltra_TextChanged(sender As Object, e As EventArgs) Handles txtRefreshFiltra.TextChanged
currentSplit = txtRefreshFiltra.Text.Trim().Split(",")
End Sub
And finally, a button click event to color the cells. I added a stop watch to time how long it takes to color the cells.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim sw As Stopwatch = New Stopwatch()
Debug.WriteLine("Starting coloring of cells -------")
sw.Start()
ColorAllCells()
sw.Stop()
Debug.WriteLine("It took: " + sw.Elapsed.TotalSeconds.ToString() + " to color the cells of 100,000 rows with 10 columns")
End Sub
This app I'm designing has a TextBox named txtValue with the properties MaxLength set to 14 and TextAlign set to Right. I want txtValue to only accept currency, and dynamically format the input so the user doesn't need to add commas, only one period.
I managed to make it so txtValue will only accept numbers and one dot in the event txtValue_KeyPress.
txtValue_LostFocus will convert the input into currency format.
Here's my code so far:
Private Sub txtValue_KeyPress(sender As Object, e As KeyPressEventArgs) Handles txtValue.KeyPress
'Allows only one dot
If (e.KeyChar.ToString = ".") And (txtValue.Text.Contains(e.KeyChar.ToString)) Then
e.Handled = True
Exit Sub
End If
'Allows only 0 to 9 and dot (once)
If (e.KeyChar.ToString < "0" OrElse e.KeyChar.ToString > "9") _
AndAlso e.KeyChar <> ControlChars.Back _
AndAlso e.KeyChar.ToString <> "." Then
e.Handled = True
End If
End Sub
Private Sub txtValue_LostFocus(sender As Object, e As EventArgs) Handles txtValue.LostFocus
txtValue.Text = Format(Val(txtValue.Text), "000,000,000.00")
End Sub
I expect the input -q1w23456789012....34 to return the output 123,456,789,012.34, but the actual output after it loses focus is 123,456,789,012.30
This seems like an easy fix, like setting MaxLength to 15, but then if I don't type a period, it'll allow me to type 15 numbers and I only want up to 12 plus 2 after the period.
I expect the input -q1w234....5678 to return the output 1,234.56, but the actual output after it loses focus is 000,000,001,234.56
This seems like a more complex fix, because I don't want to use the LostFocus event to validate what I type. I want the KeyPress event to handle the input and dynamically format what I type.
In this case:
The input 1 would have the output 1.00
The input 123.4 would have the output 123.40
The input 1234.567 would have the output 1,234.56
All of this without needing the LostFocus event, but right now I'm using the LostFocus event because that's all my very limited knowledge allows me to do.
UPDATE
Alright I'm now using the Leave event, but then again I was only using LostFocus as a placeholder because in the end I want the TextBox to adjust what the user types as they type.
An alternative way to handle. For details on formating numbers for display try MS docs https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings or https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings
Private err As New ErrorProvider()
Private d As Decimal 'In case you want to use the value as a number somewhere else
Private Sub TextBox17_Validating(sender As Object, e As CancelEventArgs) Handles TextBox17.Validating
If Not Decimal.TryParse(TextBox17.Text, d) Then
e.Cancel = True
err.SetError(TextBox17, "This text box must contain a number.")
Else
err.Clear()
End If
End Sub
Private Sub TextBox17_Validated(sender As Object, e As EventArgs) Handles TextBox17.Validated
TextBox17.Text = d.ToString("C")
End Sub
I am making a temperature converter in vb.net for my assignment. I know the conversion method and so on.. but the problem is, I need to only use two textboxes. One for Celsius and one for farenheit. Whenever I update the textbox for celsius, the changes on textbox should also happen, and when I change the value for farenheit, the celsius textbox should also change depending on the value for farenheit. What method should I do?
This is the current one im working on..
Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
If TextBox1.Focus() Then
TextBox2.Clear()
TextBox2.Text = (TextBox1.Text - 32) / 1.8
ElseIf TextBox2.Focus() Then
TextBox1.Clear()
TextBox1.Text = (TextBox2.Text * 1.8) + 32
End If
End Sub
End Class
Thanks for posting some code. This is the way I'd do it (after wiring up TextChanged event handlers for both text boxes):
Private DegreesCChanging As Boolean = False
Private DegreesFChanging As Boolean = False
Private Sub DegreesF_TextChanged(sender As Object, e As EventArgs) Handles DegreesF.TextChanged
If Not DegreesFChanging Then
Dim Temperature As Double
DegreesCChanging = True
If Double.TryParse(DegreesF.Text, Temperature ) Then
DegreesC.Text = ((Temperature - 32.0) / 9.0 * 5.0).ToString("0.##")
Else
DegreesC.Text = String.Empty
End If
DegreesCChanging = False
End If
End Sub
Private Sub DegreesC_TextChanged(sender As Object, e As EventArgs) Handles DegreesC.TextChanged
If Not DegreesCChanging Then
Dim Temperature As Double
DegreesFChanging = True
If Double.TryParse(DegreesC.Text, Temperature ) Then
DegreesF.Text = (Temperature / 5.0 * 9.0 + 32.0).ToString("0.##")
Else
DegreesF.Text = String.Empty
End If
DegreesFChanging = False
End If
End Sub
There are a few things to note.
I'm using the TextChanged event - as soon as the user types
something into either text box, the world starts changing
I use double.TryParse to convert the number to a string. If I can't figure out what's going on (i.e., the TryParse call returns False), I stick an empty string in the other text box. It works quite well.
When the user types something into a text box, it causes a TextChanged event that forces new text into the other text box - which will result in a TextChanged event for that control. I use two Boolean flags to prevent this.
I use a custom numeric format string on my ToString calls to limit
the precision to two decimal places.
My problem boils down to this:
I have six TextBoxes which expect a value that is between 0 and a given number.
What I am trying to achieve is:
If the number entered is between 0 and the specified number (as a label), the text will remain black
If the number entered exceeds the specified number, the text will turn red
The problem here is that if the specified number is "10", and the user enters 11, it turns red, as it should, HOWEVER, if they hit the backspace key (the number entered is now 1) the number remains red, which is not the intended functionality - the number 1 should be black since it's between 0 and the specified number.
All of the specified numbers are hard-coded for now (I'm in a beginner course and this is just something I'm doing for fun to increase the functionality of the program and I haven't gotten to adding classes for each "Assignment" yet) and you can technically input negative numbers, I don't care about that at the moment.
This is the Subroutine which gets added as the handler for all of the textboxes within a certain GroupBox
' Handler which gets added to all TextBoxes in "grpGrades" GroupBox
Private Sub txtGradePoints_TextChanged(sender As Object, e As EventArgs)
' Take in generic sender (Textbox) and convert to TextBox (necessary due to Strict mode)
Dim textBox = CType(sender, TextBox)
Try
' the value of the current TextBox being checked
Dim val = Decimal.Parse(textBox.Text)
Select Case textBox.Name
Case "txtPostPoints"
If val > 10 Then textBox.ForeColor = Color.Red
Case "txtCh1TestPoints", "txtCh2TestPoints", "txtCh3TestPoints"
If val > 50 Then textBox.ForeColor = Color.Red
Case "txtCh2TutPoints", "txtCh3TutPoints"
If val > 25 Then textBox.ForeColor = Color.Red
Case Else
textBox.ForeColor = Color.Black
End Select
Catch
textBox.ForeColor = SystemColors.ControlText
End Try
End Sub
This is the onLoad Handler which gets the appropriate TextBox controls from the "grpGrades" GroupBox and adds the aforementioned TextChanged handler to each one.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Get array of TextBox Controls from the "grpGrades" GroupBox
Dim textBoxes = grpGrades.Controls.OfType(Of TextBox)()
' Go through the array of TextBoxes and add the TextChanged handler to each TextChanged event
For Each txt In textBoxes
AddHandler txt.TextChanged, AddressOf txtGradePoints_TextChanged
Next
'AddHandler txtPostPoints.TextChanged, AddressOf txtGradePoints_TextChanged
'AddHandler txtCh1TestPoints.TextChanged, AddressOf txtGradePoints_TextChanged
'AddHandler txtCh2TestPoints.TextChanged, AddressOf txtGradePoints_TextChanged
'AddHandler txtCh3TestPoints.TextChanged, AddressOf txtGradePoints_TextChanged
'AddHandler txtCh2TutPoints.TextChanged, AddressOf txtGradePoints_TextChanged
'AddHandler txtCh3TutPoints.TextChanged, AddressOf txtGradePoints_TextChanged
End Sub
The last part of the Subroutine is just the commented out code and is how I originally had the Handlers added, just in case something went wrong with my new method.
EDIT: Was it seriously necessary to downvote? For what reason?
Your code never test for a valid value. The Case Else that sets the current textbox to Black is never hit when the current textbox returns to a valid value. This cannot happen because the Select Case will match the current Name of the textbox, will test again for the invalid value and then exits the Select Case block. You need to set the color for the valid value in the appropriate Case for the current text box name. The IF conditional operator could reduce everything to a single line
Private Sub txtGradePoints_TextChanged(sender As Object, e As EventArgs)
' Take in generic sender (Textbox) and convert to TextBox (necessary due to Strict mode)
Dim textBox = CType(sender, TextBox)
Try
' the value of the current TextBox being checked
Dim val = Decimal.Parse(textBox.Text)
Select Case textBox.Name
Case "txtPostPoints"
textBox.ForeColor = IF(val > 10, Color.Red, Color.Black)
Case "txtCh1TestPoints", "txtCh2TestPoints", "txtCh3TestPoints"
textBox.ForeColor = IF(val > 50, Color.Red, Color.Black)
Case "txtCh2TutPoints", "txtCh3TutPoints"
textBox.ForeColor = IF(val > 25, Color.Red, Color.Black)
Case Else
' Not sure if it is needed for other textboxes....
textBox.ForeColor = Color.Black
End Select
Catch
textBox.ForeColor = SystemColors.ControlText
End Try
End Sub
So, as others have said, you're not handling both sides of the If for each text box. You're setting the colour if it meets the condition, but not reversing it.
Here's my way of handling this. It's a bit different, but it keeps all of your code together and doesn't require any of that Case code to work out the name of the calling text box.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim check As Func(Of TextBox, Decimal, Boolean) =
Function(tb, d)
Dim value As Decimal
If (Decimal.TryParse(tb.Text, value)) Then
Return value > d
End If
Return False
End Function
Dim add As Action(Of TextBox, Decimal) =
Sub(tb, d)
AddHandler tb.TextChanged,
Sub(s, e2)
tb.ForeColor = If(check(tb, d), Color.Red, Color.Black)
End Sub
End Sub
add(txtPostPoints, 10)
add(txtCh1TestPoints, 50)
add(txtCh2TestPoints, 50)
add(txtCh3TestPoints, 50)
add(txtCh2TutPoints, 25)
add(txtCh3TutPoints, 25)
End Sub
So, check is a Func(Of TextBox, Decimal, Boolean) that takes a TextBox and safely parses its text to see if it is greater than the Decimal value and returns True if it is and False otherwise.
And add is an Action(Of TextBox, Decimal) that takes a TextBox and the Decimal and adds the handler to call check set the colour to red or black based on the result of check.
It then is very simple to call add for each TextBox.
All hard-coded with no magic string checks and all nicely encapsulated in the form load method.
You are using the select case based on the name and the only condition is that if it's > 10 then turn the forecolor red. But you're never turning it back. Now before you bring up the case else, let me inform you that the case else would only be based upon the textbox.name and since the name is found case else doesn't come in to play here.
so when a name is found you check the value > 10 and set red but you don't tell it what to do if it isn't > 10
I have a TextBox, which has a CheckBox operation to mask the containing text. This works with the following code:
Private Sub CheckBox2_Checked(ByVal sender As Object, ByVal e As EventArgs) Handles CheckBox2.CheckedChanged
TextBox14.PasswordChar = "*"
End Sub
It works well, but I want to also be able to uncheck theCheckBox and then have the recognizable text return. How can I achieve this?
The docos actually state:
The character used to mask characters entered in a single-line TextBox
control. Set the value of this property to 0 (character value) if you
do not want the control to mask characters as they are typed. Equals 0
(character value) by default.
Found here: http://msdn.microsoft.com/en-us/library/system.windows.forms.textbox.passwordchar(v=vs.110).aspx
In VB.NET, that would be easiest done by setting PasswordChar to vbNullChar.
You can do so by simply setting the PasswordChar property back to a null character, like this:
Private Sub CheckBox2_CheckedChanged(ByVal sender As Object, ByVal e As EventArgs) Handles CheckBox2.CheckedChanged
If CheckBox2.Checked Then
TextBox14.PasswordChar = "*"c
Else
TextBox14.PasswordChar = ControlChars.NullChar
End If
End Sub
The CheckedChanged event occurs every time the Checked property changes. So, when the user unchecks the CheckBox, it will raise that event too, so you need to check to see whether or not the control is currently checked.
I found just toggling the password character wasn't enough. In my case I was masking a connection string. With the lack of spaces in my text I had an issue going back and forth. My text would be cut off and wasn't wrapping properly.
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs)
Dim beforeText As String = TextBox1.Text
TextBox1.Text = ""
TextBox1.PasswordChar = IIf(CheckBox1.Checked, Convert.ToChar(0), "*"c)
TextBox1.Text = beforeText
End Sub
I imagine if you used a font like Console this would not be a problem as all character widths are constant.