I've put together a form in which a user can dynamically generate a customer order with one or more order positions. For each position, there are several attributes like amount, product name, price, discount etc.
My main problem is: What is the best way to deal with invalid values for the input fields? For example if a user types "X" into the amount field instead of 1, 2 or whatever.
The basic idea was to let the user enter everything they want - but the order can only be saved once every input field contains valid data. If not, all invalid fields will be highlighted so the user knows what he did wrong.
So far, this seems to work just fine but my idea was to also have a Customer_Order object which would be updated everytime the user changes the value of an input field.
Obviously I could not do that if I want to allow the user to enter Strings like "X" into Integer or Decimal fields... so it seems to me that I have 2 options:
A: Either restrict the input fields and programatically turn invalid values into zeros (For example: User enters "abc" into price field -> String will be converted to 0,00)
OR B: keep my original plan with not so strict input regulations and NOT have a Customer_Order object that is always kept up to date. I would instead create the object from scratch and fill it with all the data from the input fields when the user finishes the order.
My problem with A is that I would like to keep the input fields as non-strict as possible. If a user types in something invalid, they should SEE what they typed in instead of the program changing the value. And my problem with B is that having an always up-to-date object of the customer order makes it easier to calulate prices on the fly. If I don't have that object, I would have to read out and parse all the necessary input fields every time I want to calculate something.
I'm not that experienced with GUIs so I really don't know if I'm missing something here... what would be the most elegant way to handle this? Is it generally a bad idea to have an always up-to-date object in the background at all times?
One option is to only allow valid keys. This can be done by utilizing the KeyDown event handler.
Create a new Windows Forms App (.NET Framework) project
Add a TextBox to the form (name: textBoxAmount)
Open Solution Explorer
In VS menu, click View
Select Solution Explorer
Open Properties Window
In VS menu, click View
Select Properties Window
Add TextBox KeyDown event handler
In Properties Window, select textBoxAmount from the drop-down
Click
Double-click KeyDown
Add module (name: HelperInput.vb)
Click Project
Select Add Module... (name: HelperInput.vb)
Click OK
HelperInput.vb:
Imports System.Globalization
Module HelperInput
Public Sub TBKeyDownMonetaryValue(sender As Object, e As System.Windows.Forms.KeyEventArgs)
Dim tb As Control = DirectCast(sender, Control) 'TextBox
Dim isKeyAllowed As Boolean = False
Dim nfInfo As NumberFormatInfo = CultureInfo.CurrentUICulture.NumberFormat
Debug.WriteLine($"currency symbol: {nfInfo.CurrencySymbol} decimal separator: {nfInfo.CurrencyDecimalSeparator} number group separator: {nfInfo.NumberGroupSeparator} currency group separator: {nfInfo.CurrencyGroupSeparator}")
If Not Control.ModifierKeys = Keys.Shift Then
Select Case e.KeyCode
Case Keys.Enter
isKeyAllowed = True
Case Keys.Back
isKeyAllowed = True
Case Keys.Delete
isKeyAllowed = True
Case Keys.NumPad0
isKeyAllowed = True
Case Keys.NumPad1
isKeyAllowed = True
Case Keys.NumPad2
isKeyAllowed = True
Case Keys.NumPad3
isKeyAllowed = True
Case Keys.NumPad4
isKeyAllowed = True
Case Keys.NumPad5
isKeyAllowed = True
Case Keys.NumPad6
isKeyAllowed = True
Case Keys.NumPad7
isKeyAllowed = True
Case Keys.NumPad8
isKeyAllowed = True
Case Keys.NumPad9
isKeyAllowed = True
Case Keys.D0
isKeyAllowed = True
Case Keys.D1
isKeyAllowed = True
Case Keys.D2
isKeyAllowed = True
Case Keys.D3
isKeyAllowed = True
Case Keys.D4
isKeyAllowed = True
Case Keys.D5
isKeyAllowed = True
Case Keys.D6
isKeyAllowed = True
Case Keys.D7
isKeyAllowed = True
Case Keys.D8
isKeyAllowed = True
Case Keys.D9
isKeyAllowed = True
Case Else
isKeyAllowed = False
End Select
End If
'only allow one currency decimal separator
If e.KeyCode = Keys.Oemcomma AndAlso nfInfo.CurrencyDecimalSeparator = "," AndAlso (String.IsNullOrEmpty(tb.Text) OrElse Not tb.Text.Contains(nfInfo.CurrencyDecimalSeparator)) Then
isKeyAllowed = True
ElseIf e.KeyCode = Keys.OemPeriod AndAlso nfInfo.CurrencyDecimalSeparator = "." AndAlso (String.IsNullOrEmpty(tb.Text) OrElse Not tb.Text.Contains(nfInfo.CurrencyDecimalSeparator)) Then
isKeyAllowed = True
End If
If Not isKeyAllowed Then
e.Handled = True
e.SuppressKeyPress = True
End If
End Sub
End Module
Form1.vb:
Public Class Form1
Private Sub TextBoxAmount_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBoxAmount.KeyDown
HelperInput.TBKeyDownMonetaryValue(sender, e)
End Sub
End Class
Resources:
NumberFormatInfo Class
Related
I'm trying to get return false when a/b where b is equal to 0 so that my error message appears to the user. So far I've managed to do a check within my sub but I need the check to be within the function. Currently, I get a infinity as the result, when for example 7 is divided by 0. I've basically made a pretty basic calculator and would appreciate any help.
Function:
Private Function validationCheck() As Boolean
' The following checks if the two fields are numerical values, are not blank and if the 2nd number is not zero
'The latter is for the purpose of division
'If the fields meet the above conditions then true is returned
'Else if either one is not met, then false is returned
If IsNumeric(txt1stNumber.Text) And txt1stNumber.Text <> "" And IsNumeric(txt2ndNumber.Text) And txt2ndNumber.Text <> "" Then
Return True
Else
Return False
End If
End Function
Division Sub
Private Sub btnDivide_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDivide.Click
'The following sub is the code for the division button.
'First it checks whether false is returned from the ValidationCheck funtion
'If so, an error message is shown
'Else if true is returned, then the values are divided together to form the result
If validationCheck() = False Then
MsgBox("Please enter a numerical value for both fields. Also, field cannot be left blank.")
ElseIf validationCheck() = True Then
lblResult.Text = Val(txt1stNumber.Text) / Val(txt2ndNumber.Text)
End If
You can add one more condition into you function to evaluate txt2ndNumber.Text like this, then if the number b is 0 or 00 like this this will also return false.
Private Function validationCheck() As Boolean
If IsNumeric(txt1stNumber.Text) And txt1stNumber.Text <> "" And IsNumeric(txt2ndNumber.Text) And txt2ndNumber.Text <> "" Then
Dim number As Double = txt2ndNumber.Text
If number = 0 Then
Return False
Else
Return True
End If
Else
Return False
End If
End Function
I have a few datagridview's, listing different bits and bobs from a mysql database.
One of them has a column called 'outcome'.
This can be either 'Yes', 'Pending' or 'No'.
I need to format this list based on that value.
I'm using the following at the moment...
Private Sub nb_myleads_dgv_CellFormattin(ByVal sender As Object, ByVal e As DataGridViewCellFormattingEventArgs) Handles nb_myleads_dgv.CellFormatting
If e.ColumnIndex = nb_myleads_dgv.Columns("outcome").Index Then
If e.Value.ToString = "Yes" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkGreen
ElseIf e.Value.ToString = "Pending" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkOrange
ElseIf e.Value.ToString = "No" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkRed
End If
End sub
End If
It seem that I have to have my column 'outcome' visible in the DGV for this to work.
I have gotten around this by setting this column width to 3 pixels, but that seems a little dirty. Is it not possible to format cells in a datagridview based on the value of a hidden column?
Thanks in advance
Why not just loop through the rows with a for each and colorize based on the cell value. It won't matter if it's visible or not.
For Each row As DataGridViewRow In DataGridView1.Rows
If Not row.IsNewRow Then
Select Case row.Cells(2).Value.ToString
Case "Yes"
row.DefaultCellStyle.BackColor = Color.DarkGreen
Case "Pending"
row.DefaultCellStyle.BackColor = Color.DarkOrange
Case "No"
row.DefaultCellStyle.BackColor = Color.DarkRed
End Select
End If
Next
Where in this case, column 3 (cells(2) is hidden. You would do this after populating the grid instead of in the cellformatting
I believe your problem is that cell has to be visible, otherwise it will never pass if statement.
The CellFormatting event occurs every time each cell is painted, so
you should avoid lengthy processing when handling this event. This
event also occurs when the cell FormattedValue is retrieved or its
GetFormattedValue method is called.
Link
I would get rid off this if statement and do something like this:
Dim str as string = dataGridView1.Rows[e.RowIndex].Cells["outcome"].Value.ToString
If str = "Yes" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkGreen
ElseIf str = "Pending" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkOrange
ElseIf str = "No" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkRed
End If
the code is perfect, but how can i lock the check items (ONLY REQUIRED items) in listview?
lvFees.Columns.Add("Fee", 120)
lvFees.Columns.Add("Amount", 76)
connect()
rec.Open("select * from tablePayments where sem='" & cboSem.Text & "'", con, 3, 3)
If rec.RecordCount <> 0 Then
Dim i As Integer = 0
Do Until rec.EOF
lvFees.Items.Add(rec("payname").Value)
lvFees.Items(i).SubItems.Add(rec("amount").Value)
If rec("paytype").Value = "REQUIRED" Then ' the paytype has two options, the "REQUIRED" and OPTIONAL.
lvFees.Items(i).Checked = True
End If
rec.MoveNext()
i = i + 1
Loop
End If
rec.Close()
con.Close()
i want to lock the check of all REQUIRED item, while letting the user to check and uncheck the OPTIONAL items
I'm sure there are lower-level ways of accomplishing this, such as by inheriting the ListView class and overriding some of its behavior, but for a simpler approach, you could simply force the value in the ItemChecked event. You could use the Tag property of each item in the list to keep track of its "locked" value. For instance, if you loaded a ListView control with three items like this:
ListView1.Items.Add("Unlocked")
ListView1.Items.Add("Locked On").Tag = True
ListView1.Items(1).Checked = True
ListView1.Items.Add("Locked Off").Tag = False
And then put code like this in the ItemChecked event handler:
Private Sub ListView1_ItemChecked(sender As Object, e As ItemCheckedEventArgs) Handles ListView1.ItemChecked
If e.Item.Tag IsNot Nothing Then
e.Item.Checked = DirectCast(e.Item.Tag, Boolean)
End If
End Sub
Then that makes it so the check box of the first item can be altered by the user, but the other two items are locked in their respective states.
I have managed to filter my combobox so it filters and displays the correct records ie when A is typed all A records show, B is typed all B records show and so on. However it would it be possible to have a message box to display when no records is found in the combobox?
The coding i have so far is :-
Private Sub cmblogged_KeyPress(sender As Object, e As KeyPressEventArgs) Handles cmblogged.KeyPress
If Char.IsControl(e.KeyChar) Then Return
With Me.cmblogged
Dim ToFind As String = .Text.Substring(0, .SelectionStart) & e.KeyChar
Dim Index As Integer = .FindStringExact(ToFind)
If Index = -1 Then Index = .FindString(ToFind)
If Index = -1 Then Return
.SelectedIndex = Index
.SelectionStart = ToFind.Length
.SelectionLength = .Text.Length - .SelectionStart
e.Handled = True
End With
End Sub
Achieving what you want with your code is straightforward, just convert If Index = -1 Then Return into:
If Index = -1 Then
MessageBox.Show("Not Found.")
Return
End If
In any case, note that there is an in-built functionality in ComboBox performing the same action which your code does right now: AutoCompleteMode different than None and AutoCompleteSource set to ListItems. Logically, you can evolve your code to perform much more complex actions (what I guess that is the case), but I preferred to highlight this issue just in case.
I have various input textboxes on my form. And based on entries made in each of the input textboxes, I update the display textbox. However, using AppendText message of textbox. I have my input textboxes set to accept only numeric values. So for each character inputted correctly, I update the display Textbox. Problem I have is when user selects Key.Back or Key.Delete, what I would like to do is do a Delete of last character in the display textbox, but I am yet to get this right. How do I delete the last character in the string that is contained in a TextBox using AppendText() or any of the utility functions available in that control please?
If (e.KeyChar = ChrW(Keys.Back)) Then
txtDisplay.Text -= 1
txtDisplay.Update()
ElseIf (Not (Char.IsDigit(e.KeyChar) Or Char.IsControl(e.KeyChar) Or (e.KeyChar = "."))) Then
e.Handled = True
Else
txtDisplay.AppendText(e.KeyChar)
End If
Use this in KeypPress event handler of txtDisplay
If e.KeyChar = ChrW(Keys.Back) Then
If txtDisplay.Text.Length > 0 Then
txtDisplay.Text = txtDisplay.Text.Substring(0, txtDisplay.TextLength - 1)
txtDisplay.SelectionStart = txtDisplay.Text.Length
End If
ElseIf (Not (Char.IsDigit(e.KeyChar) Or Char.IsControl(e.KeyChar) Or (e.KeyChar = "."))) Then
e.Handled = True
End If