Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Suanpan

0.00/5 (No votes)
3 Apr 2011 1  
A WPF abacus
Screenshot_1.png

Introduction

The WPF Suanpan is simply an abacus existing in XAML form. The goal of this article is not to make you a follower of bead arithmetic, though you are free to convert, but to give you an idea of how I went about creating this application.

Suanpan

The suanpan is a Chinese abacus that can be used for both decimal and hexadecimal computation. With the suanpan, you can carry out addition, subtraction, multiplication, division, square root, and cube root operations.

The suanpan is divided into two sections. The top section contains beads referred to as heaven beads while the bottom section, below the separator beam, contains beads referred to as earth beads. Each column of the suanpan contains two heaven beads and five earth beads. A heaven bead has a value of 5 while an earth bead has a value of 1.

The rightmost column of the suanpan represents ones, the column to its immediate left tens, then hundreds, thousands,... The following screenshot represents the number 804,

Screenshot_2.png

If you want to learn more about the suanpan, the following resources could prove to be useful:

Requirements

To run the project provided from the download link above, you require either of the following:

  • Visual Studio 2010
  • Expression Blend

If you're using VS 2008, you can download the source files from here.

NB: If you're using the Express edition of Visual Studio, ensure that you open the solution using Visual Basic Express.

WPF Suanpan

How It Works

To move a bead/beads, place the cursor over a bead, press the left mouse button and move the bead/beads up or down. To reset the beads so that they are all away from the separator beam, double-click the separator beam.

To drag around WPF Suanpan, do so using the 'wooden' frames.

Design and Layout

The frame of the WPF Suanpan is made of polished rosewood, the heaven beads of oak... Ok, that's not it. I designed the WPF Suanpan in Expression Design and added some extra elements in Expression Blend.

There are two content controls of interest, the HeavenGrid and the EarthGrid.

Screenshot_3.png

Screenshot_4.png

The HeavenGrid contains ten Canvases and so does the EarthGrid.

Screenshot_5.png

There are two UserControls, EarthBead and HeavenBead. The element of interest in both is the Thumb control, which is clipped in both and the Opacity set to zero. The following image shows the Thumb in the EarthBead control with its Opacity property at 100%.

Screenshot_6.png

The Code

During the MainWindow Loaded event, we add beads to their respective Canvases,

Private Sub MainWindow_Loaded(ByVal sender As Object, _
                              ByVal e As System.Windows.RoutedEventArgs) _
                              Handles Me.Loaded
    LoadEarthBeads()
    LoadHeavenBeads()
End Sub

In the event handler above, we call the LoadEarthBeads method that adds EarthBeads to the Canvases in EarthGrid:

Private Sub LoadEarthBeads()
    For Each eCanvas As Canvas In EarthGrid.Children
        Dim y As Double = eCanvas.ActualHeight
        Dim i As Integer = 1
        Do
            Dim eB As New EarthBead()
            y -= eB.Height
            eB.EarthNumber = i
            Canvas.SetLeft(eB, 2)
            Canvas.SetTop(eB, y)
            eCanvas.Children.Add(eB)
            i += 1
        Loop While i < 6
        i = 0
    Next
End Sub

EarthBead has a variable named EarthNumber which is assigned a value before a bead is added to a Canvas. The EarthBeads will have values like so:

Screenshot_7.png

The LoadHeavenBeads method, that is also called in the MainWindow Loaded event handler, adds HeavenBeads to the respective Canvases in HeavenGrid:

Private Sub LoadHeavenBeads()
    For Each hCanvas As Canvas In HeavenGrid.Children
        Dim i As Integer
        Dim y As Double = 0
        Do
            Dim hB As New HeavenBead()
            Canvas.SetLeft(hB, 2)
            Canvas.SetTop(hB, y)
            hCanvas.Children.Add(hB)
            y += hB.Height
            i += 1
            hB.HeavenNumber = i
        Loop While i < 2
        i = 0
    Next
End Sub

Each HeavenBead's HeavenNumber is also assigned a value:

