Building a C# application - Cash register - Part 1
In part 1 we built a virtual 10-key keyboard. While doing so we learned a few things about planning ahead, inheritance and events.
Our virtual RegisterKeypad (which inherited from the more generic GenericKeypad class) had one purpose in life: To raise events in response to user actions.
Here in part two we are going to make a GenericDisplay class, then inherit from it to make a more specific RegisterDisplay control. It too will have only one purpose in life: To display values - but to do so in a formatted in interesting way for use on our cash register.
We aren't going to get into deeper topics of how to graphically draw cool LED looking displays etc. We are going to use fonts and labels so we can keep on topic. There is nothing stopping you from finding some nice LED font to use, if that is a look you like.
Let's stop for a moment and THINK before we code. Radical for some. We could quickly through together some label in a box and call it done - then every time we use it our main program has to do all the tedious work of formatting the text: Or we could decide to spend a little extra time building this control so it handles all the boring tasks on it's own, and our main program only has to send it a value. Hmmmm... Work smarter, not harder. Make our code and controls re-usable. Iron out all the bugs in the small bit of code that makes up the control and know it is *DONE* so we don't have to re-create it over and over and over and over and over. ?? Which way should we go?
In part one we made a Display project. It has a UserControl1.cs user control. We need to rename that to GenericDisplay just like we did with the GenericKeypad in the Keypad project.
Let's give it some methods that seem reasonable for all kinds of currency displays we would want to use in our cash register. What kinds of things would we need to do *to* a display, and what kinds of things would we need to get *from* a display? It's value for one. There are really two kinds of values that a display might have: Numeric and string. A cash register might display "10.24", but it could also display the item like "Nails, box" and when the transaction is done, it might say "Thank you for shopping here" - and when logged out it might read "register closed". So we should be able to send it either a numeric or string value, as well a get from it a bool telling us whether or not the current display is a numeric value just so our main program doesn't have to decide each and every time. Right now your code should still look like this
namespace Display.cs
{
public partial class GenericDisplay : UserControl
{
public GenericDisplay()
{
InitializeComponent();
}
}
}
We're going to need to methods for setting the text on the display. One for when a string is wanted, the other for when a number is wanted.
public virtual void SetDisplay(string TheText)
{
//TODO: Set the display to the passed in string value
}
public virtual void SetDisplay(decimal TheAmount)
{
//TODO: Format, then display the passed in numeric value
}
Let's break down the signature of one of these methods.
public virtual void SetDisplay(string TheText)
public - This means all other classes can see this method
virtual - This means that inherited classes can make a new method using this name to replace it's purpose. We'll see the use and power of this later.
void - This method does not return any result
SetDisplay - The name of this method
(string TheText) - The arguments being passed into this method when called. We expect to receive a string and we will refer to it as TheText.
The two comments have special meanings to Visual Studio. //TODO: is a special comment that Visual Studio looks for to maintain a ToDo list for you so you don't forget what you need to finish.

Notice the TaskList is already populated with these two comments, including the name of the file they are in and even the line numbers. When viewing the Task List all you have to do is double-click one of these lines to be whisked away to that comment. Not bad huh? Magical bookmarks within code so you can stub out your thinking with methods while working out the logic flow. Then come back and fill in the meat of the method later.
Here's how these two methods are going to work. If the programmer sends a string to the Display class then it means he knows just what he wants it to display so we will trust it. If he sends a number, then we wants it formatted to match the property currency expectations.
Windows already has preferences in the operating system for globalization and setting the way the user wants numbers to be formatted and displayed.


