As described in the earlier outline for my modeling project (and yes, I know I've been slow getting new posts up), the first step in creating a model-driven runtime for calculator applications is to have a working single-calculator program upon which to base the work—something, that is, that can be turned into a generic, configurable runtime. The primary benefit here is that we’ll then be able to see the incremental stages of development of the process that will, I imagine, shed some light on the general nature of runtimes as compared to specific applications.
It also affords the opportunity to explore “modeling” itself in its different expressions. Fact of the matter is that all software engages in modeling processes: the purpose of software is to essentially configure a universal machine (the computer) to behave in ways that reflect various tasks and processes (whether human or automated). To give a simple example, a spreadsheet models forms and processes that originally existed on paper; the software was modeled after some real artifact. Indeed, when I wrote the Windows 3.0/3.1/NT/95/98/2000/XP/Vista Calculator program, I directly modeled it after the $9.95 “Dynatone” job I’d bought at K-Mart when I was in high school. That is, I made the program behave pretty much like the physical device.
In this two-part article, then, we’ll discuss how the conceptual “models” of appearance and behavior typically manifest themselves in a combination of code plus data, which in our case means and C# and XAML. As you may already realize from your own work, appearance and behavior end up being spread around throughout an application’s code, mostly because writing a single application simply doesn’t require a cleaner separation. Granted, facilities like XAML and “code-behind” have gone a long way to at least separate appearance and behavior, which is fabulous for collaboration between designers and programmers. Yet still, the behavioral “models” in a typical application are not only spread around within the code itself, but is also expressed to some degree in the very structure of that code.
As we turn to modeling everything about an application in data only, we’ll be teasing those behavioral models out of the code. This means converting specific procedural code into something more generic—something parameterized with those parameters coming from data. For the time being, the data in question will just come from within the application itself, but having made that transition it will be all the easier to acquire that data from other sources. It’s at that point that the application has become a generic, configurable runtime.
But that’s getting ahead of ourselves. For now, let’s take a look at the base .NET application we’re working with and how calculator concepts are modeled therein.
[Note: I realize and know it's necessary at the outset to say that what I mean by "behavior" here might not match how others define it, especially in the context of "modeling." As mentioned in the first post of this series, I certainly don't consider myself all that educated in the modeling business, so please bear with. If you'd like to correct or improve my understanding, comments are welcome.]
Rewriting the WPF Calculator Demo
Now I could have easily started from scratch to create a clone of the Windows Vista Calculator, but besides having a healthy streak of appropriate laziness, I really didn’t’ want to scratch and sniff through MSDN to find all the .NET/WPF details I haven’t yet internalized. So I started instead with the WPF Calculator Demo sample download from MSDN, which is perhaps also somewhat familiar to .NET programmers already.
Of course, also having a healthy editorial mindset, I must admit that by the time I was done just changing the visuals I’d also significantly rewritten the code. The sample was originally written, it seems, to show off WPF features and not to provide a real and reliable application. And as is unfortunately true with most samples, this one lacks the kind of polishing that a piece of documentation normally receives (even though a sample is essentially documentation itself). So I cleaned up the XAML styling, renamed lots of stuff, cleaned up the source code (formatting, old comments, etc.), and fixed a number of behavioral bugs (not to mention typos like the word “devide” that should be “divide”). I also dumped the exception handling because .NET nicely handles values like infinity. In short, it’s just not all that much like the original sample now.
A Tour of the Stage 1 Code
In any case, the code for this project is in the attached ModelCalc_Stage1a project. While significantly different than the original WPF sample, it still maintains the same basic design structure. I intentionally didn’t change this aspect all that much because it’s so useful in discussing “modeling in code.”
So let’s take a look at those structures and talk about the implications of the design.
In the XAML (window1.xaml):
- Each button’s appearance is formally styled as a DigitButton, MemoryButton, OperatorButton, or FunctionButton.
- Each button’s behavior, however, is only informally classified based on the method assigned to the Click event. Here there are only two methods:
- DigitBtn_Click, ostensibly for “digit” buttons, but also used for Backspace and the decimal point even though the Backspace is visually styled as an OperatorButton.
- OperBtn_Click, ostensibly for “operator” buttons but is also used for memory buttons and function buttons, each of which only acts on the current value in the display with no implications as an operator with two operands.
In the code (window1.xaml.cs) now, we have four main sections:
- Variables. There are two types here:
- Variables that directly relate to a user’s concept of how a calculator operates (lines 20-59 in the code):
- A String called Op that contains the most recently entered operator.
- A Double called Stack that contains the left-hand operand (the first value entered before an operator).
- A String called Accumulator that contains the value shows in the display and is also the right-hand operand (the actual value is in _accumulator as the Accumulator implements its set method to also automatically update the display).
- A Double called Mem that contains the current memory value (the actual value is in _memory as Mem implements its set method to automatically show “M” in the memory indicator when the value is non-zero).
- A bool called AllowRepeatEquals to indicate whether a repeat = feature is enabled or not (this anticipates a configurable runtime).
- Variables for tracking internal state (lines 60-73):
- A bool called LastKeyWasOperator to know when a newly entered operator should just replace the previous one without triggering a calculation.
- A bool called LastKeyWasEquals and a Double called LastEqualsOperand to handle repeated = keys that repeat the last operation.
- A bool called OverwriteAccumulator to indicate whether digit keys that begin a new value should overwrite the display rather than append to it.
- Event handlers:
- The TextInput event is mapped to OnWindowKeyDown that translates keystrokes into button equivalents, calling either ProcessKey or ProcessOperation. (There are some bugs with this, such as the fact that F9 and Delete don’t come through this event, but we’ll leave it the way it is for now. Note that the keyboard mappings match those of the Windows Vista Calculator, which are all pretty much what I dreamed up in 1988 when I was munching Twix bars in the late afternoon, so you can blame me for anything that’s strange.)
- OperBtn_Click always calls ProcessOperation.
- DigitBtn_Click always calls ProcessKey, using the first letter of the calling button’s text as the key identifier.
- ProcessKey is then the routine that handles building up a string of digits in the display, which may include a decimal point and has to accommodate trailing zeros after a decimal point. This is why Accumulator is maintained as a String rather than a Double, since conversion to a value would remove trailing zeros. The digits 0-9, the decimal point, and the Backspace key are all handled in this routine.
- ProcessOperation then handles all the other keys: operators that involve Stack and Accumulator, functions that modify Accumulator, keys that modify Mem, or those like C and CE that reset state. A few notes:
- The ComputeLastOperation method is broken out because operator keys (+, -, *, and /) all imply pressing =. This is so entering 2+5+7-12*2.5= yields ongoing intermediate results: 2+5 (=7) + 7 (=14) – 12 (= 2) * 2.5 = 5.
- It’s important in ComputeLastOperation that the operands are in the proper order for subtraction and division. Within that method, Stack is always the left-hand operand whereas the right-hand operand is a parameter. When called from an operator, that parameter is always Accumulator. From within the = case, however, the parameter has to be the previous operand if = is being repeated, in which case Stack has to also be set to Accumulator:
if (LastKeyWasEquals && AllowRepeatEquals)
Stack = Convert.ToDouble(Accumulator);
LastEqualsOperand = Convert.ToDouble(Accumulator);
- See how the operator, =, and +/- cases are the only places that use a conditional if statement. Every other case does nothing more than assign expression results to variables. We’ll talk about what this means in a moment.
Continued in Part 2 >>>
ModelCalc_Stage1a.zip (14.92 kb)