Screenshot_8.png

EarthBead

To move a EarthBead, we cater to the DragDelta event of its Thumb control:

Private Sub EarthThumb_DragDelta(ByVal sender As Object, _
                                 ByVal e As DragDeltaEventArgs) _
                                 Handles EarthThumb.DragDelta
    ' Check whether movement is vertical.
    If Math.Abs(e.VerticalChange) > Math.Abs(e.HorizontalChange) Then
        If e.VerticalChange < 0 Then
            MoveEarthBeadUp(e)
        Else
            MoveEarthBeadDown(e)
        End If
    Else
        Exit Sub
    End If
End Sub

In the event handler above, MoveEarthBeadUp is called to move the bead upwards:

Private Sub MoveEarthBeadUp(ByVal e As DragDeltaEventArgs)
    Dim y As Double = Canvas.GetTop(Me)
    If (Me.EarthNumber = 5) Then
        If (y > 0) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, 0)
        End If
    ElseIf (Me.EarthNumber = 4) Then
        If (y > Me.Height) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, Me.Height)
        End If
    ElseIf (Me.EarthNumber = 3) Then
        If (y > (Me.Height * 2)) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, (Me.Height * 2))
        End If
    ElseIf (Me.EarthNumber = 2) Then
        If (y > (Me.Height * 3)) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, (Me.Height * 3))
        End If
    ElseIf (Me.EarthNumber = 1) Then
        If (y > (Me.Height * 4)) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, (Me.Height * 4))
        End If
    End If
    MoveBeadsAbove(e)
End Sub

The MoveBeadsAbove method, that is called in the method above, moves upwards any beads that the selected bead collides with on its upward journey:

' Move beads/bead above the bead being moved
' when this bead collides with bead above.
Private Sub MoveBeadsAbove(ByVal e As DragDeltaEventArgs)
    Dim earthCanvas As Canvas = CType(Me.Parent, Canvas)

    For Each eB As EarthBead In earthCanvas.Children
        If (eB.EarthNumber <> Me.EarthNumber) Then
            Dim My_Y As Double = Canvas.GetTop(Me)
            Dim eB_Y As Double = Canvas.GetTop(eB)

            If (eB_Y < My_Y) And (My_Y < (eB_Y + eB.Height)) Then
                eB.EarthThumb_DragDelta(Nothing, e)
            End If
            ' Ensure bead above has stopped at its limit.
            If (eB.EarthNumber = 5) And (eB_Y < 0) Then
                Canvas.SetTop(eB, 0)
            ElseIf (eB.EarthNumber = 4) And (eB_Y < eB.Height) Then
                Canvas.SetTop(eB, eB.Height)
            ElseIf (eB.EarthNumber = 3) And (eB_Y < (eB.Height * 2)) Then
                Canvas.SetTop(eB, (eB.Height * 2))
            ElseIf (eB.EarthNumber = 2) And (eB_Y < (eB.Height * 3)) Then
                Canvas.SetTop(eB, (eB.Height * 3))
            ElseIf (eB.EarthNumber = 1) And (eB_Y < (eB.Height * 4)) Then
                Canvas.SetTop(eB, (eB.Height * 4))
            End If
        End If
    Next
End Sub

In the EarthThumb DragDelta event handler, we also call MoveEarthBeadDown in order to move a bead downwards:

