Friday, November 26, 2010

Silverlight RPN Calculator Tutorial.

Well I beat my old nemesis and finished an RPN calculator. It was much easier than I remember, but then again, I tried it like four years ago. I've learned so much in the time since, I can hardly believe how silly my old code is.

Have any of you ever used a Reverse Polish Notation calculator? I did in high school. It was easily the best calculator ever (the HP=32SII). RPN is great, because you don't have to use parenthesis. The stack and the order in which you key the operators maintains order of operations.

Today, I'll walk you through creating one. Note this tutorial is technically for Silverlight, but except for the marked sections, it can be applied wholesale to WinForms or WPF. Most if this is simple stack logic, and that exists on all .NET platforms.

Read more after the jump.

Note: I'm assuming you are familiar with stack operations. If not, please visit the linked MSDN page for information.

Before we start programming, you have to understand postfix notation. Consider the following:

5 6 +

This is a postfix expression, equating to 11. The logical steps to evaluate it are:
  1. Push(5)
  2. Push(6)
  3. var b = Pop()
  4. var a = Pop()
  5. Push(a + b)
  6. Peek()

Consider a more complicated expression:

10 4 6 + 9 * -

This evaluates to -80. It is the equivelant of 10 - (( 4 + 6 ) * 9) in Infix (standard) notation. The same basic steps are followed here.
  1. Push(10)
  2. Push(4)
  3. Push(6)
  4. var b = Pop()
  5. var a = Pop()
  6. Push(a + b)
  7. Push(9)
  8. var b = Pop()
  9. var a = Pop()
  10. Push(a * b)
  11. var b = Pop()
  12. var a = Pop()
  13. Push(a - b)
  14. Peek()

The basic logic is to push numbers onto the stack until you encounter an operator. At that point, you pop the top two off the stack, evaluate them with the operator, and push the result back onto the stack. After evaluation, Peek for display.

In RPN calculator terms, there is an ENTER button to push a value onto the stack. Also, to save time, if you're currently entering a number, pushing an operator button will also push the number onto the stack.

So for 5 6 + 3 *, we'd push:
5 [ENTER] 6 [+] 3 [*].

