Error Providers - c#

I am trying to apply error providers to a text box,
The user cannot enter more than 25 characters
The textbox cannot be left blank
private void txtNameandSurn_TextChanged(object sender, EventArgs e)
{
txtNameandSurn.MaxLength = 25;
if (txtNameandSurn.Text == "")
{
txtNameandSurn.BackColor = Color.White;
errorProvider1.SetError(txtNameandSurn, "Cannot be blank!");
}
else
{
txtNameandSurn.BackColor = Color.Red;
errorProvider1.SetError(txtNameandSurn, "");
}
if (txtNameandSurn.Text.Length >= txtNameandSurn.MaxLength)
{
errorProvider1.SetError(txtNameandSurn, "Cannot input more than 25 characters!");
}
else if (txtNameandSurn.Text.Length < txtNameandSurn.MaxLength)
{
errorProvider1.SetError(txtNameandSurn, "");
}
}
The issue I have is with the character input, the error provider shows up but when i press another key the icon for the errorprovider disappears but the character is not inputted which is good. How do I keep the error provider icon showing?

You should rewrite your conditions in a logical manner. Every case where there is not a correct input should have it's definition and have the correct error set. In the other case (input is correct), remove the error. Simplified it should look like this:
if (String.IsNullOrEmpty(textBox1.Text))
{
errorProvider1.SetError(textBox1, "Cannot be blank!");
}
else if(textBox1.Text.Length >= textBox1.MaxLength)
{
errorProvider1.SetError(textBox1, "Cannot input more than 25 characters!");
}
else
{
errorProvider1.SetError(textBox1, "");
}
This way, you can add more conditions easiliy. If you want for example that the tekst doesn't contain a #, simply add following statement:
if(textBox1.Contains("#"))
{
errorProvider1.SetError(textBox1, "Cannot contain a '#'!");
}

For great readability and flexibility for adding new rules, I prefer this syntax. Note that this also lends itself to creating reusable sets of rules for specific data types.
This untested code should solve your problem with the errorProvider, if I understood it.
class Rule
{
public Func<string, bool> Test { get; set; }
public string Message { get; set; }
}
private void txtNameandSurn_TextChanged(object sender, EventArgs e)
{
var rules = new List<Rule>()
{
new Rule() { Test = s => !String.IsNullOrEmpty(s), Message="String cannot be blank." },
new Rule() { Test = s => (s.Length <= txtNameandSurn.MaxLength), Message="String cannot be longer than " + txtNameandSurn.MaxLength },
new Rule() { Test = s => !s.Contains("#"), Message = "String cannot contain a hash character." }
};
var isValid = rules.All(r => r.Test(txtNameandSurn.Text));
string[] message;
if (!isValid)
{
message = rules.Where(r => r.Test(txtNameandSurn.Text) == false).Select(r => r.Message);
}
errorProvider1.SetError((message.Length > 0) ? (string.Join(';', message)) : "");
}

Related

C# if statement to test if value is not a number

I am trying to save the .text of a label to a database but sometimes that label is an infinity symbol. To catch for this I have created an if statement which checks if the label is a number or not and throws a message box up to tell the user. However more often than not the label will be a decimal number and the if statement throws up the message box. I was wondering if anyone could help me out please?
private void btnSaveResults_Click(object sender, EventArgs e)
{
btnClearData.Enabled = true;
if (System.Text.RegularExpressions.Regex.IsMatch(lblAerobicCap.Text, "[^0-9]"))
{
MessageBox.Show("Im sorry, there seems to have been an error in the inputting of the readings, please restart the test");
}
else
{
AthletesDetailsNew users = new AthletesDetailsNew();
DateTime dateTimeVariable = DateTime.Now;
users.Date_Of_Test = dateTimeVariable;
users.First_Name = comboBoxFirstName.Text;
users.Surname = comboBoxNewSurname.Text;
users.Age = int.Parse(comboBoxAge.Text);
users.Account_Number = int.Parse(comboBoxAccountNumber.Text);
users.Aerobic_Capacity = /*Math.Truncate*/(decimal.Parse(lblAerobicCap.Text));
DataClassDataContext dbCtx = new DataClassDataContext();
dbCtx.AthletesDetailsNews.InsertOnSubmit(users);
try
{
dbCtx.SubmitChanges();
MessageBox.Show("Data saved");
}
catch
{
MessageBox.Show("Data failed to save");
}
}
}
You should use the .TryParse() method for this.
for example:
decimal value;
bool isNumber = Decimal.TryParse(inputVariable, out value);
Use decimal.TryParse so in case of success you can reuse the result
decimal aerobicCap = -1;
if (!decimal.TryParse( lblAerobicCap.Text, out aerobicCap))
{
MessageBox.Show("Im sorry, there seems to have been an error in the inputting of the readings, please restart the test");
}
else
{
// code ...
users.Aerobic_Capacity = aerobicCap;
I think you need to trim the spaces from lblAerobicCap.Text prior to checking if the value is a number. Something like lblAerobicCap.Text = lblAerobicCap.Text.Trim().
lblAerobicCap.Text = lblAerobicCap.Text.Trim();
if (System.Text.RegularExpressions.Regex.IsMatch(lblAerobicCap.Text, "[^0-9]"))
{
MessageBox.Show("Im sorry, there seems to have been an error in the inputting of the readings, please restart the test");
}
[ ... ]
Better still avoid users entering anything but numbers. That way you do not have to validate the input.
For the digits use something like this:
void Control_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsDigit(e.KeyChar))
{
e.Handled = true;
}
}
Decimal:
void Control_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode != Keys.Decimal)
{
e.Handled = true;
}
}
I have used an extension method in the past which works nicely for me:
public static bool IsNumber(this object value)
{
return value is sbyte
|| value is byte
|| value is short
|| value is ushort
|| value is int
|| value is uint
|| value is long
|| value is ulong
|| value is float
|| value is double
|| value is decimal;
}
object testObject = 0.1;
if (testObject.IsNumber()) { MessageBox.Show("Hooray!"); }

Passing an argument to a method in C#