Private Sub MoveEarthBeadDown(ByVal e As DragDeltaEventArgs)
    Dim y As Double = Canvas.GetTop(Me)
    Dim earthCanvas As Canvas = CType(Me.Parent, Canvas)
    Dim parentHeight As Double = earthCanvas.ActualHeight

    If (Me.EarthNumber = 5) Then
       If (y < (parentHeight - (Me.Height * 5))) Then
            Canvas.SetTop(Me, (y + shift))
       Else
            Canvas.SetTop(Me, (parentHeight - (Me.Height * 5)))
       End If
    ElseIf (Me.EarthNumber = 4) Then
        If (y < (parentHeight - (Me.Height * 4))) Then
            Canvas.SetTop(Me, (y + shift))
        Else
            Canvas.SetTop(Me, (parentHeight - (Me.Height * 4)))
        End If
    ElseIf (Me.EarthNumber = 3) Then
        If (y < (parentHeight - (Me.Height * 3))) Then
            Canvas.SetTop(Me, (y + shift))
        Else
            Canvas.SetTop(Me, (parentHeight - (Me.Height * 3)))
        End If
    ElseIf (Me.EarthNumber = 2) Then
        If (y < (parentHeight - (Me.Height * 2))) Then
            Canvas.SetTop(Me, (y + shift))
        Else
            Canvas.SetTop(Me, (parentHeight - (Me.Height * 2)))
        End If
    ElseIf (Me.EarthNumber = 1) Then
        If (y < (parentHeight - Me.Height)) Then
            Canvas.SetTop(Me, (y + shift))
        Else
            Canvas.SetTop(Me, (parentHeight - Me.Height))
        End If
    End If
    MoveBeadsBelow(e)
End Sub

The MoveBeadsBelow method moves downwards any bead that the selected bead collides with on its downward journey:

Private Sub MoveBeadsBelow(ByVal e As DragDeltaEventArgs)
    Dim earthCanvas As Canvas = CType(Me.Parent, Canvas)
    Dim parentHeight As Double = earthCanvas.ActualHeight

    For Each eB As EarthBead In earthCanvas.Children
        If (eB.EarthNumber <> Me.EarthNumber) Then
            Dim My_Y As Double = Canvas.GetTop(Me)
            Dim eB_Y As Double = Canvas.GetTop(eB)

            If (eB_Y > My_Y) And ((My_Y + Me.Height) > eB_Y) Then
                eB.EarthThumb_DragDelta(Nothing, e)
            End If
            ' Ensure bead below has stopped at its limit.
            If (eB.EarthNumber = 5) And _
            (eB_Y > (parentHeight - (Me.Height * 5))) Then
                Canvas.SetTop(eB, (parentHeight - (Me.Height * 5)))
            ElseIf (eB.EarthNumber = 4) And _
            (eB_Y > (parentHeight - (Me.Height * 4))) Then
                Canvas.SetTop(eB, (parentHeight - (Me.Height * 4)))
            ElseIf (eB.EarthNumber = 3) And _
            (eB_Y > (parentHeight - (Me.Height * 3))) Then
                Canvas.SetTop(eB, (parentHeight - (Me.Height * 3)))
            ElseIf (eB.EarthNumber = 2) And _
            (eB_Y > (parentHeight - (Me.Height * 2))) Then
                Canvas.SetTop(eB, (parentHeight - (Me.Height * 2)))
            ElseIf (eB.EarthNumber = 1) And _
            (eB_Y > (parentHeight - Me.Height)) Then
                Canvas.SetTop(eB, (parentHeight - Me.Height))
            End If
        End If
    Next
End Sub

HeavenBead

To move a HeavenBead, we cater to its Thumb control's DragDelta event:

Private Sub HeavenThumb_DragDelta(ByVal sender As Object, _
                                  ByVal e As DragDeltaEventArgs) _
                                  Handles HeavenThumb.DragDelta
    ' Check whether movement is vertical.
    If Math.Abs(e.VerticalChange) > Math.Abs(e.HorizontalChange) Then
        If e.VerticalChange < 0 Then
            MoveHeavenBeadUp(e)
        Else
            MoveHeavenBeadDown(e)
        End If
    Else
        Exit Sub
    End If
End Sub

The MoveHeavenBeadUp method is called to move a bead upwards:

Private Sub MoveHeavenBeadUp(ByVal e As DragDeltaEventArgs)
    Dim y As Double = Canvas.GetTop(Me)

    If (Me.HeavenNumber = 1) Then
        If (y > 0) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, 0)
        End If
    ElseIf (Me.HeavenNumber = 2) Then
        If (y > Me.Height) Then
            Canvas.SetTop(Me, (y - shift))
        Else
            Canvas.SetTop(Me, Me.Height)
        End If
    End If
    MoveBeadAbove(e)
