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,
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
.
The HeavenGrid
contains ten Canvas
es and so does the EarthGrid
.
There are two UserControl
s, 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%.
The Code
During the MainWindow
Loaded
event, we add beads to their respective Canvas
es,
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 EarthBead
s to the Canvas
es 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 EarthBead
s will have values like so:
The LoadHeavenBeads
method, that is also called in the MainWindow
Loaded
event handler, adds HeavenBeads
to the respective Canvas
es 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:
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
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:
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
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
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
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
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
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
For Each hCanvas As Canvas In HeavenGrid.Children
hCanvas.Children.Clear()
Next
For Each eCanvas As Canvas In EarthGrid.Children
eCanvas.Children.Clear()
Next
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