Since I'm lazy and don't want to re-invent the wheel, I'm just going to ask the .NET framework to do the work for me.
I'm not going to go deep into the syntax of the C# language here. The MSDN and about 500 books do that just fine. But briefly we are using the built-in formating feature to pass argument use TheAmount as argument 0, and 'C' as the format we want which is currency. Windows will then look up the currency settings and apply all the rules for us turning 1000000.0256 into "$ 1,000,000.03" if you are using English/United States.
While we are here let's discus a couple other things. Did you notice that we have two methods with identical names? We have SetDisplay that takes an argument of a string, and we have SetDisplay that takes an argument of a decimal, but otherwise they are the same. This is call "overloading" and is completely legal, and even desirable when used in certain circumstances, like this one, where the functions have nearly identical use. Notice how Intellisense was kind enough to give us a popup menu showing us both of our overloaded methods along with their argument types? My favorite example of this is the MessageBox.Show()
There are a LOT of overrides, giving you the capability of sending just one string, two strings, strings plus picking the buttons, string plus picking the buttons and the icon... specifying the parent window as the owner of this messagebox and so on and so on.
We are doing the same here, but on a smaller scale. We are giving future coders that use our GenericDisplay control and it's inheritance-derived offspring, two ways of setting what will be shown on this display. Guess what? We have just completed the TODO for code line 24. The decimal form of this overloaded method now knows what to do. We can delete line 24 because that task is done. In doing so you will see it disappear from our Task List.
It is now time to derive a simple inherited control from our GenericDisplay. Right-click on the Display.cs project. Choose "Add". Choose UserControl. Name the new UserControl "SimpleDisplay" You should now have a simple grey UserControl. Right-click anywhere on the grey and choose "View Code". Instead of this deriving from .NET's UserControl class we want it to derive from our class, so change line 11 to look like this
public partial class SimpleDisplay : GenericDisplayGo back to the designer, make the gray area bigger so you have some room to work, drag a text box on to the gray area. Now in the Properties pallet change the dock to fill

and the 'multiline' to true. You now have a simple display that uses a self-resizing textbox - that inherits our SetDisplay methods from GenericDisplay.
Like my first girlfriend, looks but no brains. Let's give this control a method so it will now how to do something. The GenericDisplay had a virtual method of SetDisplay(string TheText) that we never got around to giving any code. That was because the generic display had no way to show it. But this SimpleDisplay does: It has a textbox. So we are going to override this method from the base class, and do something with it here.
public override void SetDisplay(string TheText)
{
textBox1.Text = TheText;
}
From the debug menu choose "Start Debugging" or press the F5 key. Your Form1 will run and not be any different than before. Close the form. Just like the keypad control, this was just to compile the code and have the Visual Studio toolbox add these two Display controls.

Let's put it to use in our Form1 testing area. Drag a new SimpleDisplay to the form. I dropped mine just above our earlier textbox and made it equally wide.

Go to the code for Form1. Right now we react to the KeyPad events like this
private void registerKeypad1_ButtonPressed(object sender, KeyPressEventArgs e)
{
textBox1.Text += e.KeyChar;
}
By adding one line, our new SimpleDisplay will also get updated
private void registerKeypad1_ButtonPressed(object sender, KeyPressEventArgs e)
{
textBox1.Text += e.KeyChar;
[B][I]simpleDisplay1.SetDisplay(textBox1.Text);[/I][/B]
}
F5 Start Debugging this now. Click some numbers. You should have both boxes update to the same text. Not really exciting, huh? Lets try using that other SetDisplay method: The one that takes a number and automatically formats the text to match the system currency preferences.
private void registerKeypad1_ButtonPressed(object sender, KeyPressEventArgs e)
{
textBox1.Text += e.KeyChar;
//simpleDisplay1.SetDisplay(textBox1.Text);
[I][B] simpleDisplay1.SetDisplay(Convert.ToDecimal(textBox1.Text));[/B][/I]
}
Notice we commented our earlier call, and added this new call that will take the text we have, convert it to a decimal, and use it as the argument for SetDisplay. Visual Studio will route it to SetDisplay(decimal TheAmount) instead of SetDisplay(string TheText) because we are sending a decimal. It does the routing work for us based on the signature of the call.F5 Start Debugging again. Click on a few numbers. Wow! Look at that. The textbox still shows text, but the SimpleDisplay receives the number, formats the value to a currency string, and shows that instead

Our main application didn't have to do the work: The SimpleDisplay control did. And if your user changes preferences in the operating system with respect to how currency should be displayed, you have NO WORK to do in this control. Niiiiiiiiiiiiiiiiice.
But this doesn't look very techy or digital or like what we expect to see from a cash register readout. What if we change the font to a nice LCD or Display screen font.

Getting closer... but still not quite there. What we want would look more like this:

