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:
This is a postfix expression, equating to 11. The logical steps to evaluate it are:
Consider a more complicated expression:
This evaluates to -80. It is the equivelant of 10 - (( 4 + 6 ) * 9) in Infix (standard) notation. The same basic steps are followed here.
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 [ENTER] 6 [+] 3[*].
For
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:
We'll also need some Dictionaries to relate keyboard keys with strings, since we're not trusting the users to enter numbers by themselves:
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:
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.
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:
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.
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.
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:
This simply parses what's in the TextBox as a double.
Initialization
Here's how our constructor will look:
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.
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
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:
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:
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:
Miscellaneous methods
These don't hold much importance, just necessary for little things:
And that's that. If you've followed the instructions properly, you'll have a calculator like this one:
Cross posted from my blog.
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:
Code:
5 6 +
- Push(5)
- Push(6)
- var b = Pop()
- var a = Pop()
- Push(a + b)
- Peek()
Consider a more complicated expression:
Code:
10 4 6 + 9 * -
- 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:
Code:
private Stack<double> stack;
Code:
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");
}
Code:
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);
}
This doesn't go in the project. This is just an example of using Funcs in a dictionary.
Code:
double a = 5, b = 6; double result = op["+"](5, 6);
Also, two more bools we'll need to keep track of state:
Code:
private bool clearOnNext, errorState;
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.
Code:
<Grid x:Name="LayoutRoot" Background="White">
<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>
Code:
<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>
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:
Code:
private double current
{
get
{
if (displayTextBox.Text == string.Empty)
displayTextBox.Text = "0";
return double.Parse(displayTextBox.Text);
}
}
Initialization
Here's how our constructor will look:
Code:
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;
}
Methods
The simplest method we need to add has to handle input. We'll call this one from handlers.
Code:
private void ProcessInput(string input)
{
if (clearOnNext)
{
displayTextBox.Text = string.Empty;
clearOnNext = false;
errorState = false;
}
displayTextBox.Text += input;
}
We'll also need a method to process special characters like "Clear", "Enter", "Backspace" , and "Decimal" as well as the operators
Code:
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;
}
}
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:
Code:
private void Enter()
{
if (!errorState)
{
stack.Push(current);
RefreshDepthText();
clearOnNext = true;
}
}
Here's the method where we actually do an operation:
Code:
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;
}
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:
Code:
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;
}
These don't hold much importance, just necessary for little things:
Code:
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;
}
Cross posted from my blog.