The Calculator Solution (BETA)

来源:互联网 发布:java基础入门培训 编辑:程序博客网 时间:2024/05/21 17:56

 The Calculator Solution (BETA)

we included a "homework" assignment in the form of a non-functioning calculator with the hopes that those new to Flex may learn from attempting to get it to work. After I came up with a solution, it was code-reviewed by Adam, and a few changes were made in order to comply with best practices. We've included the source code for both, but we will be focusing our attention on the revised version as we go through an explanation of the code step by step.

Let's start with the MXML. We didn't need to code the MXML, as it was provided for us, but we need to understand what it does. Right below our application declaration, we have a Style tag:

 <mx:Style source="calculator.css" />

This refers to the file "calculator.css", which contains information about the look and feel of our calculator. The inclusion of the Style tag allows us to reference that .css file elsewhere in the application, as we'll soon see.

The next MXML we see is our CDATA section, which separates the ActionScript portion of the application.

 <mx:Script>    <![CDATA[

Under the ActionScript block, the larger MXML section starts with a Canvas tag.

 <mx:Canvas styleName="calculator" width="220" height="285">

This tag, and the subsequently nested tags, define the user interface of the calculator. The classes within the .css file are referenced with "styleName", and the calculator's width and height are also defined in the Canvas tag.

The first nested tag within the Canvas tag is a Label that displays the calculator's name.

 <mx:Label id="titleLabel" x="75" y="2" fontSize="12" fontWeight="bold"text="Flaterator" />

Following the Label tag is another Canvas tag with two nested Labels that define the calculator's display window.

<mx:Canvas x="11" y="26" width="198" height="53" styleName="display">     <mx:Label id="display" top="6" right="30" left="4" width="164" textAlign="right"         fontSize="14" />     <mx:Label id="mLabel" bottom="4" left="30" text="M" visible="false" fontSize="12" /></mx:Canvas>

Once again, a class in the .css file is referenced, this time with styleName="display". The main calculator display is defined with the first Label tag, and is given an id of "display". This id will later be referenced in the ActionScript code. The second Label tag defines a display area for the memory function icon which appears in the lower left of the calculator window when you use one of the memory function keys.

The next section defines the layout of the buttons. All of the buttons on the calculator reside within this VBox:

<mx:VBox id="buttonBox"x="12" y="88" verticalGap="2" click="handleCalculatorButtonClick( event )">

A VBox is a layout container object that arranges it's child object in a vertical fashion. In addition to defining it's position within the overall Canvas, the VBox, and all of the children of the VBox, are directed to call the "handleCalculatorButtonClick(event)" function when they are clicked. We will discuss the click handler in detail soon.

Nested within the VBox is an HBox with several child objects.

<mx:HBox horizontalGap="2">    <local:CalculatorButton label="MC" />    <local:CalculatorButton label="M+" />    <local:CalculatorButton label="M-" />    <local:CalculatorButton label="MR" /></mx:HBox>

Those child objects: local:CalculatorButton, refer to the MXML component document called CalculatorButton.mxml. The "local" keyword refers to a namespace defined in the application declaration. The calculator buttons have a certain look and feel thanks again to the .css file that gets referenced in the component document. The remaining buttons are laid out in a similar fashion; an HBox is defined, and it's children are CalculatorButtons.

The ActionScript

Directly beneath the CDATA tag is the "import mx.controls.Button" tag, which allows the calculator application to reference the Button class:

  import mx.controls.Button;

The next thing we run across is a series of constant declarations. A constant is like a variable, except that you assign a value to it only once. In our application, there are a few variables who's value will never change. For example, a calculator's "+" button will always have "+" as the value of it's label. In situations where you can identify a constant value, it's smart to use a constant instead of a literal value, like "+" or "my string", because it saves the compiler from having process each literal value, and therefore conserves resources. It also produces code that has a more meaningful context and that is more readable. These are the constants defined in our calculator applications:

 private const OPERAND_INPUT_STATE : int = 0; private const OPERATOR_INPUT_STATE : int = 1; private const OP_ADD : String = "+"; private const OP_SUB : String = "-"; private const OP_MUL : String = "x"; private const OP_DIV : String = "÷"; private const OP_EQL : String = "="; private const FUNC_CLEAR : String = "C"; private const FUNC_MEM_STORE : String = "M"; private const FUNC_MEM_CLEAR : String = "MC"; private const FUNC_MEM_ADD : String = "M+"; private const FUNC_MEM_SUB : String = "M-"; private const FUNC_MEM_RECALL : String = "MR";

Constants are declared in a way similar to variables, except the constant's name is in uppercase letters, and the keyword "const" is used instead of "var".

In addition to constants that refer to the calculator's buttons, we have two constants that are defined for "states":

private const OPERAND_INPUT_STATE : int = 0;private const OPERATOR_INPUT_STATE : int = 1;

We will use these to allow our calculator to have two different states of operation, one where the user has clicked an operator button (1, 2, 7, .), and the other where the user has clicked an operator, (+,-, etc.).

The Event Handler

The next section of code is the event handler function. This function holds a series of conditionals that make up the bulk of our calculator's logic. The first statement of the event handler:

 private function handleCalculatorButtonClick( e : MouseEvent ) : void

This tells us that the function that handles button clicks is an event, and it's of type MouseEvent. Using a lowercase "e" is a conventional way to refer to an event. When we talk about events in ActionScript, we are talking about an Event object. There are several sub class types of the Events class, MouseEvent is one of these. The function does not return any value, and therefore "void" is assigned to it.

In looking at the conditional statements of the listener function, we see the first one tests whether the object that was clicked on was in fact a calculator button.

   if ( e.target is CalculatorButton )

Again, "e" refers to a the MouseEvent that was signal as a result of a mouse click, and so if the target of the mouse click is a CalculatorButton, then proceed to the next lines of code.

 var button : CalculatorButton = e.target as CalculatorButton; var label : String = button.label;

If a CalculatorButton is clicked, then two variables, "button", and "label", are created. The variable "button" is the currently clicked "CalculatorButton", and the variable "label" is the String data held in the label property of the button.

The conditional is further-refined by including some tests for the particular buttons being clicked:

 if ( ( int( label ) >= 0 && int( label ) <= 9 ) || label == "." )

Translated into English, the above simply says, "if you click on 1-9, or 0, or a decimal point, then carry on". More completely, it says " if the integer data held in "label" is greater than or equal to zero, (logical)and the integer data held in "label" is less than or equal to 9, or "label'''s value is ".", then proceed.

If allowed to proceed, the next set of conditionals tests for which state the calculator is in.

 if ( __state == OPERAND_INPUT_STATE ){     display.text += label;}else if ( __state == OPERATOR_INPUT_STATE ){     display.text = label;     __state = OPERAND_INPUT_STATE;}

Since we need to differentiate between the times when you are entering numbers into the calculator, and times when you click on an operator, as well as allow for a new operand to be entered, we test for the value of the variable "_state" to see if it's either "OPERAND_INPUT_STATE", or "OPERATOR_INPUT_STATE".

If the calculator is in OPERAND_INPUT_STATE, the display window should show the String data from the variable "label". Since we gave the Label an id of "display" in the MXML, we can now reference it by name in the ActionScript, and that's what we're doing here:

 display.text

We use += (in this case the String concatenation operator) to add the label to the currently displayed label of our Label instance called "display". This is what we would want if we are entering numbers into a calculator. Without concatenating the data, only one digit would display at a time, making it impossible to enter anything larger than 9.

If instead our calculator is in OPERATOR_INPUT_STATE, the display window will show the last operand entered, until a new one is entered. At this point, the new operand replaces the old one because of the conditional statement in the constant declaration. Instead of the String data from label being concatenated, it now simply displays on it's own. In other words, if String data is concatenated, and I clicked "1,2,3,4" in succession, the display would show the number "1,234". If, however, the String data is not concatenated, and I clicked on "1", and then clicked on "2", the display would show only "2".

As the second part of the conditional states, the value of _state is immediately switched back to OPERAND_INPUT_STATE, and the normal entering of numbers can continue. This logic is the key to getting the calculator to work correctly. It has the effect of allowing us to enter any number as a first operand, click on an operator, (+,-,etc,) and then enter in a second operand.

The next three conditionals are at the same level as the one that checks for the particular button click.

else if ( label == FUNC_CLEAR ){     display.text = "";}else if ( label.charAt( 0 ) == FUNC_MEM_STORE ){     mLabel.visible = true; }if ( label == FUNC_MEM_RECALL ){ }

These three conditionals test for the value of the button's label, although in some unique ways. The first conditional in this set tests to see if the button that was clicked was the "C" button. An important concept to understand is that this conditional tests to see if the button is the button labeled "C", and not merely equal to the button labeled "C". This distinction becomes very important when we ask the compiler to test for values, and is enabled by using the strict equality character, (==). This button has been associated with a constant, (FUNC_CLEAR), that has a String value = "C". If you click on the "C" button, the resulting display is an empty String (a blank display), as set by the value ("").

The next two conditionals deal with the memory function buttons, which again, we haven't programmed any functionality for because we're slackers. They're worth mentioning though, because their conditionals use a little different logic than the other buttons. The first of these tests to see if the first character in the String that is entered is the "M" button. In other words, if you click on the button labeled "M" first. If "M" is in fact the first button to be clicked, then "mLabel.visible" is set to "true". We have given the Label object that will hold the "M" icon an id of "mLabel" in the MXML. Clicking on "M" button first then switches the "visible" property of the Label object from it's default of "false" to "true". And for the "MR" button, no functionality has been defined, so it's conditional has no "then" statement.

The next four conditionals, one "if" three "else-ifs", test to see if the button clicked is an operator. These four conditionals are at the same level as the others that test for button values, but because they test for operands do it differently.

The operand conditionals all follow the same logic, so looking at the first:

 if ( label == OP_ADD ){      __state = OPERATOR_INPUT_STATE;      __operator = OP_ADD;      __firstOperand = parseFloat( display.text );}

We can see that if the button that is clicked is (+), a.k.a, the constant OP_ADD, the state gets switched from the default OPERAND_INPUT_STATE, to OPERATOR_INPUT_STATE. A variable named "__operator" is assigned the value OP_ADD, which we'll be using later in our calculations, and another variable named " _firstOperand" is assign the float value of display.text.

The __firstOperand variable holds the numeric data entered by the user in the first part of our mathematical operation. To enable the calculator to do math on the numbers we enter, we need to go through a special step. Because the data that is held in a Label object is of type String, we need to convert it to numeric data in order to perform operations on it. This is achieved by calling the "parseFloat()" method, which converts our numbered String data into Float (numeric) data. Therefore, when any of the operator buttons are clicked, our numbered Strings are converted to Floats, and the mathematical operation may continue.

The next conditional we run across tests for the "equals" button.

 else if ( label == OP_EQL ){     __state = OPERATOR_INPUT_STATE;     var result : Number = 0;     var secondOperand : Number = 0;     secondOperand = parseFloat( display.text )}

If we click on (=), a few things occur. First, the state goes back to OPERATOR_INPUT_STATE. Two variables are defined: "result" is a Number initially set to 0, and "secondOperand", also a Number and initially set to 0. In addition, following the same process as the firstOperand went through, secondOperand gets converted to Number data by applying parseFloat().

If the equals button gets clicked, the execution then gets routed into a series of nested conditionals that test for the particular operator that was used. The logic is similar for all of them, differing only in the type of mathematical operation that is done.

 if ( __operator == OP_ADD ){     result = ( __firstOperand + secondOperand );}

The conditional that tests for (+) is now testing wether the variable __operator has the value OP_ADD. If this is the case, the variable "result" is given the value of the sum of __firstOperand plus secondOperand. Again, the logic for each of the operations is the same, differing only in the type of math that is applied.

We need to get the Number data that we used in the operations converted back into String data so that it can be displayed in the calculator's window. We can get this done by placing a statement outside of this group of conditionals:

 display.text = result.toString();

This says that the calculator's display window will show the value of the variable "result" converted into String data by calling the toString() method.

Finally, the last thing we run across inside the ActionScript block are some variable definitions for the variables that are used throughout the application. These are the variables __state, __firstOperand, and __operator, which we've already worked with.

That's it. This application represents a fairly simple Flex application that's source code pretty much runs from top to bottom. Hopefully this overview has clearly explained the logic of one possible solution. Again, the calculator can be brought to life by any number of solutions, and the above is just one of them. That being said, it might be worthwhile to compare both solutions included here to get an understanding of best practices.

Of course, both versions work, but the one we studied is different in a few key ways. First, instead of using literal values, the version we studied opted for the use of constants. This saves the compiler from having to parse out multiple instances of the same literal values when in fact, the value will never change and also improvements readability of the code. Second, some variables were created to simplify code that was replicated several times, i.e. the local variables "button" and "label". In addition, care was taken to name variables, functions, and constants with recognition in mind. Saying "private const OPERAND_INPUT_STATE : int = 0;", for example, is more readable than "var state : int = 1;".

Please feel free to comment any questions you may have, and we'll do our best to clear up any confusion.

THE CALCULATORS

Code Reviewed Version For Best Practices

View Source - Download Source

My Original Version

View Source - Download Source