Calculator using Flex


One day I thought of implementing a simple Calculator. And when I was looking for the platform to develop, I came up with Flex, yep, only using Flex SDK, because I do not have an IDE to simplify my task. It was quite challenging without the help of Flash Builder. And I would like to share my experience on developing the calculator with you. For compilation instructions, please see Compiling Flex without Flash Builder.


First thing that came in mind is the GUI, means the arrangement of the controls that I am going to use. I always do relative placement of controls, which has been already discussed here. Any how I have to decide upon some good looking interface. And I found it on the right side of my keyboard. Yep! The numeric pad is the one I am talking about.



The controls has to be placed in the same format. And in addition, a text input is needed for displaying the output. Putting all together, the needs are
  • 17 Buttons
  • A text box
And all these has to be put into something that resembles a Calculator. Below shown is the final UI (just shown for understanding better)



There are two buttons replaced from the actual numeric keypad. One is the NumLock, of course we don't need it. So changed it to Clear. To be simple, replace the dot (.) with Backspace:) Rest of them are in exact shape, size and place. Isn't it?

For the relative placement of controls, lets write an init function. But before that lets put all the controls we need in the mxml file. There are three crazy buttons, of different size. The zero, plus and equal to. Rest of the buttons are of square shape, and lets make the height and width of them same.


