The purposes of this project was to create an OOP Calculator. More precisely, the Requirements Specification is:

The user should be able to implement a calculation consisting of performing a mathematical operation on two numbers and retrieve a result.

 

The Core Application

The words underlined were:

  • Calculation - The Core classes of the application should include a Calculation class used for communicating between the core classes and the GUI.
  • Operation - The Operator would consist of a one character variable in the Calculation class and there is no reason to make it a class.
  • Two numbers - The two numbers involved in the calculation would both be represented by an instance of a Number class as it is appropriate  to represent each number by a Decimal value and a String value, mainly due to the method of inputting numbers. It would also be useful to have methods to append and remove digits in this class.
  • Result - As with the Operator there is no reason to make this a class, but also in this case it is not necessary even as a variable either.

 

    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).


The Number class:




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

 


The GUI

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.

This is the Form code:

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.

This is the restrictedTextBox code:


''' <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




Other Resources

Download here...