473,385 Members | 1,693 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,385 developers and data experts.

[Silverlight C#] Reverse Polish Notation Calculator Tutorial

Curtis Rutland
3,256 Expert 2GB
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.

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:

Expand|Select|Wrap|Line Numbers
  1. 5 6 +
This is a postfix expression, equating to 11. The logical steps to evaluate it are:
  • Push(5)
  • Push(6)
  • var b = Pop()
  • var a = Pop()
  • Push(a + b)
  • Peek()

Consider a more complicated expression:

Expand|Select|Wrap|Line Numbers
  1. 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.
  • Push(10)
  • Push(4)
  • Push(6)
  • var b = Pop()
  • var a = Pop()
  • Push(a + b)
  • Push(9)
  • var b = Pop()
  • var a = Pop()
  • Push(a * b)
  • var b = Pop()
  • var a = Pop()
  • Push(a - b)
  • 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:

Expand|Select|Wrap|Line Numbers
  1. 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:

Expand|Select|Wrap|Line Numbers
  1. private Dictionary<Key, string> opKeys = new Dictionary<Key, string>();
  2. private Dictionary<Key, string> numKeys = new Dictionary<Key, string>();
  3.  
  4. private void InitializeDictionaries()
  5. {
  6.     opKeys.Add(Key.C, "C");
  7.     opKeys.Add(Key.Back, "B");
  8.     opKeys.Add(Key.Add, "+");
  9.     opKeys.Add(Key.Subtract, "-");
  10.     opKeys.Add(Key.Multiply, "*");
  11.     opKeys.Add(Key.Divide, "/");
  12.     opKeys.Add(Key.Enter, "E");
  13.     opKeys.Add(Key.Decimal, ".");
  14.  
  15.     numKeys.Add(Key.D0, "0");
  16.     numKeys.Add(Key.D1, "1");
  17.     numKeys.Add(Key.D2, "2");
  18.     numKeys.Add(Key.D3, "3");
  19.     numKeys.Add(Key.D4, "4");
  20.     numKeys.Add(Key.D5, "5");
  21.     numKeys.Add(Key.D6, "6");
  22.     numKeys.Add(Key.D7, "7");
  23.     numKeys.Add(Key.D8, "8");
  24.     numKeys.Add(Key.D9, "9");
  25.     numKeys.Add(Key.NumPad0,  "0");
  26.     numKeys.Add(Key.NumPad1,  "1");
  27.     numKeys.Add(Key.NumPad2,  "2");
  28.     numKeys.Add(Key.NumPad3,  "3");
  29.     numKeys.Add(Key.NumPad4,  "4");
  30.     numKeys.Add(Key.NumPad5,  "5");
  31.     numKeys.Add(Key.NumPad6,  "6");
  32.     numKeys.Add(Key.NumPad7,  "7");
  33.     numKeys.Add(Key.NumPad8,  "8");
  34.     numKeys.Add(Key.NumPad9,  "9");
  35. }
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:

Expand|Select|Wrap|Line Numbers
  1. private Dictionary<string, Func<double, double, double>> op =
  2.     new Dictionary<string, Func<double, double, double>>();
  3.  
  4. private void InitializeOp()
  5. {
  6.     op.Add("+", (a, B) => a + B);
  7.     op.Add("-", (a, B) => a - B);
  8.     op.Add("*", (a, B) => a * B);
  9.     op.Add("/", (a, B) => a / B);
  10. }
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.
Expand|Select|Wrap|Line Numbers
  1. double a = 5, b = 6;
  2. 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:

Expand|Select|Wrap|Line Numbers
  1. 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.

Expand|Select|Wrap|Line Numbers
  1. <Grid x:Name="LayoutRoot" Background="White">
  2.  
  3.     <Grid>
  4.         <Grid.RowDefinitions>
  5.             <RowDefinition Height="Auto" />
  6.             <RowDefinition Height="*" />
  7.         </Grid.RowDefinitions>
  8.         <!-- Max length: 15 -->
  9.         <Grid Grid.Row="0">
  10.             <Grid.ColumnDefinitions>
  11.                 <ColumnDefinition Width="30" />
  12.                 <ColumnDefinition Width="50" />
  13.                 <ColumnDefinition Width="*"/>
  14.             </Grid.ColumnDefinitions>
  15.             <Border BorderBrush="Black" BorderThickness="1" CornerRadius="3">
  16.                 <Grid>
  17.                     <Grid.RowDefinitions>
  18.                         <RowDefinition />
  19.                         <RowDefinition />
  20.                     </Grid.RowDefinitions>
  21.                     <TextBlock Text="Stack Depth" Grid.Row="0" FontSize="8" TextWrapping="Wrap" VerticalAlignment="Bottom" HorizontalAlignment="Center" />
  22.                     <TextBlock Text="0" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Center" x:Name="stackDepthTextBlock" />
  23.                 </Grid>
  24.             </Border>
  25.             <Border Grid.Column="1" BorderBrush="Black" BorderThickness="1" CornerRadius="3">
  26.                 <Grid>
  27.                     <Grid.RowDefinitions>
  28.                         <RowDefinition />
  29.                         <RowDefinition />
  30.                     </Grid.RowDefinitions>
  31.                     <Button x:Name="copyButton" Content="Copy" Click="copyButton_Click" />
  32.                     <Button x:Name="pasteButton" Content="Paste" Grid.Row="1" Click="pasteButton_Click" />
  33.                 </Grid>
  34.             </Border>
  35.             <TextBox IsReadOnly="True" x:Name="displayTextBox" Text="" 
  36.              Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" HorizontalAlignment="Stretch" TextAlignment="Right" 
  37.              FontFamily="Courier New" FontSize="26" FontWeight="ExtraBold" 
  38.              KeyUp="DisplayTextBox_KeyUp" />
  39.         </Grid>
  40.         <Grid Grid.Row="1">
  41.             <Grid.RowDefinitions>
  42.                 <RowDefinition />
  43.                 <RowDefinition />
  44.                 <RowDefinition />
  45.                 <RowDefinition />
  46.                 <RowDefinition />
  47.             </Grid.RowDefinitions>
  48.             <Grid.ColumnDefinitions>
  49.                 <ColumnDefinition />
  50.                 <ColumnDefinition />
  51.                 <ColumnDefinition />
  52.                 <ColumnDefinition />
  53.             </Grid.ColumnDefinitions>
  54.  
  55.             <!-- Buttons -->
  56.  
  57.             <Button Content="Enter" Tag="E" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Click="Button_Click" IsTabStop="False" />
  58.             <Button Content="C" Tag="C" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  59.             <Button Content="?" Tag="B" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="0" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  60.  
  61.             <Button Content="7" Tag="7" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  62.             <Button Content="8" Tag="8" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  63.             <Button Content="9" Tag="9" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  64.             <Button Content="÷" Tag="/" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="1" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  65.  
  66.             <Button Content="4" Tag="7" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" />
  67.             <Button Content="5" Tag="8" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" />
  68.             <Button Content="6" Tag="9" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" />
  69.             <Button Content="×" Tag="*" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" />
  70.  
  71.             <Button Content="4" Tag="4" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  72.             <Button Content="5" Tag="5" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  73.             <Button Content="6" Tag="6" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  74.             <Button Content="×" Tag="*" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  75.  
  76.             <Button Content="1" Tag="1" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  77.             <Button Content="2" Tag="2" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  78.             <Button Content="3" Tag="3" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  79.             <Button Content="-" Tag="-" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  80.  
  81.             <Button Content="0" Tag="0" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Click="Button_Click" IsTabStop="False" />
  82.             <Button Content="." Tag="." Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  83.             <Button Content="+" Tag="+" Style="{StaticResource ViewboxButton}" Margin="2" Grid.Row="4" Grid.Column="3" Grid.ColumnSpan="1" Click="Button_Click" IsTabStop="False" />
  84.             <!-- /Buttons -->
  85.         </Grid>
  86.     </Grid>
  87. </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 <ContentPresenter> inside a <Viewbox>. 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.
Expand|Select|Wrap|Line Numbers
  1. <Style x:Key="ViewboxButton" TargetType="Button">
  2.     <Setter Property="Background" Value="#FF1F3B53"/>
  3.     <Setter Property="Foreground" Value="#FF000000"/>
  4.     <Setter Property="Padding" Value="3"/>
  5.     <Setter Property="BorderThickness" Value="1"/>
  6.     <Setter Property="BorderBrush">
  7.         <Setter.Value>
  8.             <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  9.                 <GradientStop Color="#FFA3AEB9" Offset="0"/>
  10.                 <GradientStop Color="#FF8399A9" Offset="0.375"/>
  11.                 <GradientStop Color="#FF718597" Offset="0.375"/>
  12.                 <GradientStop Color="#FF617584" Offset="1"/>
  13.             </LinearGradientBrush>
  14.         </Setter.Value>
  15.     </Setter>
  16.     <Setter Property="Template">
  17.         <Setter.Value>
  18.             <ControlTemplate TargetType="Button">
  19.                 <Grid>
  20.                     <vsm:VisualStateManager.VisualStateGroups>
  21.                         <vsm:VisualStateGroup x:Name="CommonStates">
  22.                             <vsm:VisualState x:Name="Normal"/>
  23.                             <vsm:VisualState x:Name="MouseOver">
  24.                                 <Storyboard>
  25.                                     <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
  26.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
  27.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
  28.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
  29.                                 </Storyboard>
  30.                             </vsm:VisualState>
  31.                             <vsm:VisualState x:Name="Pressed">
  32.                                 <Storyboard>
  33.                                     <ColorAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#FF6DBDD1"/>
  34.                                     <DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundAnimation" Storyboard.TargetProperty="Opacity" To="1"/>
  35.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#D8FFFFFF"/>
  36.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#C6FFFFFF"/>
  37.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#8CFFFFFF"/>
  38.                                     <ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#3FFFFFFF"/>
  39.                                 </Storyboard>
  40.                             </vsm:VisualState>
  41.                             <vsm:VisualState x:Name="Disabled">
  42.                                 <Storyboard>
  43.                                     <DoubleAnimation Duration="0" Storyboard.TargetName="DisabledVisualElement" Storyboard.TargetProperty="Opacity" To=".55"/>
  44.                                 </Storyboard>
  45.                             </vsm:VisualState>
  46.                         </vsm:VisualStateGroup>
  47.                         <vsm:VisualStateGroup x:Name="FocusStates">
  48.                             <vsm:VisualState x:Name="Focused">
  49.                                 <Storyboard>
  50.                                     <DoubleAnimation Duration="0" Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Opacity" To="1"/>
  51.                                 </Storyboard>
  52.                             </vsm:VisualState>
  53.                             <vsm:VisualState x:Name="Unfocused" />
  54.                         </vsm:VisualStateGroup>
  55.                     </vsm:VisualStateManager.VisualStateGroups>
  56.                     <Border x:Name="Background" CornerRadius="3" Background="White" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
  57.                         <Grid Background="{TemplateBinding Background}"  Margin="1">
  58.                             <Border Opacity="0"  x:Name="BackgroundAnimation" Background="#FF448DCA" />
  59.                             <Rectangle x:Name="BackgroundGradient" >
  60.                                 <Rectangle.Fill>
  61.                                     <LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
  62.                                         <GradientStop Color="#FFFFFFFF" Offset="0" />
  63.                                         <GradientStop Color="#F9FFFFFF" Offset="0.375" />
  64.                                         <GradientStop Color="#E5FFFFFF" Offset="0.625" />
  65.                                         <GradientStop Color="#C6FFFFFF" Offset="1" />
  66.                                     </LinearGradientBrush>
  67.                                 </Rectangle.Fill>
  68.                             </Rectangle>
  69.                         </Grid>
  70.                     </Border>
  71.                     <Viewbox>
  72.                         <ContentPresenter Content="{TemplateBinding Content}" />
  73.                     </Viewbox>
  74.                     <Rectangle x:Name="DisabledVisualElement" RadiusX="3" RadiusY="3" Fill="#FFFFFFFF" Opacity="0" IsHitTestVisible="false" />
  75.                     <Rectangle x:Name="FocusVisualElement" RadiusX="2" RadiusY="2" Margin="1" Stroke="#FF6DBDD1" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
  76.                 </Grid>
  77.             </ControlTemplate>
  78.         </Setter.Value>
  79.     </Setter>
  80. </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:

Expand|Select|Wrap|Line Numbers
  1. private double current
  2. {
  3.     get
  4.     {
  5.         if (displayTextBox.Text == string.Empty)
  6.             displayTextBox.Text = "0";
  7.         return double.Parse(displayTextBox.Text);
  8.     }
  9. }
This simply parses what's in the TextBox as a double.

Initialization

Here's how our constructor will look:

Expand|Select|Wrap|Line Numbers
  1. public MainPage()
  2. {
  3.     InitializeComponent();
  4.     InitializeDictionaries();
  5.     InitializeOp();
  6.     stack = new Stack<double>();
  7.     this.Loaded += (s, ea) =>
  8.     {
  9.         if (!App.Current.IsRunningOutOfBrowser)
  10.             System.Windows.Browser.HtmlPage.Plugin.Focus();
  11.         displayTextBox.Focus();
  12.     };
  13.     clearOnNext = false;
  14.     errorState = false;
  15. }
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.

Expand|Select|Wrap|Line Numbers
  1. private void ProcessInput(string input)
  2. {
  3.     if (clearOnNext)
  4.     {
  5.         displayTextBox.Text = string.Empty;
  6.         clearOnNext = false;
  7.         errorState = false;
  8.     }
  9.     displayTextBox.Text += input;
  10. }
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

Expand|Select|Wrap|Line Numbers
  1. private void ProcessSpecial(string input)
  2. {
  3.     switch (input)
  4.     {
  5.         case "C":
  6.             if (displayTextBox.Text.Length > 0)
  7.                 displayTextBox.Text = string.Empty;
  8.             else
  9.             {
  10.                 stack.Clear();
  11.                 RefreshDepthText();
  12.             }
  13.             clearOnNext = false;
  14.             break;
  15.         case "B":
  16.             if (!clearOnNext && displayTextBox.Text.Length > 0)
  17.                 displayTextBox.Text = displayTextBox.Text.Substring(0, displayTextBox.Text.Length - 1);
  18.             break;
  19.         case ".":
  20.             if (!displayTextBox.Text.Contains("."))
  21.             {
  22.                 if (displayTextBox.Text.Length < 1 || errorState)
  23.                     ProcessInput("0.");
  24.                 else
  25.                     ProcessInput(".");
  26.             }
  27.             break;
  28.         case "E":
  29.             Enter();
  30.             break;
  31.         case "+":
  32.         case "-":
  33.         case "*":
  34.         case "/":
  35.             DoOp(input);
  36.             break;
  37.     }
  38. }
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:

Expand|Select|Wrap|Line Numbers
  1. private void Enter()
  2. {
  3.     if (!errorState)
  4.     {
  5.         stack.Push(current);
  6.         RefreshDepthText();
  7.         clearOnNext = true;
  8.     }
  9. }
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:

Expand|Select|Wrap|Line Numbers
  1. private void DoOp(string input)
  2. {
  3.     if (!clearOnNext)
  4.     {
  5.         stack.Push(current);
  6.     }
  7.     if (stack.Count < 2)
  8.     {
  9.         errorState = true;
  10.         clearOnNext = true;
  11.         displayTextBox.Text = "OUT OF STACK";
  12.         return;
  13.     }
  14.     double b = stack.Pop();
  15.     double a = stack.Pop();
  16.     stack.Push(op[input](a, B));
  17.     double res = stack.Peek();
  18.     if (res == double.NegativeInfinity || res == double.PositiveInfinity || res == double.NaN)
  19.     {
  20.         stack.Clear();
  21.         RefreshDepthText();
  22.         errorState = true;
  23.         clearOnNext = true;
  24.         displayTextBox.Text = "DIV BY ZERO";
  25.         return;
  26.     }
  27.     displayTextBox.Text = stack.Peek().ToString();
  28.     RefreshDepthText();
  29.     clearOnNext = true;
  30. }
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:

Expand|Select|Wrap|Line Numbers
  1. private void Button_Click(object sender, RoutedEventArgs e)
  2. {
  3.     string value = (sender as Button).Tag as string;
  4.     if (numKeys.ContainsValue(value))
  5.         ProcessInput(value);
  6.     else
  7.         ProcessSpecial(value);
  8.     displayTextBox.Focus();
  9. }
  10.  
  11. private void DisplayTextBox_KeyUp(object sender, KeyEventArgs e)
  12. {
  13.     if (opKeys.ContainsKey(e.Key))
  14.         ProcessSpecial(opKeys[e.Key]);
  15.     else if (numKeys.ContainsKey(e.Key))
  16.         ProcessInput(numKeys[e.Key].ToString());
  17.     else return;
  18. }
Miscellaneous methods

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

Expand|Select|Wrap|Line Numbers
  1. private void RefreshDepthText()
  2. {
  3.     stackDepthTextBlock.Text = stack.Count.ToString();
  4. }
  5.  
  6. private void copyButton_Click(object sender, RoutedEventArgs e)
  7. {
  8.     Clipboard.SetText(current.ToString());
  9. }
  10.  
  11. private void pasteButton_Click(object sender, RoutedEventArgs e)
  12. {
  13.     if (!Clipboard.ContainsText())
  14.         return;
  15.     string s = Clipboard.GetText();
  16.     double x;
  17.     if (double.TryParse(s, out  x))
  18.         displayTextBox.Text = s;
  19. }
And that's that. If you've followed the instructions properly, you'll have a calculator like this one:

Cross posted from my blog.
Nov 26 '10 #1
0 5788

Sign in to post your reply or Sign up for a free account.

Similar topics

1
by: ron | last post by:
have been stuck on this for several days now. I am trying to create a reverse polish calculator and I'm stuck at an intermediate stage. This is what I know I have to do (just not sure how to do it...
4
by: dogbite | last post by:
I need help with an understanding and completing a project in C++ which involves takingm a string of characters Example /*-A+BCDE as an input and outputting a postfix expression ABC+-D*E/. I have...
3
by: Paul | last post by:
I want to make a simple calculator program but dont know where to get started. This is not GUI but a simple terminal program. It would get input like this Enter number: 5 + 10
3
by: Saïd | last post by:
Hi, Is there a pure C library that emulate a calculator (with +-/* operations, plus log, sin,cos... functions). I would like to call such a program in a way resembling this: ...
11
by: Steve Lambert | last post by:
Hi Anyone know how to convert a bracketed boolean expression into reverse polish? Cheers
58
by: Flipke | last post by:
Hello, I wanna make a calculator where i can give the input in one word. for example : (15*3) / (2+3) I think i most read this in as a string and then share it in different parts to do the...
1
by: Eric Terrell | last post by:
Folks: I've posted the full source code to C# Programmable Calculator at my website: http://www.personalmicrocosms.com/html/cspcalc.html C# Programmable Calculator is a Reverse Polish...
6
by: PIEBALD | last post by:
Anyone got an infix to postfix (RPN) math notation converter? I've looked around a bit and haven't found anything quite what I want. I just want a method that will take a string in infix notation...
14
by: arnuld | last post by:
Stroustrup starts chapter 6 with a programme for desk-calculator: here is a grammer for the langugae accepted by the calcualtor: program: END // END is end-of-input...
4
by: JOYCE | last post by:
Hello, I'm a new learner.I want to know how to make a calculator in C programming. I hope someone can help me .Thanks!
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...
0
by: ryjfgjl | last post by:
In our work, we often need to import Excel data into databases (such as MySQL, SQL Server, Oracle) for data analysis and processing. Usually, we use database tools like Navicat or the Excel import...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.