So, this program functions as it should, but I don't think the code is very "clean," so I'm looking for suggestion. Two of the big issues I have:
For the method public double temperatureInFahrenheit, I want to pass the argument celsiusTemperature to the function instead of having to redeclare the variable, convert it from the text to the double, etc. Whenever I attempt to try that I get an error in the MessageBox.Show when I try to call the function myAirport.temperatureInFahrenheit (I don't specifically remember what the error is, so I'll have to recode if that's needed). Any thoughts on what I can do to make this work?
The MessageBox.Show in the public partial class Form1 seems like messy code to me. What I'd like to do is write a method in internal class Airport, where the necessary arguments are passed to the method, and then just do something like MessageBox.Show(myAirport.message()), but I'm assuming if I tried that I'd get the same error as I'm getting for 1. Any thoughts?
Again, the code is completely functional and works and meets the required specifications, but I don't like just having functional code, I like having functional code that's "pretty." Note: I don't have comments for any of the methods, variables, etc. I'm putting those in right now, but thought I'd try and get some feedback first.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string airportName;
double celsiusTemperature, elevation;
if (String.IsNullOrEmpty(txtAirport.Text)) {
MessageBox.Show("You did not enter a name for the airport. Please enter a name for the airport.");
return;
} else
{
airportName = Convert.ToString(txtAirport.Text);
}
if (Double.TryParse(txtTemperature.Text, out celsiusTemperature))
{
if (celsiusTemperature > 50 || celsiusTemperature < -50)
{
MessageBox.Show("The value you entered for temperature is outside the acceptable range. Please reenter the information");
Application.Restart();
}
}
else
{
MessageBox.Show("You did not enter a numeric value for the temperature. Please enter a valid, i.e. numeric, value for the temperature.");
return;
}
if (Double.TryParse(txtElevation.Text, out elevation))
{
if (elevation > 12000 || elevation < -300)
{
MessageBox.Show("The value you entered for elevation is outside the acceptable range. Please reenter the information.");
Application.Restart();
}
}
else
{
MessageBox.Show("You did not enter a numeric value for the elevation. Please enter a valid, i.e. numeric, value for the elevation.");
return;
}
Airport myAirport = new Airport(airportName, celsiusTemperature, elevation);
MessageBox.Show("The airport name is: " + myAirport.airportName(airportName) + Environment.NewLine + "The Celsius temperature is: " + myAirport.celsiusTemperature(celsiusTemperature)
+ Environment.NewLine + "The Fahrenheit temperature is: " + myAirport.temperatureInFahrenheit(celsiusTemperature) + Environment.NewLine + "The elevation is: " + myAirport.elevation(elevation));
}
private void button2_Click(object sender, EventArgs e)
{
Close();
}
}
internal class Airport
{
private string airportName1;
private double celsiusTemperature1;
private double elevation1;
public Airport(string airportName1, double celsiusTemperature1, double elevation1)
{
this.airportName1 = airportName1;
this.celsiusTemperature1 = celsiusTemperature1;
this.elevation1 = elevation1;
}
public string airportName(string airportName1)
{
return airportName1;
}
public double celsiusTemperature(double celsiusTemperature1)
{
return celsiusTemperature1;
}
public double elevation(double elevation1)
{
return elevation1;
}
public double temperatureInFahrenheit(double celsiusTemperature1)
{
double fahrenheitTemperature = 0;
fahrenheitTemperature = celsiusTemperature1 * (1.8) + 32;
return fahrenheitTemperature;
}
}
}
Here is an example of how you could simplify the Airport class:
public class Program
{
static void Main(string[] args)
{
var airport = new Airport { AirportName = "JFK", Temperature = 28.5 };
Console.WriteLine(airport.ToString());
}
}
public class Airport
{
private string _airportName;
private double _temperatureInCelsius;
private double _temperatureInFahrenheit;
public string AirportName
{
get
{
return _airportName;
}
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new Exception("You did not enter a name for the airport. Please reenter the information.");
}
_airportName = value;
}
}
public double Temperature
{
get
{
return _temperatureInCelsius;
}
set
{
if (value > 50 || value < -50)
{
throw new Exception("The value you entered for temperature is outside the acceptable range. Please reenter the information");
}
_temperatureInCelsius = value;
_temperatureInFahrenheit = _temperatureInCelsius *(1.8) + 32;
}
}
public override string ToString()
{
return string.Format(
"The airport name is: {0}\r\nThe Celsius temperature is: {1}\r\nThe Fahrenheit temperature is: {2}", _airportName, _temperatureInCelsius, _temperatureInFahrenheit);
}
}
Notice that the AirportName setter validates the passed airport name (via value) and if it's not valid, then an exception is thrown.
I will leave the other property - elevation, as an excercise for you to finish.
Regards the Message.Show you can do this:
Message.Show(airport.ToString());
The ToString() method return a string that describes the airport.
My code is a suggestion, you don't have to use it exactly (i.e. you might not like throwing an exception from the setters. Instead you could validate the values before creating an Airport instance.), but hopefully it will guide you.
There are many ways to separate the different requirements, so i try to give the idea.
First, a simple airport data class with properties and inside validation (That means invalid instances are possible):
internal class Airport
{
private string _airportName;
private double _celsiusTemperature;
private double _elevation;
public Airport(string airportName, double celsiusTemperature, double elevation)
{
this._airportName = airportName;
this._celsiusTemperature = celsiusTemperature;
this._elevation = elevation;
}
public string AirportName
{
get
{
return _airportName;
}
set
{
_airportName = value;
}
}
public double CelsiusTemperature
{
get
{
return _celsiusTemperature;
}
set
{
_celsiusTemperature = value;
}
}
public double Elevation
{
get
{
return _elevation;
}
set
{
_elevation = value;
}
}
public double TemperatureInFahrenheit
{
get
{
return _celsiusTemperature * (1.8) + 32.0;
}
set
{
if (value != 32.0)
{
_celsiusTemperature = (value - 32.0) / (1.8);
}
else
{
_celsiusTemperature = 0.0;
}
}
}
public bool IsValid(out string errorMessage)
{
bool result = false;
bool ok = true;
errorMessage = "";
if (String.IsNullOrEmpty(_airportName))
{
ok = false;
errorMessage = "You did not enter a name for the airport.";
}
if (_celsiusTemperature > 50 || _celsiusTemperature < -50)
{
ok = false;
errorMessage = "The value you entered for temperature is outside the acceptable range.";
}
if (_elevation > 12000 || _elevation < -300)
{
ok = false;
errorMessage = "The value you entered for elevation is outside the acceptable range.";
}
result = ok;
return result;
}
}
Note that Fahrenheit is a calculated value based on Celsius. It could be vice versa.
Also note the validation. The airport class is accepting all values, only the type of the fields is defined. It does the semantic check (business logic).
Now, how to use it:
private void button1_Click(object sender, EventArgs e)
{
string airportName;
double celsiusTemperature;
double elevation;
// Get data from controls and do syntactic checks
bool ok = true;
airportName = txtAirport.Text;
ok = Double.TryParse(txtTemperature.Text, out celsiusTemperature);
if (!ok)
{
// Error
MessageBox.Show("The value you entered for temperature is not a number!", "Error");
}
ok = Double.TryParse(txtElevation.Text, out elevation);
if (!ok)
{
// Error
MessageBox.Show("The value you entered for elevation is not a number!", "Error");
}
if (ok)
{
// Create the instance of the data class and do semantic checks
Airport myAirport = new Airport(airportName, celsiusTemperature, elevation);
string errorMessage;
if (!myAirport.IsValid(out errorMessage))
{
// Error
MessageBox.Show(errorMessage + " Please reenter the information", "Error");
}
else
{
// Ok, data is valid. Continue normal work...
MessageBox.Show("The airport name is: " + myAirport.AirportName + Environment.NewLine +
"The Celsius temperature is: " + myAirport.CelsiusTemperature + Environment.NewLine +
"The Fahrenheit temperature is: " + myAirport.TemperatureInFahrenheit + Environment.NewLine +
"The elevation is: " + myAirport.Elevation);
}
}
You get the data from controls and do syntactic checks. If everthing is allright, you let the class do the semantic checks itself. Here it will give a string as message details, but many otehr ways are passible.
So, in the end, if a syntatic error occurs, the user gets a message and can continue. If a semantic error occurs, the user gets a message and can continue. If everything is ok, you can operate an valid data.
Hope this helps...
I think, you should refactor your code in the following ways:
Airport seems to be a data class. So it should not know about the TextBox's of Form1. So, try to separate them.
For easy access, you should think of properties (or getter/setter methods) for your fields in the airport class.
You are checking the data when you are reading it out of the airport class. What if you are never reading it or using it inside before? So, you should separate this.
If you estimate an error, you are restarting the complete application. That is normaly not a good deal to the user. So try to show the error and let the user do some corrections. And check again, and so on...
If Airport is data class, then you can have to properties for Celsius and Fahrenheit. No matter what representation you have internaly.
I would like to give these hints without code first, so you can think and try for yourself before looking at a complete solution. Then, if you get stuck somewhere, i (and others) will give more concrete tips.
So, good luck for you...

Exception Handling C#

I am relatively new at this and have been racking my brain attempting to get my program to work properly and it just won't. I am working in Visual Studio 2012 C# on a Forms Application.
I need it to produce a distinct error message when the user input value is more than 0 but less than 10,000. It also must produce a distinct error message when the user enters a non-numeric value and a distinct error message when the user fails to enter any value at all.
The code I've written so far produces a distinct error message when the user enters a non-numeric value or when they fail to enter any value at all, but it does not trigger an error message when the user enters a value that is below or over the required range.
It is as if the compiler is ignoring the code I've written for the first exception/overflow exception and only recognizing the code for the second and final exception. My code has no coding errors. It appears that my problem is in the logic.
Please help me if you can. My code is below thanks!
private void btnCalculate_Click(object sender, System.EventArgs e)
{
try
{
{
decimal subtotal = Convert.ToDecimal(txtSubtotal.Text);
decimal discountPercent = .25m;
decimal discountAmount = subtotal * discountPercent;
decimal invoiceTotal = subtotal - discountAmount;
lblDiscountPercent.Text = discountPercent.ToString("p1");
lblDiscountAmount.Text = discountAmount.ToString("c");
lblTotal.Text = invoiceTotal.ToString("c");
}
}
catch (OverflowException)
{
decimal subtotal = Convert.ToDecimal(txtSubtotal.Text);
if (subtotal <= 0)
{
MessageBox.Show("Subtotal must be greater than $0.00. ", "Error Entry");
txtSubtotal.Focus();
}
if (subtotal >= 10000)
{
MessageBox.Show("Subtotal must be less than $10000.00. ", "Error Entry");
txtSubtotal.Focus();
}
}
catch (Exception)
{
if (txtSubtotal.Text == "")
{
MessageBox.Show("Subtotal is a required field. ", "Error Entry");
}
else
{
MessageBox.Show(
"Please enter a valid Number for the subtotal field.", "Error Entry");
txtSubtotal.Focus();
}
}
}
private void btnExit_Click(object sender, System.EventArgs e)
{
this.Close();
}
private void txtSubtotal_TextChanged(object sender, EventArgs e)
{
}
}
}
I would used KeyEvent press enter or Leave event for this, first I need to create generic class for verification if the input from the user is not a string.
Condition:
1 verify if the input is not a string Im using generic for general purposes class.
public class iCnF()
{
public static System.Boolean IsNumeric(System.Object Expression)
{
if (Expression == null || Expression is DateTime)
return false;
if (Expression is Int16 || Expression is Int32 || Expression is Int64 || Expression is Decimal || Expression is Single || Expression is Double || Expression is Boolean)
return true;
try
{
if(Expression is string)
Double.Parse(Expression as string);
else
Double.Parse(Expression.ToString());
return true;
} catch {} // just dismiss errors but return false
return false;
}
}
Then I need to verify if the input is not empty
private void txtSubtotal_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Tab)
{
if (txtSubtotal.Text.Length > 0)
{
bool sd = iCnF.IsNumeric(txtSubtotal.Text);
if (sd == false)
{
MessageBox.Show("Subtotal must be a numeric value. ", "Error Entry");
txtSubtotal.Clear();
txtSubtotal.Focus();
}
else
{
decimal subtotal = Convert.ToDecimal(txtSubtotal.Text);
if (subtotal <= 0)
{
MessageBox.Show("Subtotal must be greater than $0.00. ", "Error Entry");
txtSubtotal.Focus();
}
if (subtotal >= 10000)
{
MessageBox.Show("Subtotal must be less than $10000.00. ", "Error Entry");
txtSubtotal.Focus();
}
}
}
else
{
MessageBox.Show("Subtotal must not be empty. ", "Error Entry");
txtSubtotal.Focus();
}
}
}
if not empty and numberic value my subtotal <= 0 and subtotal >= 10000
Hope this will help you :D
As I already mentioned in comment you should consider moving your code out of catch block.
In this case you should think of creating a simple method which will validate your input and produces output message.
An example is for you:
private bool IsPageValid()
{
string errorMessage = string.Empty;
bool isValid = true;
if (subtotal <= 0)
{
errorMessage+="<li>"+"<b>Subtotal must be greater than $0.00. ", "Error Entry"+ "</b><br/>";
txtSubtotal.Focus();
isValid=false;
}
}
Likewise write other clause of validations in this function..This will validate each of your condition and if fails it would make the isValid false thus not allowing users to submit.
Now you should call this function in your button click.
protected void btnSubmit_Click(object sender, EventArgs e)
{
ClearMessage();
if (IsPageValid().Equals(true))
{
// allow next action to happen.
}
}
Code should look like this
try {
decimal subtotal = Convert.ToDecimal(txtSubtotal.Text);
try {
if(x<0 || x>10000){
throw new OverFlowException("");
}
//Do what ever you want
}
catch(Exception ex){
// catch overflow
}
}
catch(Exception ex){
// catch nonnumeric value
}