So how do we do that? By using a couple more overrides, but this time the base methods come from the UserControl class. In our SimpleDialog we override the forecolor and backcolor properties and pass them to the textbox we are using.
public override System.Drawing.Color ForeColor
{
get
{
return textBox1.ForeColor;
}
set
{
textBox1.ForeColor = value;
}
}
public override Color BackColor
{
get
{
return textBox1.BackColor;
}
set
{
textBox1.BackColor = value;
}
}
Now when you set the backcolor of the control, it will make that the backcolor of TextBox1. Same with the forecolor. Set the backcolor to black and the forecolor to Lawn green.There is also one really big cheat that we don't do for final projects, but for now makes it look cleaner JUST FOR TESTING. I dragged the SimpleDisplay on top of the textbox from part 1, covering it up completely. Its still there and we are still using it. We just can't see it. Bad bad bad in release versions. But fast and dirty while testing just to check out our affect.
Between part 1 and part 2 we've done a lot of code changes, so I'm going to wrap this up with the code for each file, just so we are all on the same page. Notice how little code there really is. 20-80 lines for any individual piece.
GenericDisplay
namespace Display.cs
{
public partial class GenericDisplay : UserControl
{
public GenericDisplay()
{
InitializeComponent();
}
public virtual void SetDisplay(string TheText)
{
//TODO: Set the display to the passed in string value
}
public virtual void SetDisplay(decimal TheAmount)
{
string temp = string.Format("{0:C}", TheAmount);
SetDisplay(temp);
}
}
}
SimpleDisplay
namespace Display.cs
{
public partial class SimpleDisplay : GenericDisplay
{
public SimpleDisplay()
{
InitializeComponent();
}
public override void SetDisplay(string TheText)
{
textBox1.Text = TheText;
}
public override System.Drawing.Color ForeColor
{
get
{
return textBox1.ForeColor;
}
set
{
textBox1.ForeColor = value;
}
}
public override Color BackColor
{
get
{
return textBox1.BackColor;
}
set
{
textBox1.BackColor = value;
}
}
}
}
Form1 used for testing
namespace DemoPOS
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void registerKeypad1_ButtonPressed(object sender, KeyPressEventArgs e)
{
textBox1.Text += e.KeyChar;
//simpleDisplay1.SetDisplay(textBox1.Text);
simpleDisplay1.SetDisplay(Convert.ToDecimal(textBox1.Text));
}
}
}
GenericKeypad
namespace Keypad.cs
{
public partial class GenericKeypad : UserControl
{
public GenericKeypad()
{
InitializeComponent();
}
#region Events
public event KeyPressEventHandler ButtonPressed;
#endregion Events
#region Methods
public void RaiseButtonPressed(char WhatToSend)
{
KeyPressEventHandler handler = ButtonPressed;
if (handler != null)
{
handler(this, new KeyPressEventArgs(WhatToSend));
}
}
#endregion Methods
}
}
RegisterKeypad
namespace Keypad.cs
{
public partial class RegisterKeypad : GenericKeypad
{
public RegisterKeypad()
{
InitializeComponent();
}
private void btn1_Click(object sender, EventArgs e)
{
RaiseButtonPressed('1');
}
private void btn2_Click(object sender, EventArgs e)
{
RaiseButtonPressed('2');
}
private void btn3_Click(object sender, EventArgs e)
{
RaiseButtonPressed('3');
}
private void btn4_Click(object sender, EventArgs e)
{
RaiseButtonPressed('4');
}
private void btn5_Click(object sender, EventArgs e)
{
RaiseButtonPressed('5');
}
private void btn6_Click(object sender, EventArgs e)
{
RaiseButtonPressed('6');
}
private void btn7_Click(object sender, EventArgs e)
{
RaiseButtonPressed('7');
}
private void btn8_Click(object sender, EventArgs e)
{
RaiseButtonPressed('8');
}
private void btn9_Click(object sender, EventArgs e)
{
RaiseButtonPressed('9');
}
private void btn0_Click(object sender, EventArgs e)
{
RaiseButtonPressed('0');
}
private void btn00_Click(object sender, EventArgs e)
{
RaiseButtonPressed('0');
RaiseButtonPressed('0');
}
private void btnEnter_Click(object sender, EventArgs e)
{
RaiseButtonPressed('E');
}
private void btnPlus_Click(object sender, EventArgs e)
{
RaiseButtonPressed('+');
}
private void btnMinus_Click(object sender, EventArgs e)
{
RaiseButtonPressed('-');
}
}
}
Part 0 - Custom events
Part 1 - Virtual Keypad (inheritance)
Part 2 - The LCD display panel
Part 3 - [coming soon]
Part 4 - [after that]
This post has been edited by tlhIn'toq: 07 June 2010 - 11:50 AM






MultiQuote








|