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 an 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 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 it 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

Reseting 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
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"