C# - Correct validation for integers - c#

I am currently building my project using windows forms and came across a minor "problem".
I have the user enter an hour which is stored as an int. I want to provide detailed feedback to the user so that they know exactly what they have done wrong should they cause an error.
If no value is given, a format exception is thrown.
If anything but an integer is given, a format exception is thrown.
This means I cannot directly tell the user that the new item could not be added due to EITHER 1) no value or 2) not an integer as they both use the same exception.
How can I solve this and what would be the best solution?
Many thanks.

Use the Int32.TryParse Method and check return value. You can simply check for no value entered before calling TryParse.
Here's an example of usage from MSDN:
int number;
bool result = Int32.TryParse(value, out number);
if (result)
{
Console.WriteLine("Converted '{0}' to {1}.", value, number);
}
else
{
if (value == null) value = "";
Console.WriteLine("Attempted conversion of '{0}' failed.", value);
}

some example code related to your question; note ValidateData in particular:
// called from ok button click or similar event
private void Accept()
{
if (!ValidateData())
return;
SaveData();
DialogResult = DialogResult.Ok;
Dispose();
}
private bool ValidateData()
{
int val;
if (string.IsNullOrEmpty(mTextBox.Text))
return FailValidation("Value can not be empty.", mTextBox);
if (!int.TryParse(mTextBox.Text, out val))
return FailValidation("Value was not an integer.", mTextBox);
return true;
}
// do something with the value if you need
private void SaveData()
{
}
// post a message to the user, and highlight the problematic control
// always evaluates to false
private bool FailValidation(string pMessage, Control pControl)
{
if (pControl != null)
{
pControl.Focus();
TextBox textBox = pControl as TextBox;
if (textBox != null)
textBox.SelectAll();
}
AlertBox(pMessage);
return false;
}
// quick alert message method
private void AlertBox(string pMessage)
{
return MessageBox.Show
(
pMessage,
Application.ProductName,
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1
);
}

Use int.TryParse to check format and than in success case check if integer is in valid range. Use String.IsNulOrEmpty to check for empty string.

If I can suggest a possible alternate solution... The best validation is preventing the bad input in the first place. Can you restrict the values the user can choose by using a control like a time picker or dropdown list? A dropdown list would still be keyboard friendly for powerusers, and it is a little easier for those who prefer a mouse. Wins for everyone.

This is well supported in Winforms. Use the Validating event to check the entry, the ErrorProvider component to report the error. A sample event handler:
private void textBox1_Validating(object sender, CancelEventArgs e) {
int hour;
e.Cancel = true;
if (textBox1.Text.Length == 0) errorProvider1.SetError(textBox1, "Can't be empty");
else if (!int.TryParse(textBox1.Text, out hour)) errorProvider1.SetError(textBox1, "Not a number");
else if (hour < 1) errorProvider1.SetError(textBox1, "Hour too small");
else if (hour > 24) errorProvider1.SetError(textBox1, "Hour too large");
else {
e.Cancel = false;
errorProvider1.SetError(textBox1, "");
}
}
Then you just need to check if all entries were satisfactory. Use the ValidateChildren() method in the dialog's OK button click event handler:
private void OKButton_Click(object sender, EventArgs e) {
if (ValidateChildren()) this.DialogResult = DialogResult.OK;
}

Related

Using Linq to loop through all controls only get the first control

