The requirements for this application are that the user should be able to implement a calculation consisting of performing a mathematical operation on two numbers and retrieve a result.
The words underlined were:
Figure 1. The method used for saving
the result allows long strings of multiple calculations.
So after analyzing the project requirements the core application consists of just two classes.
Figure 1.1. The distinct core classes
A Calculation class with several properties used in differentiating between various input values, with one Public function the push method which is used for passing and retrieving values to and from the GUI. The Calculation class has three notable properties, two to represent Number class instances and an [Operator] property. I haven't posted the Calculation class in this article, but it's available in the download.
This shows the instances of the classes and the Relationship between them:
Figure 1.2. Instances and Relationship
Figure 1.3. Core classes diagram
The Number class has a Decimal property and a String property, both of which are used to keep track of numbers and two functions for adding and removing digits.
By some juggling of the two Number classes property values it's possible to perform long strings of multiple calculations (see Figure 1).
Public Class Number #Region " properties" Private _value As Decimal Public Property value() As Decimal Get Return _value End Get Set(ByVal value As Decimal) _value = value End Set End Property Private _valueString As String = "" Public Property valueString() As String Get If _valueString.StartsWith(".") Then Return "0" & _valueString End If Return _valueString End Get Set(ByVal value As String) _valueString = value If _valueString <> "." AndAlso _valueString <> "" AndAlso _valueString <> "-" Then Me.value = CDec(_valueString) Else Me.value = 0D End If End Set End Property #End Region #Region " functions" ''' <summary> ''' handles parsing button input to decimal ''' </summary> ''' <param name="aValue">the button text</param> ''' <returns></returns> Public Function appendDigit(aValue As String) As String If aValue = "." Then If Not Me.valueString.Contains(".") Then Me.valueString &= aValue End If Else Me.valueString &= aValue End If If Not valueString = "." AndAlso _valueString <> "-" Then Me.value = CDec(valueString) Else Me.value = 0 End If Return Me.valueString End Function ''' <summary> ''' handles removing digits with the backspace button, ''' then parsing the remaining digits as decimal ''' </summary> ''' <returns></returns> Public Function backSpace() As String If Me.valueString.Length > 0 Then Me.valueString = Me.valueString.Substring(0, valueString.Length - 1) End If If Not Me.valueString = "." AndAlso Not Me.valueString = "" AndAlso Not Me.valueString = "-" Then Me.value = CDec(Me.valueString) Else Me.value = 0 End If Return Me.valueString End Function #End Region End Class
Figure 2. The GUI.
In terms of component controls the GUI consists of a Panel, 18 Buttons, an extended TextBox, and a Label. At runtime the TextBox and the Label appear as one control (see Figure 1.). The TextBox is restricted to allow no other input except the programmatic input from the Core classes.
The Form code is limited to handling the Panel_Paint event where it draws a border around the Label and TextBox, handling all of the Button click events in one handler, and capturing keyboard input. The 18 Buttons are the only Controls that respond to user input, either through mouseclicks or by typing the number, operator, or controlchar keys.
Public Class calcForm ''' <summary> ''' this is the calculator object ''' </summary> Private calc As New Calculation ''' <summary> ''' the textbox is actually a textbox with a label above it. ''' this draws a border around both controls to give the appearance of one control ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub Panel1_Paint(sender As Object, e As PaintEventArgs) Handles Panel1.Paint Dim r As New Rectangle(TextBox1.Left - 1, Label1.Top - 1, Label1.Width + 2, Label1.Height + TextBox1.Height + 2) ControlPaint.DrawBorder3D(e.Graphics, r, Border3DStyle.SunkenInner) End Sub ''' <summary> ''' this sets up the buttons for keyboard or mouseclick input ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Me.KeyPreview = True For Each b As Button In Panel1.Controls.OfType(Of Button) AddHandler b.Click, AddressOf button_clicked Next End Sub ''' <summary> ''' onclick the button's text is sent to the calculator object's push method ''' which handles all input, and returns the textual output for the textbox and the label ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub button_clicked(sender As Object, e As EventArgs) Dim output() As String = calc.push(DirectCast(sender, Button).Text) TextBox1.Text = output(0) Label1.Text = output(1) End Sub ''' <summary> ''' this converts keyboard input into button clicks ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> Private Sub Form1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles Me.KeyPress If e.KeyChar = Chr(Keys.Back) Then Button1.PerformClick() Else Dim btn As Button = Panel1.Controls.OfType(Of Button).FirstOrDefault(Function(b) b.Text.ToLower = e.KeyChar) If Not btn Is Nothing Then btn.PerformClick() End If End If End Sub End Class
The extended TextBox assigns a new empty ContextMenuStrip to the TextBox, which has the effect of removing the right click menu. The Cut, Copy, Paste, Clear, and Undo keyboard shortcuts are also detected and nullified.
''' <summary> ''' removes the textbox contextmenustrip and disables keyboard shortcuts ''' </summary> Public Class restrictedTextBox Inherits TextBox Const WM_CUT As Integer = &H300 Const WM_COPY As Integer = &H301 Const WM_PASTE As Integer = &H302 Const WM_CLEAR As Integer = &H303 Const WM_UNDO As Integer = &H304 Public Sub New() Me.ContextMenuStrip = New ContextMenuStrip End Sub Protected Overrides Sub WndProc(ByRef m As Message) Dim disabledOperations() As Integer = {WM_CUT, WM_COPY, WM_PASTE, WM_CLEAR, WM_UNDO} If disabledOperations.Contains(m.Msg) Then Return MyBase.WndProc(m) End Sub End Class
The example project is available for download here...