I wrote some code testing out a BankAccount and I finished it (for the most part).
The purpose was me attempting to learn how to throw and try/catch exceptions. For the most part I learned quite a bit. I realize this wasn't an "exceptional" case and if/else statements would have been more appropriate. I am just trying to learn exception handling.
I am trying to write the exception classes to extend a class to include a new message detailing the error being sent when an exception is thrown.
I read up on what a super class is, or base class in c#, and tried it. It compiles and looks right but I don't know what I am doing wrong. Can someone explain to me why my message in my throw new NegativeDepositException will not show.
What I would like for it to do, is to throw a message instead of me putting a string in MessageBox.Show.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public class NegativeBalanceException : Exception { }
public class NegativeWithdrawException : Exception { }
public class NegativeDepositException : Exception
{
public NegativeDepositException() { } // this doesn't do what I would like for it to do.
public NegativeDepositException(string message)
: base(message) { }
}
public Form1()
{
InitializeComponent();
}
public class BankAccount
{
decimal amount = 300.00m;
// Declare Delegate Type Object
public delegate void BankDelegate(decimal oldBalance, decimal newBalance);
// Create Delegate Type Events
public event BankDelegate OnDeposit;
public event BankDelegate OnWithdraw;
public void Deposit(decimal a)
{
if (a < 0)
throw new NegativeDepositException(); //"type some message here instead of messagebox below");
OnDeposit(this.amount, this.amount + a);
this.amount += a;
}
public void Withdraw(decimal a)
{
if (a < 0)
throw new NegativeWithdrawException();
OnWithdraw(this.amount, this.amount - a);
this.amount -= a;
if (this.amount < 0)
throw new NegativeBalanceException();
}
}
// this is my bank class variable...
BankAccount account = null;
private void Form1_Load(object sender, EventArgs e)
{
account = new BankAccount();
account.OnDeposit += new BankAccount.BankDelegate(account_OnDeposit);
account.OnWithdraw += new BankAccount.BankDelegate(account_OnWithdraw);
}
private void btnDeposit_Click(object sender, EventArgs e)
{
{
var amount = Convert.ToDecimal(textBox1.Text);
try { account.Deposit(amount); }
catch (NegativeDepositException)
{
MessageBox.Show("Cannot deposit negative amounts");
}
}
}
private void btnWIthdraw_Click(object sender, EventArgs e)
{
var amount = Convert.ToDecimal(textBox1.Text);
try { account.Withdraw(amount); }
catch (NegativeBalanceException)
{
MessageBox.Show("Cannot withdraw money that isn't there");
}
catch (NegativeWithdrawException)
{
MessageBox.Show("Cannot withdraw negative money");
}
}
void account_OnDeposit(decimal oldBalance, decimal newBalance)
{
label4.Text = oldBalance.ToString();
label5.Text = newBalance.ToString();
}
void account_OnWithdraw(decimal oldBalance, decimal newBalance)
{
label4.Text = oldBalance.ToString();
label5.Text = newBalance.ToString();
}
}
Maybe I'm misunderstanding the problem, but you're not passing the message when throwing the Exception;
It should be:
if (a < 0)
throw new NegativeDepositException("Cannot deposit negative amounts");
and when catching it you need to call the Message property of the Exception (which is in the Exception base class)
catch (NegativeWithdrawException exception)
{
MessageBox.Show(exception.Message);
}
Related
I have a winform application(WindowsFormsApplication2) that loads a dll using appdomain. In the winform, I have suscribed to 'TestEvent' implemented in ClassLibrary.dll. If the TestEvent returns an argument,how can I capture the returned argument in the event handler HandleEvent implemented in winform?
This is the winform application codes.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Diagnostics;
using ClassLibrary1;
using static System.Net.Mime.MediaTypeNames;
namespace WindowsFormsApplication2
{
[Serializable]
public partial class Form1 : Form
{
void HandleEvent(object sender, EventArgs e)
{
Debug.WriteLine("HandleEvent called");
}
string DLL = #"..\ConsoleApplication1\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
public Form1()
{
InitializeComponent();
Loader.Call( DLL, "ClassLibrary1.Class1", "RaiseEvent", HandleEvent, DateTime.Now.ToShortDateString());
}
private void button1_Click(object sender, EventArgs e)
{
Application.Restart();
Application.Run(new Form1());
this.Close();
}
}
public class Loader : MarshalByRefObject
{
AppDomain ad = AppDomain.CreateDomain("Test");
object CallInternal(string dll, string typename, string method, EventHandler handler, object[] parameters)
{
Assembly a = Assembly.LoadFile(dll);
object o = a.CreateInstance(typename);
Type t = o.GetType();
// Subscribe to the event
EventInfo eventInfo = t.GetEvent("TestEvent");
eventInfo.AddEventHandler(o, handler);
MethodInfo m = t.GetMethod(method);
return m.Invoke(o, parameters);
}
public static object Call( string dll, string typename, string method, EventHandler handler, params object[] parameters)
{
Loader ld = (Loader)ad.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(Loader).FullName);
object result = ld.CallInternal(dll, typename, method, handler, parameters);
AppDomain.Unload(ad);
return result;
}
}
}
This is the ClassLibrary1.dll codes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ClassLibrary1
{
[Serializable]
public class Class1
{
public event EventHandler TestEvent;
public int RaiseEvent(string msg)
{
try
{
TestEvent(this, EventArgs.Empty);
}
catch (Exception ex)
{
Console.WriteLine("the exception is: " + ex.ToString());
if (ex.InnerException != null)
{
Console.WriteLine("the inner exception is: " + ex.InnerException.Message.ToString());
}
}
return 2;
}
}
}
You can use something like this:
void HandleEvent(object sender, EventArgs e)
{
Debug.WriteLine("HandleEvent called");
}
string DLL = #"..\ConsoleApplication1\ClassLibrary1\bin\Debug\ClassLibrary1.dll";
//You can can create new appDomain: var dom = AppDomain.CreateDomain("domain name");//If you really need it!
var dom = AppDomain.CurrentDomain;
var assembly = dom.Load(DLL);
var type = assembly.GetTypes().FirstOrDefault(p => p.FullName == "ClassLibrary1.Class1");
var inst = Activator.CreateInstance(type);
var eventInfo = type.GetEvent("event name");
var handleEventMethod = GetType().GetMethod("HandleEvent");
Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, type, handleEventMethod);
eventInfo.AddEventHandler(inst, handler);
var method = type.GetMethod("RaiseEvent");
var resultData = (int)method.Invoke(inst, new object[]{"Expecting argument of your method"});
How do I use reflection to call a generic method?
Invoke a method of anonymous class
Subscribe to an event with Reflection
Invoke static methods
As I understand it, your want your HandleEvent method to be able to consume or produce some custom value that we'll call Argument. In this case, the outcome can be achieved by inheriting EventArgs for a custom event.
public class Class1
{
public event ValueReceivedEventHandler TestEvent;
public int RaiseEvent(string msg)
{
try
{
TestEvent?.Invoke(
this,
new ValueReceivedEventArgs{ Argument = msg } );
}
catch (Exception ex)
{
Console.WriteLine("the exception is: " + ex.ToString());
if (ex.InnerException != null)
{
Console.WriteLine("the inner exception is: " + ex.InnerException.Message.ToString());
}
}
return 2;
}
}
Custom event args class
public delegate void ValueReceivedEventHandler(Object sender, ValueReceivedEventArgs e);
public class ValueReceivedEventArgs : EventArgs
{
public object Argument { get; set; }
}
Then you can handle event in one of these ways:
void HandleEvent(object sender, EventArgs e)
{
if(e is ValueReceivedEventArgs ePlus)
{
Debug.WriteLine(ePlus.Argument.ToString());
}
}
void HandleEvent(object sender, ValueReceivedEventArgs e)
{
Debug.WriteLine(e.Argument.ToString());
}
Please leave me a comment if this is way off from what you're trying to achieve.
This question already has answers here:
Model-View-Presenter in WinForms
(3 answers)
How to Structure a C# WinForms Model-View-Presenter (Passive View) Program?
(2 answers)
Closed 2 years ago.
I have a predicament! I don't know where I should put my variables in a C# Windows Forms Application. For example, in my WFA project I have a list of my own datatype object called "Item" and at the moment I've had to make it global, however, I understand global isn't the best of practises. Because I come from a C++ background I'd always create my variables in int main() and pass them to functions where needed. I honestly can't find an answer anywhere. Many thanks to anyone who can assist me.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Sales_Calculator
{
public partial class Form1 : Form
{
//Should my list<Item> go here or is there a better place for it?
List<Item> objItems = new List<Item>();
public Form1()
{
InitializeComponent();
}
public class Item
{
private string m_sItemName;
private double m_dItemPrice;
private int m_iQuantitySold;
public Item(string sItemName, double dItemPrice, int iQuantitySold)
{
m_sItemName = sItemName;
m_dItemPrice = dItemPrice;
m_iQuantitySold = iQuantitySold;
}
public string Name
{
get { return m_sItemName; }
set { m_sItemName = value; }
}
public int QuantitySold
{
get { return m_iQuantitySold; }
set { m_iQuantitySold = value; }
}
public double Price
{
get { return m_dItemPrice; }
set { m_dItemPrice = value; }
}
}
bool DoesItemNameExist(string sName)
{
return (objItems.Find(delegate (Item objItem){return sName == objItem.Name;}) != null) ? true : false;
}
private Item HighestSellingItem()
{
int MaxQuantitySold = objItems.Max(objItem => objItem.QuantitySold);
return objItems.Find(delegate (Item objItem) {
return objItem.QuantitySold == MaxQuantitySold;
});
}
private double Average()
{
int iTotal = 0;
objItems.ForEach(delegate (Item objItem)
{
iTotal += objItem.QuantitySold;
});
double dAverage = iTotal / objItems.Count;
return dAverage;
}
private void label1_Click(object sender, EventArgs e)
{
}
private void AddToDataGridView(object sender)
{
dgvItems.Rows.Add(objItems.Count, objItems.Last().Name, objItems.Last().QuantitySold, objItems.Last().Price);
}
void ClearTextBoxes()
{
txtItemName.Clear();
txtItemPrice.Clear();
txtQuantitySold.Clear();
}
void AddItemToList()
{
string sItemName = "";
if (DoesItemNameExist(sItemName = Convert.ToString(txtItemName.Text)))
{
MessageBox.Show("You can't have that name, it already exists!");
txtItemName.Clear();
return;
}
double dItemPrice = Convert.ToDouble(txtItemPrice.Text);
int iQuantitySold = Convert.ToInt32(txtQuantitySold.Text);
Item objItem = new Item(sItemName, dItemPrice, iQuantitySold);
objItems.Add(objItem);
MessageBox.Show("You have added an item!");
ClearTextBoxes();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
AddItemToList();
AddToDataGridView(sender);
lblAverageQuantitySold.Text = Average().ToString();
lblExistingItems.Text = objItems.Count.ToString();
}
catch (Exception ex)
{
MessageBox.Show("You have inputted incorrectly!", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
dvgHighestSellingItem.Rows.Clear();
dvgHighestSellingItem.Rows.Add(HighestSellingItem().Name);
}
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
}
}
}
My goal is to throw a NegativeBalanceException if there is an attempt to take the balance below zero. Just like if this was a real bank account. I had if and else statements, but butchered them up and am trying to learn try, catch, and throw statements (was reading on finally as well but I don't think that applies here). Anyway, I set up where a catch statement works if I just hit the deposit button without typing anything. But, I don't understand where it is wanting me to implement it in for taking it below zero. Is it in my deposit method? Or is it in the actual btn_deposit? Also, what is the purpose of using try catch statements over if else statements? I am new to programming and am just trying to learn.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public class BankAccount
{
decimal amount = 300.00m;
// Declare Delegate Type Object
public delegate void BankDelegate(decimal oldBalance, decimal newBalance);
// Create Delegate Type Events
public event BankDelegate OnDeposit;
public event BankDelegate OnWithdraw;
public void Deposit(decimal a)
{
{
if (a > 0)
{
OnDeposit(this.amount, this.amount + a);
this.amount += a;
}
else
{
MessageBox.Show("No!");
}
}
}
public void Withdraw(decimal a)
{
// Fire OnWithdraw Event and pass old and new balance amount
OnWithdraw(this.amount, this.amount - a);
this.amount -= a;
}
}
// Declare BankAccount class variable
BankAccount account = null;
private void Form1_Load(object sender, EventArgs e)
{
account = new BankAccount();
// Attach Event Handlers with Events
account.OnDeposit += new BankAccount.BankDelegate(account_OnDeposit);
account.OnWithdraw += new BankAccount.BankDelegate(account_OnWithdraw);
}
private void btnDeposit_Click(object sender, EventArgs e)
{
try
{
account.Deposit(Convert.ToDecimal(textBox1.Text));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnWIthdraw_Click(object sender, EventArgs e)
{
account.Withdraw(Convert.ToDecimal(textBox1.Text));
}
void account_OnDeposit(decimal oldBalance, decimal newBalance)
{
label4.Text = oldBalance.ToString();
label5.Text = newBalance.ToString();
}
void account_OnWithdraw(decimal oldBalance, decimal newBalance)
{
label4.Text = oldBalance.ToString();
label5.Text = newBalance.ToString();
}
}
}
You should only throw exceptions for exceptional circumstances. Overdrawn accounts are not an exceptional circumstance.. but depositing a negative amount is.
Therefore, I would do something like this for the Deposit method:
public void Deposit(decimal a)
{
if (a < 1)
throw new NegativeDepositException("You cannot deposit this amount");
OnDeposit(this.amount, this.amount + a);
this.amount += a;
}
BUT.
You should be validating this prior to entering the method. That way, the exception should never be called - unless you call Deposit from another method without the check - which would be exceptional.
private void btnDeposit_Click(object sender, EventArgs e)
{
// try..catch removed. This will now crash if you forget to check the value.
var amount = Convert.ToDecimal(textBox1.Text);
if (amount < 1)
MessageBox.Show("You cannot deposit this amount");
else
account.Deposit(amount);
}
Also, I would change Withdraw to return bool, since overdrawing is not really an exceptional circumstance:
public bool Withdraw(decimal a)
{
if (this.amount - a >= 0)
{
// Fire OnWithdraw Event and pass old and new balance amount
OnWithdraw(this.amount, this.amount - a);
this.amount -= a;
return true; // successful
}
else
{
return false; // unsuccessful
}
}
Then when you call it:
private void btnWIthdraw_Click(object sender, EventArgs e)
{
if (!account.Withdraw(Convert.ToDecimal(textBox1.Text)))
{
MessageBox.Show("Insufficient funds");
}
}
EDIT:
In response to your comment. You must create your own exception class if you want it to be named a specific way (or if you want it to extend the functionality of a normal Exception). In my example, you would have to create this:
public class NegativeDepositException : Exception {
}
Thats it. It gets everything it needs from Exception.. for now.
I have 2 classes: MyForm and Database
In MyForm I have a method to change a label text to show error:
public void printError(string text){
label1.Text = text;
}
My Database class needs to access that method too, so I make it static:
public static void printError(MyForm form, string text){
form.label1.Text = text;
}
Now the problem is, how do I call that method from Database class?
This question I found said that I need to pass MyForm into Database's contructor like this:
class MyForm : Form{
Database db;
public Form(){
db = new Database(this);
}
}
class Database{
MyForm form;
public Database(MyForm f){
form = f;
}
...
//then I can access the printError like this
MyForm.printError(form, "You got error");
}
I tried that and it freezes the form. Any other solution?
Thanks
Here is a very simple example of how you can achieve this without your data layer knowing about your UI:
class MyForm : Form
{
Database db;
public Form()
{
db = new Database(this);
}
public void DoSomething()
{
var errors = db.Login("", "");
if (errors.Any())
label1.Text = errors.First(); // Or you can display all all of them
}
}
class Database
{
public List<string> Login(string username, string password)
{
var errors = new List<string>();
if (string.IsNullOrEmpty(username))
errors.Add("Username is required");
if (string.IsNullOrEmpty(password))
errors.Add("Password is required");
[...]
return errors;
}
}
Like #Matthew Ferreira and others have stated the design is not idea, but here's something to get you started.
class MyForm : Form
{
public void SomeMethod()
{
var dataAccess = new Repository();
dataAccess.ExecuteQuery();
if (dataAccess.Exceptions.Any())
{
// display your error messages
form.label1.Text = dataAccess.Exceptions.Select(x => x.ToString());
}
}
}
class Repository
{
private readonly HashSet<Exception> _exceptions = new HashSet<Exception>();
public IEnumerable<Exception> Exceptions
{
get { return _exceptions; }
}
public int ExecuteQuery()
{
var numberOfRecordsAffected = 0;
try
{
// do something
}
catch (Exception ex)
{
// normall catching exceptions is a bad idea
// and you should really catch the exception at the
// layer best equiped to deal with it
_exceptions.Add(ex);
}
// but, for the purpose of this example we might want to add some logic to try the query on another database ????
try
{
// do something
}
catch (Exception ex)
{
_exceptions.Add(ex);
}
return numberOfRecordsAffected;
}
}
You need to look up "seperation of concerns". Its really bad to mix your UI code with you Database Access Layer (DAL). Better to bind the UI to business objects that are populated via a DAL.
To let the UI know about an error you could simply use a delegate.
namespace OperationErrorDelegate
{
public delegate void OperationErrorHandler(Exception ex);
public class DAL
{
public event OperationErrorHandler ReportError;
public void DoDALOperationThatCausesError()
{
try
{
int i = 1;
int j = 0;
int k = i/j;
}
catch (Exception ex)
{
ReportError(ex);
}
}
}
}
Add this code to the form:
using System ;
using System.Windows.Forms;
namespace OperationErrorDelegate
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
DAL DAL = new DAL();
DAL.ReportError += new OperationErrorHandler(DAL_OperationErrorProgress);
DAL.DoDALOperationThatCausesError();
}
private void DAL_OperationErrorProgress(Exception ex)
{
label1.Text = ex.Message;
}
}
}
Assuming the OP's requirement is to display an error message in a label, when the credentials are wrong:
private void btn_login_Click(object sender, EventArgs e)
{
MySqlConnection con = new MySqlConnection("server=localhost;uid=root;password=abc;database=mydb");
MySqlCommand cmd = new MySqlCommand("select * from emp where name='" + textBox1.Text + "'and pwd='" + textBox2.Text + "'",con);
con.Open();
MySqlDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{ //successful
//navigate to next page or whatever you want
}
else
Label1.Text("Invalid userid or password");
con.Close();
}
And if you need error message for wrong data type (the user input string but the database column is Integer), then use validations at client side. You dont need to do it at backend, since that will be a burden.
You can use regular expressions for that in the button_click itself.
Specifically looking at the arrive method in the Customer class. I am using a for loop to create instances of the customer class, and when I try to write out their arrival times to a textBox (Just for testing purposes) the text box does not update. Why is this?
This is just a small simulation project for my Computing class. It is in its early stages, and is probably wrong in a lot of places!
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace QueueSimulation
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void Form1_Load(object sender, EventArgs e)
{
MessageBox.Show("The form has loaded");
}
public void goButton_Click(object sender, EventArgs e)
{
Initialisers init = new Initialisers();
Customer customer = new Customer();
customer.Arrive();
}
private void stopButton_Click(object sender, EventArgs e)
{
// put code here to break out of the program
}
}
public class Customer : Initialisers
{
int waitingTime;
int arrivalTime;
int arrivalInterval;
Initialisers init = new Initialisers();
public void Arrive()
{
Customer[] customer = new Customer[1000];
int counter = 0;
for (int i = 1; i <= 10; i++)
{
customer[i] = new Customer();
customer[i].TimeArrived();
displayArrival.Text = displayArrival.Text + customer[i].TimeArrived().ToString();
// Implement something to either show the time in the queue if needed
Thread.Sleep(init.CustomerArriveTime*100);
}
MessageBox.Show("All of the customers have arrived");
}
public string TimeArrived()
{
return Convert.ToString(DateTime.Now);
}
public void Leave()
{
}
public void GetServed()
{
}
}
public class Server
{
bool servingStatus;
int servingTime;
public void Serve()
{
}
}
public class Initialisers : Form1
{
private int cust_no = 3;
public int CustomerArriveTime
{
get
{
return cust_no;
}
set
{
cust_no = value;
}
}
private int s_time = 4;
public int serveTime
{
get
{
return s_time;
}
set
{
s_time = value;
}
}
}
}
Pass to the Arrive the instance of the textbox object created on your Form1.
public void Arrive(TextBox displayArrival)
Why are you inheriting the Form1 in Initialiserz? It's better to pass the reference to Form1 instead of inheritance in this case.
This seems overly complex. Try to model the real world. What is Initialisers, and why do you have an inheritance tree: Customer > Initialisers > Form1?
You're customer is writing to its own TextBox, instead of the TextBox you're looking at (the one from the Form that is visible).
Why not have a method Arrive that sets a private field to DateTime.Now. Then, ask the Customer its TimeArrived, which returns this field. In your Form, call these methods as much as needed in your loop.
This also seperaties command (Arrive) from query (TimeArrived) + keeps your inheritance more logical.
You might not even need Initialisers anymore. And don't let Customer inherit from Form, because a Customer isn't a Form.
I think there is more of a design issue here, you are creating instances of customer inside customer.
Your customer Arrive method should probably be a function inside the another class, like below, customer should just define what a customer is. Processing them should be handled by a different class.
class Customer
{
int waitingTime;
int arrivalTime;
int arrivalInterval;
// etc...
}
class ProcessCustomers
{
pubic void Arrive()
{
// etc...
}
}
public void goButton_Click(object sender, EventArgs e)
{
Initialisers init = new Initialisers();
ProcessCustomers CustomerQueue = new ProcessCustomers();
CustomerQueue .Arrive();
}
But for the text box issue you will have to expose a property in the form class and set it like that,
string ArrivalTime
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}