473,657 Members | 2,366 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

[Silverlight C#] Reverse Polish Notation Calculator Tutorial

Curtis Rutland
3,256 Recognized Expert Specialist
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 <ContentPresent er> 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_Cli ck". 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.Positive Infinity. 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 5841

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

Similar topics

1
4255
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 to much of a newbie :( ) I'm trying to call method, isOp, in the int method, evaluate. While in INT evaluate method and in a while(hasNextToken) loop. Calling isOp on each token, nextToken. If it returns False, convert to (Integer) and push...
4
4720
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 to use a Stack and a Queue. I have been given the following main function which I can not change. #include"a:utility.h" #incude"a:Stack.h" #include"a:Queue.h"
3
5209
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
3316
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: result("(4+6)*sin(pi/3)"); Or if the calculator is a reverse polish one result("4 6 + pi 3 / sin *");
11
2274
by: Steve Lambert | last post by:
Hi Anyone know how to convert a bracketed boolean expression into reverse polish? Cheers
58
4088
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 calculation. But i don't now how tho start with it. Can somebody help me. Thanks in advanced.
1
6934
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 Notation (RPN) calculator with custom buttons programmable in C#.
6
5566
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 and return a string in Reverse Polish Notation.
14
4956
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 expr_list END expr_list: expression PRINT // PRINT is semicolon expression PRINT expr_list
4
5901
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!
0
8399
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
8312
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
8827
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
8732
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
0
8606
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
7337
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
0
4159
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
4318
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
2
1622
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

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.