For 1 6 2 / 3 4 / * + (which is the equivalent of 1 + (( 6 / 2 ) * ( 3 / 4 )), you'd push:
1 [ENTER] 6 [ENTER] 2 [/] 3 [ENTER] 4 [/] [*] [+]

On a standard calculator, you'd have to use the Memory function to do this with the proper order of operation. With a graphing calculator, you'd have to use parenthesis. But since you can keep state in the stack, you don't have to worry about any of that with an RPN calc.

So, without further ado, lets get into the code.

Setup

We'll obviously need a stack:

private Stack<double> stack;

We'll also need some Dictionaries to relate keyboard keys with strings, since we're not trusting the users to enter numbers by themselves:

private Dictionary<Key, string> opKeys = new Dictionary<Key, string>();
private Dictionary<Key, string> numKeys = new Dictionary<Key, string>();

private void InitializeDictionaries()
{
    opKeys.Add(Key.C, "C");
    opKeys.Add(Key.Back, "B");
    opKeys.Add(Key.Add, "+");
    opKeys.Add(Key.Subtract, "-");
    opKeys.Add(Key.Multiply, "*");
    opKeys.Add(Key.Divide, "/");
    opKeys.Add(Key.Enter, "E");
    opKeys.Add(Key.Decimal, ".");

    numKeys.Add(Key.D0, "0");
    numKeys.Add(Key.D1, "1");
    numKeys.Add(Key.D2, "2");
    numKeys.Add(Key.D3, "3");
    numKeys.Add(Key.D4, "4");
    numKeys.Add(Key.D5, "5");
    numKeys.Add(Key.D6, "6");
    numKeys.Add(Key.D7, "7");
    numKeys.Add(Key.D8, "8");
    numKeys.Add(Key.D9, "9");
    numKeys.Add(Key.NumPad0,  "0");
    numKeys.Add(Key.NumPad1,  "1");
    numKeys.Add(Key.NumPad2,  "2");
    numKeys.Add(Key.NumPad3,  "3");
    numKeys.Add(Key.NumPad4,  "4");
    numKeys.Add(Key.NumPad5,  "5");
    numKeys.Add(Key.NumPad6,  "6");
    numKeys.Add(Key.NumPad7,  "7");
    numKeys.Add(Key.NumPad8,  "8");
    numKeys.Add(Key.NumPad9,  "9");
}

Now, we're going to use Lambda methods for the operators. This makes things quite simple. If you're not familiar with what's going on here, I have a tutorial on Lambdas that you're welcome to read. They're basically a shorthand way to write anonymous methods. And the Func object is a way to store these methods as objects. We're going to make a Dictionary of Funcs keyed by strings:

private Dictionary<string, Func<double, double, double>> op =
    new Dictionary<string, Func<double, double, double>>();

private void InitializeOp()
{
    op.Add("+", (a, B) => a + B);
    op.Add("-", (a, B) => a - B);
    op.Add("*", (a, B) => a * B);
    op.Add("/", (a, B) => a / B);
}

If this seems strange to you, here's an example of how we would invoke this.

This doesn't go in the project. This is just an example of using Funcs in a dictionary.
double a = 5, b = 6;
double result = op["+"](5, 6);

Now we've stored methods in a dictionary, and depending on which operator we pass, the proper one will be invoked. This is quite useful, because it allows us to skip a switch or if else if statement.

Also, two more bools we'll need to keep track of state:

private bool clearOnNext, errorState;

clearOnNext lets us know if the next number we push will start a new number or append to the current one. errorState is a simple bool that lets us know if we're currently reporting an error to the user (like Div By Zero or Out of Stack).

Silverlight Specific: XAML MARKUP (display code)

Here's the XAML markup we're using. This could be replicated by WinForms, but I prefer to work with Silverlight/WPF whenever I can.



 <Grid>
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto" />
   <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <!-- Max length: 15 -->
  <Grid Grid.Row="0">
   <Grid.ColumnDefinitions>
    <ColumnDefinition Width="30" />
    <ColumnDefinition Width="50" />
    <ColumnDefinition Width="*"/>
   </Grid.ColumnDefinitions>
   <Border BorderBrush="Black" BorderThickness="1" CornerRadius="3">
    <Grid>
     <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
     </Grid.RowDefinitions>
     <TextBlock Text="Stack Depth" Grid.Row="0" FontSize="8" TextWrapping="Wrap" VerticalAlignment="Bottom" HorizontalAlignment="Center" />
     <TextBlock Text="0" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Center" x:Name="stackDepthTextBlock" />
    </Grid>
   </Border>
   <Border Grid.Column="1" BorderBrush="Black" BorderThickness="1" CornerRadius="3">
    <Grid>
     <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
     </Grid.RowDefinitions>
     <Button x:Name="copyButton" Content="Copy" Click="copyButton_Click" />
     <Button x:Name="pasteButton" Content="Paste" Grid.Row="1" Click="pasteButton_Click" />
    </Grid>
   </Border>
   <TextBox IsReadOnly="True" x:Name="displayTextBox" Text="" 
    Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" HorizontalAlignment="Stretch" TextAlignment="Right" 
    FontFamily="Courier New" FontSize="26" FontWeight="ExtraBold" 
    KeyUp="DisplayTextBox_KeyUp" />
  </Grid>
  <Grid Grid.Row="1">
   <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
   </Grid.RowDefinitions>
   <Grid.ColumnDefinitions>
    <ColumnDefinition />
    <ColumnDefinition />
    <ColumnDefinition />
    <ColumnDefinition />
   </Grid.ColumnDefinitions>

   <!-- Buttons -->

   <Button Content="Enter" Tag="E" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Click="Button_Click" IsTabStop="False" />
   <Button Content="C" Tag="C" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="?" Tag="B" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />

   <Button Content="7" Tag="7" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="8" Tag="8" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="9" Tag="9" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="÷" Tag="/" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />

   <Button Content="4" Tag="7" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" />
   <Button Content="5" Tag="8" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" />
   <Button Content="6" Tag="9" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" />
   <Button Content="×" Tag="*" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" />

   <Button Content="4" Tag="4" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="5" Tag="5" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="6" Tag="6" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="×" Tag="*" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />

   <Button Content="1" Tag="1" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="2" Tag="2" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="3" Tag="3" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="-" Tag="-" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />

   <Button Content="0" Tag="0" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Click="Button_Click" IsTabStop="False" />
   <Button Content="." Tag="." Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <Button Content="+" Tag="+" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="4" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
   <!-- /Buttons -->
  </Grid>
 </Grid>
</Grid>

One neat thing I did was to make a new style for a Button. I copied the Button's default template from the MSDN, but the one small change I made was putting the inside a . The Viewbox is a neat control that will stretch and scale a single child to fill all available space. Without it, the buttons can scale to fit, but the text inside would remain the same size. With it, the text scales to fill the button. Here's the template.


<Style x:Key="ViewboxButton" TargetType="Button">
 <Setter Property="Background" Value="#FF1F3B53"/>
 <Setter Property="Foreground" Value="#FF000000"/>
 <Setter Property="Padding" Value="3"/>
 <Setter Property="BorderThickness" Value="1"/>
 <Setter Property="BorderBrush">
  <Setter.Value>
   <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
    <GradientStop Color="#FFA3AEB9" Offset="0"/>
    <GradientStop Color="#FF8399A9" Offset="0.375"/>
    <GradientStop Color="#FF718597" Offset="0.375"/>
    <GradientStop Color="#FF617584" Offset="1"/>
   </LinearGradientBrush>
  </Setter.Value>
 </Setter>
 <Setter Property="Template">
  <Setter.Value>
   <ControlTemplate TargetType="Button">
    <Grid>
     <vsm:VisualStateManager.VisualStateGroups>
      <vsm:VisualStateGroup x:Name="CommonStates">
       <vsm:VisualState x:Name="Normal"/>
       <vsm:VisualState x:Name="MouseOver">
        <Storyboard>
         <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
        </Storyboard>
       </vsm:VisualState>
       <vsm:VisualState x:Name="Pressed">
        <Storyboard>
         <ColorAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#FF6DBDD1"/>
         <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#D8FFFFFF"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#C6FFFFFF"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#8CFFFFFF"/>
         <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#3FFFFFFF"/>
        </Storyboard>
       </vsm:VisualState>
       <vsm:VisualState x:Name="Disabled">
        <Storyboard>
         <DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
        </Storyboard>
       </vsm:VisualState>
      </vsm:VisualStateGroup>
      <vsm:VisualStateGroup x:Name="FocusStates">
       <vsm:VisualState x:Name="Focused">
        <Storyboard>
         <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="1"/>
        </Storyboard>
       </vsm:VisualState>
       <vsm:VisualState x:Name="Unfocused" />
      </vsm:VisualStateGroup>
     </vsm:VisualStateManager.VisualStateGroups>
     <Border x:Name="Background" CornerRadius="3" Background="White" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
      <Grid Background="{TemplateBinding Background}"  Margin="1">
       <Border Opacity="0"  x:Name="BackgroundAnimation" Background="#FF448DCA" />
       <Rectangle x:Name="BackgroundGradient" >
        <Rectangle.Fill>
         <LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
          <GradientStop Color="#FFFFFFFF" Offset="0" />
          <GradientStop Color="#F9FFFFFF" Offset="0.375" />
          <GradientStop Color="#E5FFFFFF" Offset="0.625" />
          <GradientStop Color="#C6FFFFFF" Offset="1" />
         </LinearGradientBrush>
        </Rectangle.Fill>
       </Rectangle>
      </Grid>
     </Border>
     <Viewbox>
      <ContentPresenter Content="{TemplateBinding Content}" />
     </Viewbox>
     <Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
     <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Margin="1" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
    </Grid>
   </ControlTemplate>
  </Setter.Value>
 </Setter>
</Style>

Non-Silverlight Specific

If you're not doing this in Silverlight, the important thing to note for the rest of this tutorial is that I'm assuming you created a Read Only TextBox named "displayTextBox". You can name it whatever you like, just make sure to change it in the code as well. Another thing I've assumed is that you've created all your buttons, and set their Tag properties to their numeral or operator value. Also, they all should call the same handler for their Click event: "Button_Click". One more thing, I've attached an event handler to the KeyUp event of the displayTextBox.

Here's a property we'll be using for convenience sake:

private double current
{
    get
    {
        if (displayTextBox.Text == string.Empty)
            displayTextBox.Text = "0";
        return double.Parse(displayTextBox.Text);
    }
}

This simply parses what's in the TextBox as a double.

Initialization

Here's how our constructor will look:

public MainPage()
{
    InitializeComponent();
    InitializeDictionaries();
    InitializeOp();
    stack = new Stack<double>();
    this.Loaded += (s, ea) =>
    {
        if (!App.Current.IsRunningOutOfBrowser)
            System.Windows.Browser.HtmlPage.Plugin.Focus();
        displayTextBox.Focus();
    };
    clearOnNext = false;
    errorState = false;
}

There's a lambda in here too. It's just a quick event handler to set the focus onto the Display TextBox when the program loads. Since for some reason, the Silverlight app doesn't focus itself when it starts, I've added a line to tell the browser to first focus the SL app first.

Methods

The simplest method we need to add has to handle input. We'll call this one from handlers.

private void ProcessInput(string input)
{
    if (clearOnNext)
    {
        displayTextBox.Text = string.Empty;
        clearOnNext = false;
        errorState = false;
    }
    displayTextBox.Text += input;
}

It checks to see if we're starting a new number. If so, it clears it and resets the state, otherwise, it just appends text.

We'll also need a method to process special characters like "Clear", "Enter", "Backspace", and "Decimal" as well as the operators

private void ProcessSpecial(string input)
{
    switch (input)
    {
        case "C":
            if (displayTextBox.Text.Length > 0)
                displayTextBox.Text = string.Empty;
            else
            {
                stack.Clear();
                RefreshDepthText();
            }
            clearOnNext = false;
            break;
        case "B":
            if (!clearOnNext && displayTextBox.Text.Length > 0)
                displayTextBox.Text = displayTextBox.Text.Substring(0, displayTextBox.Text.Length - 1);
            break;
        case ".":
            if (!displayTextBox.Text.Contains("."))
            {
                if (displayTextBox.Text.Length < 1 || errorState)
                    ProcessInput("0.");
                else
                    ProcessInput(".");
            }
            break;
        case "E":
            Enter();
            break;
        case "+":
        case "-":
        case "*":
        case "/":
            DoOp(input);
            break;
    }
}

This one walks through the possibilities in a switch statement and takes the appropriate action. At this point, some of the methods defined here are undeclared. We'll soon remedy this. I'll also explain each case.

Case "C" is clear. First click, it clears the display. Second (or first if the display is already clear) clears the stack.

Case "B" is backspace. Substring if there's room to.

Case "." handles the decimal. We only allow one of those, and for visual's sake, add a 0 in front if it's the first button clicked.

Case "E" is enter. We'll write that method soon.

The remaining cases are operators. We'll also look at that method soon.

Calculator Logic methods

Here's the Enter method now:

private void Enter()
{
    if (!errorState)
    {
        stack.Push(current);
        RefreshDepthText();
        clearOnNext = true;
    }
}

It's quite simple. We don't want to try to push an error message on the stack, so we check for that first. If there's no error, we push the current value onto the stack, and refresh the Depth Text Display (this just shows a count of how much is on the stack). Also, we set clearOnNext to true, since we'll be starting a new number.


Here's the method where we actually do an operation:

private void DoOp(string input)
{
    if (!clearOnNext)
    {
        stack.Push(current);
    }
    if (stack.Count < 2)
    {
        errorState = true;
        clearOnNext = true;
        displayTextBox.Text = "OUT OF STACK";
        return;
    }
    double b = stack.Pop();
    double a = stack.Pop();
 stack.Push(op[input](a, B));
 double res = stack.Peek();
 if (res == double.NegativeInfinity || res == double.PositiveInfinity || res == double.NaN)
 {
        stack.Clear();
        RefreshDepthText();
        errorState = true;
        clearOnNext = true;
        displayTextBox.Text = "DIV BY ZERO";
  return;
 }
 displayTextBox.Text = stack.Peek().ToString();
 RefreshDepthText();
 clearOnNext = true;
}

The first if statement checks to see if we've been entering a new number. Remember, the way an RPN calc works is that if you're entering a number, an operator key will push that number for you. Next, we check to see if we can pop enough values to proceed. If you try to do an operation that there's not enough numbers for, you'll "run out of stack." Once we know we can, we pop both values, b first (this only matters for subtraction and division). Now we perform the actual operation. My first thought was that we could use a Try/Catch to check for Div By Zero, but apparently that results in double.PositiveInfinity. The string "Infinity" is also a valid parsable double, by the way. So I'm just checking the result now.

Anyway, if everything was successful, Peek the value into the textbox, and move on.

Now, a keen observer would note that while we're set up to handle the calculator logic now, we have no way of actually entering numbers or operators. Have no fear, that's the next section.

Input logic

We've set up event handlers for the KeyUp on the readonly textbox as well as handling the Click event for each button. Here's the code for those event handlers:

private void Button_Click(object sender, RoutedEventArgs e)
{
    string value = (sender as Button).Tag as string;
    if (numKeys.ContainsValue(value))
        ProcessInput(value);
    else
        ProcessSpecial(value);
    displayTextBox.Focus();
}

private void DisplayTextBox_KeyUp(object sender, KeyEventArgs e)
{
    if (opKeys.ContainsKey(e.Key))
        ProcessSpecial(opKeys[e.Key]);
    else if (numKeys.ContainsKey(e.Key))
        ProcessInput(numKeys[e.Key].ToString());
    else return;
}

Miscellaneous methods

These don't hold much importance, just necessary for little things:

private void RefreshDepthText()
{
    stackDepthTextBlock.Text = stack.Count.ToString();
}

private void copyButton_Click(object sender, RoutedEventArgs e)
{
    Clipboard.SetText(current.ToString());
}

private void pasteButton_Click(object sender, RoutedEventArgs e)
{
    if (!Clipboard.ContainsText())
        return;
    string s = Clipboard.GetText();
    double x;
    if (double.TryParse(s, out  x))
        displayTextBox.Text = s;
}

And that's that. If you've followed the instructions properly, you'll have a calculator like this one:


Cross posted from DreamInCode. Will update when it goes live.

The sample project will be available on the Dream In Code tutorial page once it's approved, since Blogger apparently won't let me upload it here.

No comments:

Post a Comment

Speak your mind.