C#.Net : How to make my listview items clickable

I am creating a tool in Visual C#.Net. The algorithm of the tool is to check for all space/s before/after a parenthesis and create an error message for the found errors.
For example: input is ( Text )
Error will be raise because space before and after the parenthesis is detected.
If errors are found the code will add the errors in listview1.items().
To make my question much clearer for you here's my code:
private void button1_Click(object sender, EventArgs e)
{
int error_counter = 0;
listView1.Items.Clear();
//requirement 8c
//check for a space in open and close parenthesis
Regex test = new Regex(#"\(\s.+\s\)|\[\s.+\s\]|\{\s.+\s\}", RegexOptions.IgnoreCase);
MatchCollection matchlist = test.Matches(richTextbox1.Text);
if (matchlist.Count > 0)
{
for (int i = 0; i < matchlist.Count; i++)
{
Match firstMatch = matchlist[i];
string firstMatch_string = firstMatch.ToString();
string[] errors = new string[matchlist.Count];
errors[i] = "Ommit Space between a bracket";
listView1.Items.Add(errors[i]);
error_counter++;
}
}
}
private void listView1_ItemActivate(object sender, EventArgs e)
{
if (listView1.SelectedItems.Count > 0)
{
ListViewItem item = listView1.SelectedItems[0];
MessageBox.Show(item.ToString());
}
}
What I looking for is that all of the items of my listview1 will be clickable, and after a click was made by the user the tool will highlight the error found in the richtextbox1.
Thanks for all your help guys!
As someone already told you, use the Index and Length properties of the Match class. Here's a short example implementing a weird textbox selection strategy. But it works effectively demonstrating the concept:
public partial class Form1 : Form
{
List<Error> errors = new List<Error>();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
errors = new List<Error>();
listView1.Items.Clear();
foreach(Match m in Regex.Matches(richTextBox1.Text, #"(\(\s+|\s+\)|\[\s+|\s+\]|\{\s+|\s+\})", RegexOptions.IgnoreCase))
{
//you may decide to differentiate the msg according to the specific problem
Error error = new Error(m, "Ommit Space between a bracket");
this.errors.Add(error);
listView1.Items.Add(error.msg);
}
}
private void listView1_SelectedIndexChanged(object sender, EventArgs e)
{
if (listView1.SelectedIndices.Count > 0)
{
Error error = errors[listView1.SelectedIndices[0]];
Select(richTextBox1, error);
}
}
private static void Select(RichTextBox rtb, Error e) {
string o = rtb.Text;
rtb.Clear();
for (int i = 0; i < o.Length; i++)
{
if (i >= e.index && i <= e.index + e.length)
{
rtb.SelectionColor = Color.White;
rtb.SelectionBackColor = Color.Red;
}
else
{
rtb.SelectionColor = Color.Black;
rtb.SelectionBackColor = Color.White;
}
rtb.AppendText(o[i].ToString());
}
}
}
public class Error
{
public int index;
public int length;
public string value;
public string msg;
public Error(Match m, string msg)
{
this.index = m.Index;
this.length = m.Length;
this.value = m.Value;
this.msg = msg;
}
}
The Match object (like firstMatch) has two usefull properties here : Index and Length.
They give you the position and the length of the match in question in the original text.
With that in your knowledge, you just have to implement the highlight in the richTextBox !

C# : Mini Application Structural Design (Classes/Interfaces/etc.)

I've been creating a small application that allows a user to convert images to various sizes and formats. I've been struggling on getting a good solid design with this application. I have the application up and running, but it does integrate good Object-Oriented design. Since this is a personal project, I've been wanting to learn more about integrating interfaces, good class inheritance, object composition, and other elements of OO design.
However, I've been struggling to do so. Don't get me wrong, I know about OO design and what it is, I just don't know how to implement good OO design in projects. Of course its easy to look at class Examples that you read in books, or online. Examples may have simple scenarios such as the following.
Interface IPerson has member functions Walk(), Run() . Abstract Class Person uses IPerson Interface. Class Man and Class Female inherit from Abstract Class Person.
but when it comes to Real Projects I struggle to implement good design. I was hoping for some insight. Here is what I currently have.
Interface:
interface IPicture
{
Bitmap ReturnImage(string path, int width, int height);
}
Main Class that Holds Picture Information. This class basically stores information about the image passed, and information about the new values the user wants (i.e. new size, new file location, new pic format, etc.)
public class MyPictures : IPicture
{
//All Private variables below are properties. Property get/set's have been removed
//for the sake of space
private int _NewWidth;
private int _NewHeight;
private string _NewImgName;
private string _NewImgPath;
private string _NewImgFullPath;
private ImageFormat _NewImgFormat;
//Declare variables to hold values that have been determined
private int _OldWidth;
private int _OldHeight;
private string _OldImgName;
private string _OldImgPath;
//Old Image Format is in String format because of certain extension scenarios.
private string _OldImgFormat;
public MyPictures(Image img, string file)
{
ClearProperties();
//...set properties based on passed variables in constructor...
}
public void ClearProperties()
{
_NewWidth = 0;
_NewHeight = 0;
_NewImgName = "";
_NewImgPath = "";
_NewImgFullPath = "";
_NewImgFormat = null;
_OldWidth = 0;
_OldHeight = 0;
_OldImgName = "";
_OldImgPath = "";
_OldImgFormat = null;
}
public override string ToString()
{
return _OldImgPath;
}
public void ImageSave()
{
Bitmap tempBmp = new Bitmap(_OldImgPath);
Bitmap bmp = new Bitmap(tempBmp, _NewWidth, _NewHeight);
bmp.Save(_NewImgPath + #"\" + _NewImgName + "." + _NewImgFormat.ToString().ToLower(), _NewImgFormat);
}
public Bitmap ImageClone()
{
Bitmap bmp = new Bitmap(_OldImgPath);
return bmp;
}
Bitmap IPicture.ReturnImage(string path, int width, int height)
{
return new Bitmap(new Bitmap(path), width, height);
}
}
Main Class; Starting point of application. This definatly needs some work...
public partial class Form1 : Form
{
static bool hasThreadBeenStopped = false;
static bool imageProcessingComplete = false;
static bool imgConstrained = false;
//Default text when user selects 'All' checkbox for new image name
static string newNameDefault = "'Name' + #";
Utility.Validation.Validate valid = new Utility.Validation.Validate();
public Form1()
{
InitializeComponent();
//Populate Combo Box With Possible Image Formats...
//Conditionally show Image Properties...
ImgPropertiesEnabled();
//Set static progress bar properties...
progressBar1.Minimum = 0;
progressBar1.Step = 1;
}
private void Form1_Load(object sender, EventArgs e)
{
lblImgProcessed.Text = "";
lblFile.Text = "";
txtContentFolder.Text = "";
}
//Delegate declarations. Used for multi-thread processing
public delegate void PopulateTextboxDelegate(Label lbl, string text);
public delegate void ThreadWorkDelegate(Label lbl, string text);
public delegate void ImageDisplayDelegate(Image i);
public delegate void ProgressBarDelegate(ProgressBar p, int step, int value);
//Populate textbox fields with image processed, and image path being processed
public void PopulateTextbox(Label lbl, string text)
{
lbl.Text = "";
lbl.Text = text;
}
public void ThreadWork(Label lbl, string text)
{
this.Invoke(new PopulateTextboxDelegate(PopulateTextbox),
new object[] { lbl, text });
}
//Display Currently Processed Image
public void ImageDisplay(Image i)
{
pbMain.Image = null;
pbMain.Image = i;
}
public void ThreadWorkImg(Image i)
{
this.Invoke(new ImageDisplayDelegate(ImageDisplay),
new object[] {i});
}
//Increment Progress Bar
public void ProgressBarDisplay(ProgressBar pg, int max, int value)
{
//Dynamically set the Progress Bar properties
pg.Maximum = max;
pg.Value = value;
}
public void ThreadProgress(ProgressBar p, int max, int value)
{
this.Invoke(new ProgressBarDelegate(ProgressBarDisplay),
new object[] { p, max, value });
}
private void btnStart_Click(object sender, EventArgs e)
{
string IsValidResult = IsValid();
//If string is empty, Utility passed
if (IsValidResult == "")
{
Thread t = new Thread(new ThreadStart(ProcessFiles));
t.Start();
}
else
{
MessageBox.Show(IsValidResult);
}
}
public void ProcessFiles()
{
int count = 0;
ThreadWorkDelegate w = ThreadWork;
ImageDisplayDelegate im = ThreadWorkImg;
ProgressBarDelegate pb = ThreadProgress;
try
{
foreach (MyPictures mp in lstHold.Items)
{
try
{
if (hasThreadBeenStopped == false)
{
//Disable certain controls during process. We will use the generic
//MethodInvoker, which Represents a delegate that can execute any method
//in managed code that is declared void and takes no parameters.
//Using the MethodInvoker is good when simple delegates are needed. Ironically,
//this way of multi-thread delegation was used because the traditional way as used
//by the rest of the delegates in this method, was not working.
btnApply.Invoke(new MethodInvoker(delegate { btnApply.Enabled = false; }));
btnStart.Invoke(new MethodInvoker(delegate { btnStart.Enabled = false; }));
//Call delegate to show current picture being processed
im.BeginInvoke(mp.ImageClone(), null, null);
mp.ImageSave();
//Increment Count; Image has been processed
count++;
//Invoke Img Proceessed Output
w.BeginInvoke(lblImgProcessed, count.ToString() +
" of " + lstHold.Items.Count.ToString() + " processed",
null, null);
//Invoke File Process Output
w.BeginInvoke(lblFile, mp.NewImgPath, null, null);
//Invoke Progressbar output. Delegate is passed The count of images,
//which will be set as the progressbar max value. the 'count' variable is
//passed to determine the current value.
pb.BeginInvoke(progressBar1, lstHold.Items.Count, count, null, null);
}
else //Thread has been called to stop
{
MessageBox.Show("Image Processing Stopped: " + count + "of " +
lstHold.Items.Count + " processed");
//Enable controls after process
btnApply.Invoke(new MethodInvoker(delegate { btnApply.Enabled = true; }));
btnStart.Invoke(new MethodInvoker(delegate { btnStart.Enabled = true; }));
break;
}
}
catch (Exception ex)
{
MessageBox.Show("Error while processing pictures");
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error while attempting to execute pictures: " + ex.ToString());
}
finally
{
//Loop has ended:
//In finally statement, re-enable disabled controls
//Enable certain controls during process
btnApply.Invoke(new MethodInvoker(delegate { btnApply.Enabled = true; }));
btnStart.Invoke(new MethodInvoker(delegate { btnStart.Enabled = true; }));
//Reset class variables
hasThreadBeenStopped = false;
imageProcessingComplete = false;
}
}
private void btnContent_Click(object sender, EventArgs e)
{
string selection = null;
string[] files = null;
lstAll.Items.Clear();
contentBrowser.ShowDialog();
selection = contentBrowser.SelectedPath;
txtContentFolder.Text = selection;
if (selection != "" || selection != null)
{
try
{
files = System.IO.Directory.GetFiles(selection.Trim());
foreach (string file in files)
{
lstAll.Items.Add(file);
}
}
catch (Exception ex)
{
// MessageBox.Show(ex.ToString());
}
}
}
private void btnGo_Click(object sender, EventArgs e)
{
//Grab files from folder based on user input in the textbox.
string selection = txtContentFolder.Text.Trim();
string[] files = null;
lstAll.Items.Clear();
if (valid.IsNull(selection) == false || valid.IsEmpty(selection) == false)
{
try
{
files = System.IO.Directory.GetFiles(selection);
foreach (string file in files)
{
lstAll.Items.Add(file);
}
}
catch (Exception ex)
{
MessageBox.Show("Invalid Directory");
}
}
txtContentFolder.Text = selection;
}
private void btnDestination_Click(object sender, EventArgs e)
{
string selection = null;
destinationBrowser.ShowDialog();
selection = destinationBrowser.SelectedPath;
txtNewImgPath.Text = selection;
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
private void btnStop_Click(object sender, EventArgs e)
{
//Flag variable that the stop button has been called. This variable is checked
//conditionally when looping over each picture.
hasThreadBeenStopped = true;
}
public string IsValid()
{
StringBuilder sb = new StringBuilder("");
if (lstHold.Items.Count <= 0)
{
return "No items exist to process";
}
//Validate that there is a value in each field for every object in lstHold. All the fields will be
//validated. Note: If there is one invalid field, the rest do not need to be considered.
foreach (MyPictures mp in lstHold.Items)
{
if (mp.NewImgName == "")
{
sb.Append(mp.OldImgPath + ", ");
}
else if (mp.NewImgPath == "")
{
sb.Append(mp.OldImgPath + ", ");
}
else if (mp.NewImgFormat == null)
{
sb.Append(mp.OldImgPath + ", ");
}
else if (mp.NewWidth == 0)
{
sb.Append(mp.OldImgPath + ", ");
}
else if (mp.NewHeight == 0)
{
sb.Append(mp.OldImgPath + ", ");
}
}
//If the returned string is empty, the image is valid. The check for the listbox's count
//will return a string immediatly if false. Because of this, we know that the returning
//string at this level will either be empty (validation passed) or filled with image paths
//of images missing required values. If image is not valid, return this concatenated string of image paths
//that are missing values, and insert a prefixed string literal to this list.
if (sb.ToString() != "")
{
sb.Insert(0, "The following images are missing required values: ");
return sb.ToString();
}
else //String is empty and has passed validation
{
return sb.ToString();
}
}
private void btnMoveOne_Click(object sender, EventArgs e)
{
//Loop through All strings in the lstAll list box. Then use each picture path to convert
//each picture into their own class
foreach (string file in lstAll.SelectedItems)
{
//isImgExistFlag is a flag indicating wheter the image coming from lstAll already exists
//in lstHold. By default, the variable is false. It is set to true if an image does exist
//This variable must be re-created within the scope of the main foreach loop to ensure a proper
//reset of the variable for each image comparison.
bool isImgExistFlag = false;
try
{
Image img;
img = Image.FromFile(file);
MyPictures mp = new MyPictures(img,file);
//If lstHold contains no items, add the item with no validation check.
if (lstHold.Items.Count == 0)
{
lstHold.Items.Add(mp);
}
else
{
//Run through each object in the lstHold to determine if the newly created object
//already exists in list box lstHold.
for (int i = 0; i < lstHold.Items.Count; i++)
{
MyPictures p = (MyPictures)lstHold.Items[i];
//Unique objects will be identified by their Original Image Path, because
//this value will be unique
if (p.OldImgPath == mp.OldImgPath)
{
isImgExistFlag = true;
}
}
//If isImgExistFlag is false, the current Image object doesnt currently exist
//in list box. Therefore, add it to the list.
if (isImgExistFlag == false)
{
lstHold.Items.Add(mp);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
private void btnMoveAll_Click(object sender, EventArgs e)
{
//This event has the same functionality as btnMoveOne_Click, except the main foreach loop
//is based on all of lstAll's items, rather than just the selected items.
foreach (string file in lstAll.Items)
{
bool isImgExistFlag = false;
try
{
Image img;
img = Image.FromFile(file);
MyPictures mp = new MyPictures(img, file);
if (lstHold.Items.Count == 0)
{
lstHold.Items.Add(mp);
}
else
{
for (int i = 0; i < lstHold.Items.Count; i++)
{
MyPictures p = (MyPictures)lstHold.Items[i];
if (p.OldImgPath == mp.OldImgPath)
{
isImgExistFlag = true;
}
}
if (isImgExistFlag == false)
{
lstHold.Items.Add(mp);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
private void btnRemoveOne_Click(object sender, EventArgs e)
{
/*
Create a seperate List to populate:
This is necessary because if you explicitly remove an item from the listbox
you will get the following error:
"List that this enumerator is bound to has been modified. An enumerator can
only be used if the list does not change."
*/
//This variable will keep track of the first index processed.
int first_index = 0;
int count = 0;
List<MyPictures> TempMp = new List<MyPictures>();
if (lstHold.Items.Count >= 1)
{
try
{
foreach (MyPictures mp in lstHold.SelectedItems)
{
if (count == 0)
{
first_index = lstHold.SelectedIndex;
}
//Add objects to be removed
TempMp.Add(mp);
}
foreach (MyPictures mp2 in TempMp)
{
lstHold.Items.Remove(mp2);
}
}
catch (Exception ex)
{
//Hide Error: MessageBox.Show(ex.ToString());
}
//Select new item in list if possible, as long as there is a item in the list
if (lstHold.Items.Count >= 1)
{
//If the first_index variable = the amount of items in the list, the new selected index
//should be the first index -1. This is because the variable first_index would be the
//index of the now deleted item in the list. Therefore we must subtract the variable by 1
//before assigning it to the selected value. Otherwise, we'll be assigning a selected index that
//no longer exists.
//There is also a check to make sure there is more than one item in the list. Otherwise, we could
//potentially assign a selected index of -1.
if (first_index == lstHold.Items.Count && lstHold.Items.Count != 1)
{
lstHold.SelectedIndex = first_index - 1;
}
else if (lstHold.Items.Count == 1)
{
lstHold.SelectedIndex = 0;
}
else
{
lstHold.SelectedIndex = first_index;
}
}
else
{
ClearTextBoxes();
}
}
}
private void btnRemoveAll_Click(object sender, EventArgs e)
{
lstHold.Items.Clear();
ClearTextBoxes();
ImgPropertiesEnabled();
}
private void lstHold_SelectedIndexChanged(object sender, EventArgs e)
{
//This prevents trying to access a negative index. This can happen when a item is removed.
if (lstHold.SelectedIndex >= 0)
{
try
{
MyPictures mp = (MyPictures)lstHold.Items[lstHold.SelectedIndex];
txtOldName.Text = mp.OldImgName;
txtOldImgPath.Text = mp.OldImgPath;
txtOldImgFormat.Text = mp.OldImgFormat.ToString();
txtOldWidth.Text = mp.OldWidth.ToString();
txtOldHeight.Text = mp.OldHeight.ToString();
txtNewName.Text = mp.NewImgName;
cbFormat.SelectedItem = mp.NewImgFormat;
txtNewWidth.Text = mp.NewWidth.ToString();
txtNewHeight.Text = mp.NewHeight.ToString();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
//Call function to determine which controls should be enabled/disabled
ImgPropertiesEnabled();
}
private void btnApply_Click(object sender, EventArgs e)
{
//Reset color. It could be grey depending on if user changed default name.
txtNewName.ForeColor = Color.Black;
if (lstHold.SelectedIndex == -1)
{
MessageBox.Show("Picture not selected. Select picture to apply properties to.");
}
else if (lstHold.SelectedIndex >= 0)
{
MyPictures mp = (MyPictures)lstHold.Items[lstHold.SelectedIndex];
//User wants to apply a generated name to all pictures within the list
if (chkNewPicName.Checked == true)
{
int count = 0;
foreach (MyPictures pic in lstHold.Items)
{
pic.NewImgName = txtNewName.Text + count.ToString();
++count;
}
txtNewName.Text = mp.NewImgName;
}
//User wants to apply a custom name to this picture only
else
{
mp.NewImgName = txtNewName.Text;
}
//User wants to apply this path to all pictures within the list
if (chkNewPicPath.Checked == true)
{
foreach (MyPictures pic in lstHold.Items)
{
pic.NewImgPath = txtNewImgPath.Text;
}
txtNewImgPath.Text = mp.NewImgPath;
}
//User wants to apply this path to this picture only
else
{
mp.NewImgPath = txtNewImgPath.Text;
}
//User wants to apply this image format to all pictures within the list
if (chkNewPicFormat.Checked == true)
{
foreach (MyPictures pic in lstHold.Items)
{
pic.NewImgFormat = (ImageFormat)cbFormat.SelectedItem;
}
}
//User wants to apply this image format to this picture only
else
{
mp.NewImgFormat = (ImageFormat)cbFormat.SelectedItem;
}
//User wants to apply this size to all pictures
if (chkNewSize.Checked == true)
{
foreach (MyPictures pic in lstHold.Items)
{
pic.NewWidth = Convert.ToInt32(txtNewWidth.Text);
pic.NewHeight = Convert.ToInt32(txtNewHeight.Text);
}
txtNewWidth.Text = mp.NewWidth.ToString();
txtNewHeight.Text = mp.NewHeight.ToString();
}
//User wants to apply this size to this picture only
else
{
mp.NewWidth = Convert.ToInt32(txtNewWidth.Text);
mp.NewHeight = Convert.ToInt32(txtNewHeight.Text);
}
mp.NewImgName = txtNewName.Text;
mp.NewImgFormat = (ImageFormat)cbFormat.SelectedItem;
mp.NewWidth = Convert.ToInt32(txtNewWidth.Text);
mp.NewHeight = Convert.ToInt32(txtNewHeight.Text);
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (chkSelectAll.Checked)
{
chkNewPicName.Checked = true;
chkNewPicPath.Checked = true;
chkNewPicFormat.Checked = true;
chkNewSize.Checked = true;
}
else
{
chkNewPicName.Checked = false;
chkNewPicPath.Checked = false;
chkNewPicFormat.Checked = false;
chkNewSize.Checked = false;
}
}
private void previewToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBox.Show("hi there!");
}
private void btnPreview_Click(object sender, EventArgs e)
{
try
{
if (lstHold.Items.Count <= 0)
{
MessageBox.Show("No pictures are available to preview");
}
else if (lstHold.SelectedItem == null)
{
MessageBox.Show("No picture is selected to preview");
}
else
{
MyPictures mp = (MyPictures)lstHold.SelectedItem;
//Bitmap bmp = new Bitmap(mp.OldImgPath);
Form2 frm = new Form2(mp);
frm.Show();
}
}
catch (Exception ex)
{
MessageBox.Show("An Error has occured:\n " + ex.ToString());
}
}
public void ImgPropertiesEnabled()
{
//Enable Image properties when an image is selected
if (lstHold.SelectedIndex >= 0)
{
gbCheckAll.Enabled = true;
gbImgProperties.Enabled = true;
}
else
{
//Disable Image properties when an image is not selected
gbCheckAll.Enabled = false;
gbImgProperties.Enabled = false;
}
//Preview buttons enablement will depend on the same conditions
btnPreview.Enabled = gbImgProperties.Enabled;
}
public void ClearTextBoxes()
{
txtNewImgPath.Text = "";
txtNewName.Text = "";
txtNewHeight.Text = Convert.ToString(0);
txtNewWidth.Text = Convert.ToString(0);
cbFormat.SelectedItem = null;
chkSelectAll.Checked = false;
}
}
Having scanned through the code, yes it is eleborate... maybe a little to much ;)
One thing that i noticed was your naming conventions. Even though it does not change anything in runtime it does make an API/code-maintenance easier.
So, instead of having an IPicture, i would make it something like `IResizableImage´ (reading your spec, thats what it is. Not just a picture, but a resizable one)
Instead of ´ReturnImage()´ i would use something like ´Scale()´. 'ImageSave()' to 'Save()'
Your code will start to read (Which added symantical information by naming convention)
IResizableImage myImg = new ResizableImage( orignalBitmap );
Image rescaledImg = myImg.Scale( "new path", 320,240 );
resccaledImg.Save();
instead of:
IPicture myImg = new MyPictures();
Image rescaled = myImg.ReturnImage( "newpath", 320, 240 );
rescaledImg.ImageSave();
So, Generally classes are nouns, methods are verbs, adjetives are properties/fields. Try to minimize duplication or redancy. "ImageSave" is a method on your Image. Isn't "Image.Save()" clearer than "Image.ImageSave()"?
Just some of my thoughts;
In coding guidelines there is no absolute right or wrong.
Think of being another person when USING the API versus WRITING the API. Jump out of the box of "i know what it does" and imagine being a user never having seen this API before. Does it feel natural and easy accesible?
Hope this helps,
Here are some improvements for you code and design. This tips are not all OO related but you should be aware that good design is not just OO design.
1.Avoid commenting what is obvious.
//Declare variables to hold values that have been determined
private int _OldWidth;
This comment is superfluous because any programmers will understand that is a declaration.
2.Avoid giving wrong name. For example the class "MyPictures" is not really correct because:
Is holds just one picture, while the name suggests many pictures.
It contains "My" which, in my opinion is not correct since if I read your code is not my class. It is yours ;)
3.Avoid concatenating strings. Use string.Format or, for paths, Path.Combine
bmp.Save(_NewImgPath + #"\" + _NewImgName + "." + _NewImgFormat.ToString().ToLower(), _NewImgFormat);
4.Keep methods short. It is hard to keep all methods to 5 lines of code but 30 lines (if my count is correct - without comments and empty lines) for ProcessFiles is a little bit too much.
5.Don't use design elements just because you want to have them. I see no reason to use the interface in your code. In your case it just increases the complexity of your code. Even more, you haven't used it(!!!). You just implemented it and that's all. Use interfaces when you have multiple types that share common functionality (the ones in interface) and you want to treat them all similar without being aware of the actual implementation.
interface IImage
{
void DrawLine(Point startPoint, Point endPoint);
}
class MonochromeImage:IImage
{
void DrawLine(Point startPoint, Point endPoint)
{
//Draw a monochrome line on images with one channel
}
}
class ColorImage:IImage
{
void DrawLine(Point startPoint, Point endPoint)
{
//Draw a red line on images with three channels
}
}
...
void DrawLineOnImage()
{
List<IImage> images = new List<IImage>();
images.Add(new ColorImage());
images.Add(new MonochromeImage());
//I am not aware of what kind of images actually have
//all it matters is to have a draw line method
foreach(IImage image in images)
{
image.DrawLine(p1,p2)
}
}
6.As others already mentioned, try to separate the presentation (graphical user interface - GUI) from the logic. Make it in such a way that you can replace the GUI without changing logic code.
7.Make single responsibility functions. btnMoveOne_Click has more than one responsibility: it checks if file exists and it handles elements on user interface.
8.You image class is coupled to the file system. What happens if I want to store images created in memory? What is the path then? Here is where you can improve the design of the class. Make it in such a way it doesn't matter if files are from disk (HINT: in a FileStream) or from memory (HINT: in a MemoryStream) or any other place.
That's all for now. Hope this information will help you.
To achieve good design you need to apply TDD (Test Driven Design).
You will soon find then testability requires separating the project to layers, such as presentation and business logic.
Start covering your project with tests, and you won't believe how fast you will find design inconsistences with it.
Things will just stand up and scream: "No way you will test me!"
The worst anemy nere is the code buried in the WinForms.
What you can do is making a view "humble". http://codebetter.com/blogs/jeremy.miller/archive/2007/05/23/build-your-own-cab-part-2-the-humble-dialog-box.aspx
As for the project samples, you have to look at architectural patterns, not the OOP samples.
The keywords you will be lookign for are MVC, MVP, MVVM.
Well, here's what I'd do. It's probably different than what many people would do, but I think it's a pretty good, flexible design.
public abstract class AbstractConverter : IImageHandler
{
public AbstractConverter(IImageHandler nextHandler)
{
output = nextHandler;
}
public void HandleImage(Bitmap b)
{
var outBitmap = Convert(b);
output.HandleImage(outBitmap);
}
protected abstract Bitmap Convert(Bitmap input);
private IImageHandler output;
}
public interface IImageHandler
{
void HandleImage(Bitmap b);
}
Now, the rest of your app is:
Creating implementations of AbstractConverter to handle the individual transformations you want
Creating something that can build and wire converters together
Getting the initial Bitmap, and writing the final result out.

Categories

Resources