End Sub

To move the bead that the selected bead collides with on its upward journey, the MoveBeadAbove method is called:

Private Sub MoveBeadAbove(ByVal e As DragDeltaEventArgs)
    Dim heavenCanvas As Canvas = CType(Me.Parent, Canvas)

    For Each hB As HeavenBead In heavenCanvas.Children
        If (hB.HeavenNumber <> Me.HeavenNumber) Then
            Dim My_Y As Double = Canvas.GetTop(Me)
            Dim hB_Y As Double = Canvas.GetTop(hB)

            If (hB_Y < My_Y) And (My_Y < (hB_Y + hB.Height)) Then
                hB.HeavenThumb_DragDelta(Nothing, e)
            End If
            ' Ensure bead above has stopped at its limit.
            If (hB_Y < 0) Then
                Canvas.SetTop(hB, 0)
            End If
        End If
    Next
End Sub

The MoveHeavenBeadDown method, that is called in HeavenThumb's DragDelta event handler, moves a bead downwards:

Private Sub MoveHeavenBeadDown(ByVal e As DragDeltaEventArgs)
    Dim y As Double = Canvas.GetTop(Me)
    Dim heavenCanvas As Canvas = CType(Me.Parent, Canvas)
    Dim parentHeight As Double = heavenCanvas.Height

    If (Me.HeavenNumber = 2) Then
        If (y < (parentHeight - Me.Height)) Then
            Canvas.SetTop(Me, (y + shift))
        Else
            Canvas.SetTop(Me, (parentHeight - Me.Height))
        End If
    ElseIf (Me.HeavenNumber = 1) Then
        If (y < (parentHeight - (Me.Height * 2))) Then
            Canvas.SetTop(Me, (y + shift))
        Else
            Canvas.SetTop(Me, (parentHeight - (Me.Height * 2)))
        End If
    End If
    MoveBeadBelow(e)
End Sub

To move the bead that the selected bead collides with on its downward journey, the MoveBeadBelow method is called:

Private Sub MoveBeadBelow(ByVal e As DragDeltaEventArgs)
    Dim heavenCanvas As Canvas = CType(Me.Parent, Canvas)
    Dim parentHeight As Double = heavenCanvas.ActualHeight

    For Each hB As HeavenBead In heavenCanvas.Children
        If (hB.HeavenNumber <> Me.HeavenNumber) Then
            Dim My_Y As Double = Canvas.GetTop(Me)
            Dim hB_Y As Double = Canvas.GetTop(hB)

            If (hB_Y > My_Y) And ((My_Y + Me.Height) > hB_Y) Then
                hB.HeavenThumb_DragDelta(Nothing, e)
            End If
            ' Ensure bead below has stopped at its limit.
            If (hB_Y > (parentHeight - hB.Height)) Then
                Canvas.SetTop(hB, (parentHeight - Me.Height))
            End If
        End If
    Next
End Sub

Resetting WPF Suanpan

Remember I explained in the How it Works section that to reset the beads, you double-click on the separator beam. What you're actually double-clicking is a button whose Opacity is set to zero:

Private Sub ResetButton_MouseDoubleClick(ByVal sender As Object, _
                     ByVal e As System.Windows.Input.MouseButtonEventArgs) _
                     Handles ResetButton.MouseDoubleClick
    ' Clear heaven beads.
    For Each hCanvas As Canvas In HeavenGrid.Children
        hCanvas.Children.Clear()
    Next
    ' Clear earth beads.
    For Each eCanvas As Canvas In EarthGrid.Children
        eCanvas.Children.Clear()
    Next
    ' Reload beads.
    LoadEarthBeads()
    LoadHeavenBeads()
End Sub

Conclusion

That's it. I hope you enjoyed reading the article and that it was useful in one way or another. If you've been influenced into becoming a practitioner of bead arithmetic, then good for you. I personally favor button arithmetic.

History

  • 1st Apr, 2011: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here