The three buttons of different heights and widths can be calculated manually. No big deal!! Two times the size + some extra space, the space between the two buttons, horizontal or vertical, both are same. Lets assume the size (since it is a square only one side is enough) as 40 (it actually is) and the spacing between the controls as 5 (this one too:)) the modified size would be then 2 * 40 + 5 = 85. It is that simple. For zero make the width 85, and for the other two make the height 85. Also I have used one more hidden button (Don't search in the layout, it is hidden:)) for serving some special purpose. So putting it all together in the mxml, the contents of the file are

<?xml version="1.0" encoding="utf-8"?>


<s:Application  xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
applicationComplete="init_layout()">
<fx:Script source="Calc.as"/>
<s:Button id="btn0" label="0" height="40" width="85"/>
  <s:Button id="btn1" label="1" height="40" width="40"/>
<s:Button id="btn2" label="2" height="40" width="40"/>
<s:Button id="btn3" label="3" height="40" width="40"/>
<s:Button id="btn4" label="4" height="40" width="40"/>
<s:Button id="btn5" label="5" height="40" width="40"/>
<s:Button id="btn6" label="6" height="40" width="40"/>
<s:Button id="btn7" label="7" height="40" width="40"/>
<s:Button id="btn8" label="8" height="40" width="40"/>
<s:Button id="btn9" label="9" height="40" width="40"/>
<s:Button id="btnDel" label="D" height="40" width="40"/>
<s:Button id="btnClr" label="C" height="40" width="40"/>
<s:Button id="btnAdd" label="+" height="85" width="40"/>
<s:Button id="btnMul" label="-" height="40" width="40"/>
<s:Button id="btnSub" label="*" height="40" width="40"/>
<s:Button id="btnDiv" label="/" height="40" width="40"/>
<s:Button id="btnEql" label="=" height="85" width="40"/>
<s:Button id="btnHidden" visible="false"/>
<s:TextInput id="txtResult" enabled="false" textAlign="right" 
fontSize="18" maxChars="8" height="40" width="175" />
</s:Application>

Now the controls are put, in the form, but not at the exact place. To achieve that lets take a look at the init_layout function. The function is presented here, after so many modifications, of course the one I could think of simple, with minimal amount of code.


public function init_layout():void
{
var i:Number, j:Number;
var size:Number = 40;
var spacing:Number = 5;
var location:Array = new Array();
var rows:Array = new Array();

  location[0] = 50;

for (i = 1; i <= 5; i++)
  location[i] = location[i - 1] + size + spacing;

  rows[0] = [txtResult, btnHidden, btnHidden, btnHidden];
rows[1] = [btnClr, btnDiv, btnMul, btnSub ];
rows[2] = [btn7, btn8, btn9, btnAdd];
rows[3] = [btn4, btn5, btn6, btnHidden];
rows[4] = [btn1, btn2, btn3, btnEql];
rows[5] = [btn0, btnHidden, btnDel, btnHidden];
for (i = 0; i < 6; i++) {
  for (j = 0; j < 4; j++) {
    rows[i][j].x = location[j];
    rows[i][j].y = location[i];
  }
  }
add_listeners();
}

There are six rows including the text input at the first row. And a maximum of four columns, and minimum two columns (only one column in the first row, that is the text box). The layout, I would say is a 2D Jagged Array (a term used in C#). A Jagged array is something that has different columns in different rows. I wrote the very first function for layout which has more than hundred lines, and of course no one would have the patience to read all those lines. Then I thought of simplifying it, and I was able to achieve this one after my tenth attempt (I am not a master in Programming, I am just starting up:)). Hope now you are able to grasp why that btnHidden is there:)

Lets fill the first row, second and so on. Wherever you could not find an element, put the btnHidden. That is all about it. And from where to start is a really big question. I have chosen 50, only 50. I mean (50, 50), since most of the controls are square in shape, it is rather easy to decide upon the location. Simple math isn't it? We have the locations now, then what, place the controls at the calculated locations. And my layout is ready.

Next step is adding the logic to it. The logic I used here is what I could think of. Please don't stick to it. Start writing your own. The first step is to add listeners. There are two types of listeners I have used, the Mouse and the Keyboard. Ideally these two are of same behavior. So I thought of writing a common sub routine for both the Events and call the respective routines from the Listener functions.

public function add_listeners():void
{
var numbers:Array = new Array();
  var operators:Array = new Array();
var btn:Button;

  numbers.push(btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9);
  operators.push(btnAdd, btnSub, btnMul, btnDiv);

for each (btn in numbers)
    btn.addEventListener(MouseEvent.CLICK, num_input);
  for each (btn in operators)
    btn.addEventListener(MouseEvent.CLICK, opr_input);

  btnDel.addEventListener(MouseEvent.CLICK, del_input);
  btnClr.addEventListener(MouseEvent.CLICK, clr_input);
  btnEql.addEventListener(MouseEvent.CLICK, eql_input);

  stage.addEventListener(KeyboardEvent.KEY_UP, key_input);
}

stage corresponds to the application. Next goes the implementation of these listeners. I am putting the code straight away now. Hope it is not hard to understand. The Key board listener function seems to be quite messy. You can re-write it in a better way. I thought of just invalidating the unnecessary keys. It has support for the keys in the numeric keypad also.


public function key_input(event:KeyboardEvent):void
{
if (event.keyCode >= Keyboard.NUMBER_0 && event.keyCode <= Keyboard.NUMBER_9) 
    append_number(String(event.keyCode - 48));
  else if (event.keyCode >= Keyboard.NUMPAD_0  && event.keyCode <= Keyboard.NUMPAD_9)
    append_number(String(event.keyCode - 96));
  else if (event.keyCode == Keyboard.NUMPAD_ADD || event.keyCode == 43)
    do_operation("+")
  else if (event.keyCode == Keyboard.NUMPAD_SUBTRACT || event.keyCode == 45)
    do_operation("-");
  else if (event.keyCode == Keyboard.NUMPAD_MULTIPLY || event.keyCode == 47)
    do_operation("/");
  else if (event.keyCode == Keyboard.NUMPAD_DIVIDE || event.keyCode == 42)
    do_operation("*");
  else if (event.keyCode == Keyboard.BACKSPACE)
    delete_number();
   else if (event.keyCode == Keyboard.ESCAPE)
    clear_number();
  else if (event.keyCode == Keyboard.ENTER || event.keyCode == Keyboard.NUMPAD_ENTER 
|| event.keyCode == Keyboard.EQUAL)
    display_result();
}

public function num_input(event:MouseEvent):void
{
append_number(event.currentTarget.label);
}

public function del_input(event:MouseEvent):void
{
delete_number();
}

public function clr_input(event:MouseEvent):void
{
clear_number();
}

public function eql_input(event:MouseEvent):void
{
display_result();
}

public function opr_input(event:MouseEvent):void
{
do_operation(event.currentTarget.label);
}

As you could see, the listeners call the same routine, no matter it is from keyboard or mouse. This helps in easy debugging in case of any issues. The following code holds the actual logic. The first function is the append_number which appends the input to the text box. Maximum of 8 digits, and no leading zeroes.


public function append_number(num:String):void
{
if (cur_oper == "=") {
    txtResult.text = "";
    cur_oper = "0";
  }
  if (txtResult.text.length == 8)
    return;
  if (txtResult.text.length == 0 && num == "0")
    return;
  txtResult.text += num;
}

This function removes a last digit, and that is the way it should be. 


public function delete_number():void
{
if (txtResult.text.length != 0)
    txtResult.text = txtResult.text.substring(0, txtResult.text.length - 1);
}

This function clears the whole text, resets few global data. Also triggered on hitting the ESC key.

public function clear_number():void
{
  txtResult.text = "";
  a = 0;
  b = 0;
  cur_oper = "0";
}

This function displays the result after doing the necessary calculation.

public function display_result():void
{
if (txtResult.text.length > 0) {
    b = parseInt(txtResult.text, 0);
  if (cur_oper == "+")
    txtResult.text = String(a + b);
    else if (cur_oper == "-")
    txtResult.text = String(a - b);
    else if (cur_oper == "*")
  txtResult.text = String(a - b);
  else if (cur_oper == "/")
    txtResult.text = String(a / b);
    cur_oper = "=";
}
}

This function is called on clicking an operator. Sets some global variable.

public function do_operation(opr:String):void
{
if (txtResult.text.length > 0) {
    cur_oper = opr;
    a = parseInt(txtResult.text, 0);
    txtResult.text = "";
}
}

Putting it all together in the (.as) file (I missed out some global declarations and imports), the calculator is ready. I won't say that this program is bug free. I just gave a hint. Change the logic to meet your requirements. Thanks for reading this blog.

See also 

No comments:

Post a Comment