Click here to Skip to main content
15,860,859 members
Articles / Desktop Programming / WPF

WPF Suanpan

Rate me:
Please Sign up or sign in to vote.
4.79/5 (15 votes)
3 Apr 2011CPOL4 min read 41K   1.1K   12   17
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,

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
' 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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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:

VB.NET
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Kenya Kenya
Experienced C# software developer with a passion for WPF.

Awards,
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • CodeProject MVP 2021

Comments and Discussions

 
QuestionMy vote 5 Pin
Duncan Edwards Jones13-Mar-14 2:24
professionalDuncan Edwards Jones13-Mar-14 2:24 
GeneralRe: My vote 5 Pin
Meshack Musundi13-Mar-14 2:32
professionalMeshack Musundi13-Mar-14 2:32 
QuestionMy vote of 5 Pin
Kenneth Haugland14-Oct-13 8:41
mvaKenneth Haugland14-Oct-13 8:41 
GeneralRe: My vote of 5 Pin
Meshack Musundi14-Oct-13 23:10
professionalMeshack Musundi14-Oct-13 23:10 
GeneralMy vote of 3 Pin
Jyothikarthik_N7-Apr-11 19:14
Jyothikarthik_N7-Apr-11 19:14 
GeneralRe: My vote of 3 Pin
Meshack Musundi7-Apr-11 21:32
professionalMeshack Musundi7-Apr-11 21:32 
GeneralMy vote of 3 Pin
ring_03-Apr-11 20:43
ring_03-Apr-11 20:43 
GeneralRe: My vote of 3 Pin
Sacha Barber4-Apr-11 0:36
Sacha Barber4-Apr-11 0:36 
GeneralRe: My vote of 3 Pin
Meshack Musundi4-Apr-11 1:06
professionalMeshack Musundi4-Apr-11 1:06 
GeneralRe: My vote of 3 Pin
Sacha Barber4-Apr-11 1:43
Sacha Barber4-Apr-11 1:43 
GeneralRe: My vote of 3 Pin
Meshack Musundi4-Apr-11 2:12
professionalMeshack Musundi4-Apr-11 2:12 
GeneralRe: My vote of 3 Pin
Meshack Musundi4-Apr-11 0:57
professionalMeshack Musundi4-Apr-11 0:57 
GeneralRe: My vote of 3 Pin
ring_04-Apr-11 4:10
ring_04-Apr-11 4:10 
Do you really think there is any learning material either it be WPF or anything than a couple of blend screen shots?.
As I have already voted 5 on your earlier articles, I don't think any reason that I like about this article so 3 for you.
Cheers Smile | :) .
Man having 1 bit brain with parity error

GeneralOrganize your articles to a book Pin
karukutimothy1-Apr-11 4:15
karukutimothy1-Apr-11 4:15 
GeneralRe: Organize your articles to a book Pin
Meshack Musundi1-Apr-11 5:15
professionalMeshack Musundi1-Apr-11 5:15 
GeneralMy vote of 5 Pin
Slacker0071-Apr-11 1:58
professionalSlacker0071-Apr-11 1:58 
GeneralRe: My vote of 5 Pin
Meshack Musundi1-Apr-11 2:46
professionalMeshack Musundi1-Apr-11 2:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.