I have a form with 5 text boxes and a button, I want to check all these text boxes for empty or null user input.
Using this simple method:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (verifyUI() == true)
MessageBox.Show("user input for all textboxes was correct!");
else
MessageBox.Show("user input for all textboxes was missing!");
}
private bool verifyUI()
{
bool userInputOk = false;
foreach (Control cTxt in Controls.OfType<TextBox>())
{
if (string.IsNullOrWhiteSpace(cTxt.Text) || cTxt.Text == "")
{
userInputOk = false;
}
else
{
userInputOk = true;
}
}
return userInputOk;
}
}
When I enter a value in text box 1 , the method is checking only the first text box and returning true, and ignoring all other text boxes.
I am sure something is wrong in the logic of the method which I am using.
It seems you want to know if any input is wrong (or all the input is correct):
private bool verifyUI() {
return !Controls
.OfType<TextBox>()
.Any(cTxt => string.IsNullOrWhiteSpace(cTxt.Text));
}
or (equivalent All() implementation)
private bool verifyUI() {
return Controls
.OfType<TextBox>()
.All(cTxt => !string.IsNullOrWhiteSpace(cTxt.Text));
}
In your current code you constantly re-write userInputOk and so return the last value
The easiest way would be to use the All method:
private bool verifyUI()
{
return Controls.OfType<TextBox>().All(tb => !string.IsNullOrWhiteSpace(tb.Text));
}
You could also invert the logic and use the Any method as well. I prefer All in this case simply because it conveys the intention better and makes the logic more clear for the next person.
I think just for checking whether any textbox is not filled. following code is enough.
private bool verifyUI()
{
bool alluserInputsOk = true;
foreach (Control cTxt in Controls.OfType<TextBox>())
{
if (string.IsNullOrWhiteSpace(cTxt.Text))
{
userInputOk = false;
break;
}
}
return userInputOk;
}
or You can use .any() method with your list
Your code is actually only checking whether the last control in the list is blank or not, since that's where your iteration ends.
Your code checks only the last textbox it finds. Try this All() statement instead:
bool userInputOk = Controls.OfType<TextBox>()
.All(tb => !string.IsNullOrWhiteSpace(tb.Text));
Note that string.IsNullOrWhiteSpace() is true for string.Empty, too. So you don't need that extra check for string.Empty.
Actually, it's probably looping though all controls, but only returning the result of the last one, since you set the value of userInputOk back to true if the control has valid text. So the end result is the result for the last control. Now it's possible that textBox1 is the last control in the collection, depending on how they were added. You can either just remove the else block, which will flag userInputOk only if a control has an invalid value, or use Linq:
bool userInputOk =
!Controls.OfType<TextBox>()
.Any(cTxt => string.IsNullOrWhiteSpace(cTxt.Text))

Position cursor on textbox that has error

I have a simple form that takes 9 decimal numbers from 9 textboxes and I put some validation so that the users can only enter decimal numbers and nothing else.
Now the challenge I'm having is how to set the cursor in the textbox that had no decimal number after showing the error message in the try-catch statement?
Here's my code:
private void btn_Aceptar_Click(object sender, EventArgs e)
{
POI GPI = new POI();
POI VIM = new POI();
POI ST = new POI();
try
{
GPI.POI_x = Convert.ToDecimal(txt_GPIx.Text);
GPI.POI_y = Convert.ToDecimal(txt_GPIy.Text);
GPI.POI_z = Convert.ToDecimal(txt_GPIz.Text);
VIM.POI_x = Convert.ToDecimal(txt_VIMx.Text);
VIM.POI_y = Convert.ToDecimal(txt_VIMy.Text);
VIM.POI_z = Convert.ToDecimal(txt_VIMz.Text);
ST.POI_x = Convert.ToDecimal(txt_STx.Text);
ST.POI_y = Convert.ToDecimal(txt_STy.Text);
ST.POI_z = Convert.ToDecimal(txt_STz.Text);
}
catch (Exception)
{
MessageBox.Show("Ingrese solamente nĂºmeros en las variables GPI/VIM/ST", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
//Set the cursor in the first textbox that had no decimals..
return;
}
Comisurales Comisurales = new Comisurales();
Comisurales.calculo_coord_comisurales(PC, AC, IHP, GPI, VIM, ST);
}
Let me add that I also have a function to ensure the user is only limited to enter decimals but I wasn't able to figure how to avoid the "." only or this for example: "1."
As an addition to my question, here's what gets validated every time the user press a key in the textbox:
private void ValidarDecimal(object sender, KeyPressEventArgs e)
{
// permitir 0-9, backspace, y decimal
if (((e.KeyChar < 48 || e.KeyChar > 57) && e.KeyChar != 8 && e.KeyChar != 46))
{
e.Handled = true;
return;
}
// chequear solamente un decimal
if (e.KeyChar == 46)
{
if ((sender as TextBox).Text.IndexOf(e.KeyChar) != -1)
e.Handled = true;
}
}
I guess I have 2 ways to resolve my issue. Number one would be find a way to ensure the user never ever enters something weird in the textbox (which I've done partially) and number 2 would be to use the try-catch with the current limitations I mentioned above and then point the user to the textbox that has issues, both are acceptable.
The Decimal class has a TryParse method that could be used to avoid all this logic driven by catching exceptions (a very expensive approach in terms of performance)
decimal value;
if(decimal.TryParse(txt_GPIx.Text, out value))
GPI.POI_x = value;
else
{
MessageBox.Show("Invalid decimal value");
txt_GPIx.Focus();
}
Of course this code needs to be repeated for every control in your list, but you could write a generic function like this one
private decimal GetValueAndValidate(Textbox txt, out bool isOK)
{
isOK = true;
decimal value = 0m;
if(!decimal.TryParse(txt.Text, out value))
{
MessageBox.Show("Invalid decimal value");
txt.Focus();
isOK = false;
}
return value;
}
and then use the following approach in your code inside the button click
bool isOK = true;
if(isOK) GPI.POI_x = GetValueAndValidate(txt_GPIx, out isOK);
if(isOK) GPI.POI_y = GetValueAndValidate(txt_GPIy, out isOK);
.... and so on for the other fields ....
For the second part of your question, finding a way to completely control the input logic is not easy. What happens for example if your user PASTE an invalid text in your textbox? There are very edge case situations that takes a lot of effort to code correctly. It is a lot more easy to leave freedom of typing to your user and apply a strict logic when you get that input.

Prevent other Methods from running under a condition is met in a previous Method

My problem is when the user clicks on myButton the program operates perfectly fine. But if the user was to input a value less than 3 in the first textbox a message box will appear to the user stating that the value must be greater than 3 metres. If you click OK the next method in myButton runs anyway and the result message box appears anyway.
I've tried looking around to solve this problem of mine using Nested For Loops but failed to get them to work (most likely a fault on my end). I also prefer not to use Goto because it isn't exactly good programming practice to use. Of course you can tell me otherwise if you want :) .
// Button
private void myButton_Click(object sender, EventArgs e)
{
checkIfNumericalValue();
testIfTextBoxOnesMinimumIsMet();
testIfTextBoxTwosMinimumIsMet();
displayResultToUser();
resetOrClose();
}
// Textbox One
public void testIfTextBoxOnesMinimumIsMet()
{
if (length < 3)
{
MessageBox.Show("length must be greater than 3 metres");
}
}
Help would be greatly appreciated this is also my second attempt at C# on Visual Studio 2012. Do not worry this has nothing to do with my year 10 schooling as my school doesn't have a programming subject. This problem occurs in testIfTextBoxOnesMinimumIsMet() and testIfTextBoxOnesMinimumIsMet() as well but if someone can help me with this one method I should be able to fix the rest :)
You could throw an exception from your inner functions and catch it from your button's function, something like this:
// Button
private void myButton_Click(object sender, EventArgs e)
{
try
{
checkIfNumericalValue();
testIfTextBoxOnesMinimumIsMet();
testIfTextBoxTwosMinimumIsMet();
displayResultToUser();
resetOrClose();
}
catch (ArgumentException ex)
{
// The error message we defined at the exception we threw
MessageBox.Show(ex.Message);
}
}
// Textbox One
public void testIfTextBoxOnesMinimumIsMet()
{
if (length < 3)
{
throw new ArgumentException("Length must be greater than 3 meters.");
}
}
An alternative would be to deal with the validation within your button like so:
// Button
private void myButton_Click(object sender, EventArgs e)
{
checkIfNumericalValue();
if (length < 3)
{
MessageBox.Show("Length must be greater than 3 meters.");
return;
}
testIfTextBoxTwosMinimumIsMet();
displayResultToUser();
resetOrClose();
}
What happens above is that the return will leave that function without further processing anything else.
So, if I'm understanding this correctly, if the text boxes contain numerical values, text box 1 meets the minimum and text box 2 meets the minimum, you want to displayResultToUser() and then resetOrClose().
If that's the case, you can have the 3 methods checkIfNumericalValue(), testIfTextBoxOnesMinimumIsMet() and testIfTextBoxTwosMinimumIsMet() return a bool depending on what the minimum condition is and then write something like this:
private void myButton_Click(object sender, EventArgs e)
{
if (checkIfNumericalValue() && testIfTextBoxOnesMinimumIsMet(Convert.ToInt32(txtBoxOne.Text)) && testIfTextBoxTwosMinimumIsMet(Convert.ToInt32(txtBoxTwo.Text)))
{
displayResultToUser();
resetOrClose();
}
}
public bool testIfTextBoxOnesMinimumIsMet(int length)
{
if (length < 3)
{
MessageBox.Show("length must be greater than 3 metres");
return false;
}
return true;
}
It appears that you need some other variable to track whether or not you have encountered errors. To do this, you could have a bool noErrors variable defined, and you should return a boolean from your error check methods that is True if there were no errors, otherwise False. This way you know if you ran into any problems.
Finally, you should check for the state of errrorsFound before running any of your other methods.
For example:
// Button
private void myButton_Click(object sender, EventArgs e)
{
bool noErrors =
isNumericalValue() &&
textBoxOnesMinimumIsMet() &&
textBoxTwosMinimumIsMet();
if (noErrors)
{
displayResultToUser();
resetOrClose(); // I'm not sure if this should happen regardless of errors?
}
}
// Textbox One
public bool textBoxOnesMinimumIsMet()
{
if (length < 3)
{
MessageBox.Show("length must be greater than 3 metres");
return false;
}
return true;
}

Form Validation Before Storing to SQLite DB in C#

I have a save button. When a user clicks it. The data-input in the the form will be validated before it will be send to the db. How can I achieve it?
Here's the code of my save button.
private void save_Click(object sender, EventArgs e)
{
try
{
int civil_caseI = int.Parse(civil_case.Text);
}
catch (Exception cc)
{
MessageBox.Show("Enter Number Only on CIVIL CASE");
}
string areaI = area.Text;
if (areaI.Length <= 0)
{
MessageBox.Show("Area Field must not be Empty");
}
string addressI = address.Text;
if (addressI.Length <= 0)
{
MessageBox.Show("Address Field must not be Empty");
}
// HERE WILL BE THE QUERY TO INSERT THE DATA AFTER THE FORM IS VALIDATED.
}
You are not returning from your method if there is an error in validation. return early from your method if there is an exception/failure in rule validation. You can just add return; statement after showing the error message.
A better way would be to use int.TryParse to parse int values instead of try-catch. Remember exceptions are expensive. You can also use string.IsNullOrWhiteSpace instead of addressI.Length <= 0 which will consider white space as invalid values (if you are using .Net framework 4.0 or higher, otherwise use `string.IsNullOrEmpty).
private void save_Click(object sender, EventArgs e)
{
int civil_caseI;
if (!int.TryParse(civil_case.Text, out civil_caseI))
{
MessageBox.Show("Enter Number Only on CIVIL CASE");
return; //add that - early return
}
string areaI = area.Text;
if (string.IsNullOrEmpty(areaI.Trim()))
{
MessageBox.Show("Area Field must not be Empty");
return;
}
string addressI = address.Text;
if (string.IsNullOrEmpty(addressI.Trim())) //or addressI.Trim().Length <= 0
{
MessageBox.Show("Address Field must not be Empty");
return;
}
//HERE WILL BE THE QUERY TO INSERT THE DATA AFTER THE FORM IS VALIDATED.
}
You can also extract out the logic of your validation in a separate method returning bool and then call that method for validation.

How to insist that a textbox has a value

I have a textbox on a form and a button on the same form. On the button click event I want to insist that the textbox has a value in the form of a Double. What I have so far is this -
public double getUnitStake(Form frontpage)
{
double doubleresult=0;
bool unitStake;
foreach (Control c in frontpage.Controls)
{
if (c.Name == "tbUnitStake")
{
unitStake = double.TryParse( (c as TextBox).Text, out doubleresult);
if (!unitStake)
{
}
else
{
doubleresult=double.Parse((c as TextBox).Text);
}
}
}
return doubleresult;
}
But for the life of me I can't figure out what to do if the double.tryparse method is false. What I want is the program execution to halt until a suitable value has been entered into the textbox. How can I achieve that please? Thanks for all and any help.
It seems to me you are validating the contents of a textbox. Therefore you can use the validating event of a TextBox Control. Optionally connect an errorProvider control to it to give special focus on the error (blinking exclamation mark). The "e.Cancel" statement will prevent you from doing anything else with your form until a double is entered.
private void textBox1_Validating(object sender, CancelEventArgs e)
{
double doubleresult = 0;
bool result = Double.TryParse(textBox1.Text, out doubleresult);
if (result)
{
errorProvider1.SetError(textBox1, string.Empty);
}
else
{
errorProvider1.SetError(textBox1, "Must be a Double");
e.Cancel = true;
}
}
I am not aware of the complete requirements of your program.
You could disable the button by default. In the text change event of the text box, check if the value entered is a double.
Once you have verified that the value is indeed a double, then you could enable the button.
This way the program execution is halted by default, unless a double is entered.
Your code is fine except the following change
public double getUnitStake(Form frontpage)
{
double doubleresult=0;
bool unitStake;
foreach (Control c in frontpage.Controls)
{
if (c.Name == "tbUnitStake")
{
unitStake = double.TryParse( (c as TextBox).Text, out doubleresult);
}
}
return doubleresult;
}
you need not to parse the "doubleresult" again as the tryparse has already done it. You can use unitStake variable for further use.
you just need TryParse yor value if your value not a double this method return you 0.0:
public double getUnitStake(Form frontpage)
{
double doubleresult=0;
foreach (Control c in frontpage.Controls)
{
if (c.Name == "tbUnitStake")
{
double.TryParse( (c as TextBox).Text, out doubleresult);
}
}
return doubleresult;
}

Categories

Resources