Φτιάχνοντας ένα mapping control
Σε συνέχεια του post με θέμα «Σχεδιασμός φόρμας για mapping πεδίων μεταξύ πινάκων» έγραψα αυτό το tutorial για το πως μπορεί να κατασκευαστεί ένα τέτοιο control. Η ιδέα είναι να παρουσιαστεί το πως γίνεται η υλοποίηση βήμα προς βήμα, όχι τόσο με αναλυτικές πληροφορίες του τύπου «κάντε κλικ εκεί, επιλέξτε αυτό, κάντε copy/paste αυτό» αλλά παρουσιάζοντας τον τρόπο και που μπορεί κάποιος να λύσει ένα πρόβλημα, δεν είχα ασχοληθεί με GDI+ στο παρελθόν, θέτοντας στόχους και λύνοντας τα προβλήματα ένα-ένα μέχρι να φτάσει στο τελικό ζητούμενο.
Σε αυτό το tutorial θα δούμε
- Πως δουλεύουμε με το ListBox control
- Πως χρησιμοποιούμε το DGI+
- Πως δουλεύουμε με το ArrayList
- Πως κατασκευάζουμε κλάσεις με τις οποίες γεμίζουμε το ArrayList
- Πως δουλεύουμε με το drag n drop
- Πως κάνουμε extend ένα υπάρχον control για να του δώσουμε επιπλέον λειτουργικότητα
- Πως κατασκευάζουμε ένα User Control
Στάδιο 1
Το βασικό χαρακτηριστικό του control που θέλουμε να φτιάξουμε είναι ότι σχεδιάζεται μια γραμμή που ενώνει δύο controls. Δεν χρειάζεται απευθείας να πάμε σε ListBox controls, αρχικά ας δούμε πως θα μπορούσαμε να ενώσουμε δύο TextBox άσχετα με τη θέση τους. Το μόνο που μας ενδιαφέρει είναι ότι το ένα είναι στα αριστερά και το άλλο στα δεξιά.
Η ρουτίνα μας είναι η παρακάτω:
Private Sub PaintMe(ByVal fromControl As Control, ByVal toControl As Control)
Dim g As Graphics = CreateGraphics()
Dim pen2 As New Pen(Color.Red, 3)
Dim p1 As Integer = fromControl.Left + fromControl.Width
Dim p2 As Integer = fromControl.Top + (fromControl.Height / 2)
Dim p3 As Integer = toControl.Left
Dim p4 As Integer = toControl.Top + (toControl.Height / 2)
g.DrawLine(pen2, p1, p2, p3, p4)
g.Dispose()
End Sub
Για να μπορέσουμε να σχεδιάσουμε πάνω στην φόρμα, χρειάζεται να χρησιμοποιήσουμε ένα Graphics object. Όλα τα controls έχουν τη μέθοδο CreateGraphics. Τώρα, απλά την καλούμε και implicitly παίρνουμε το Graphics object της φόρμας. Κατόπιν, καθορίζουμε ένα Pen object (χρώμα και πάχος) και ύστερα βρίσκουμε τα σημεία Χ και Y που θα αρχίζει και θα τελειώνει η γραμμή μας. Αυτό γίνεται με reference τα δύο controls που μέσα στην ρουτίνα τα ονομάζουμε fromControl και toControl αντίστοιχα. Τέλος, με την DrawLine method ζωγραφίζουμε τη γραμμή και κατόπιν κάνουμε Dispose το Graphics object.
Για να τρέξει το δοκιμαστικό αυτό προγραμματάκι, χρειαζόμαστε δύο TextBox controls πάνω στη φόρμα και ένα Button που στο Click event θα καλούμε τη PaintMe ρουτίνα.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
PaintMe(TextBox1, TextBox2)
End Sub
Επίσης, μην ξεχάσετε να κάνετε Import το Namespace System.Drawing.Drawing2D

Στάδιο 2
Ας δούμε τώρα πως θα εφαρμόσουμε τα παραπάνω μεταξύ δύο ListBox controls. H φιλοσοφία είναι η ίδια, ωστόσο υπάρχει μια σημαντική διαφορά. Στην PaintMe ρουτίνα δεν μπορούμε να παίξουμε σε επίπεδο control γιατί μας ενδιαφέρει ποιο Item από το ListBox είναι επιλεγμένο. Εδώ θα μας βοηθήσει ένα method που ονομάζεται GetItemRectangle(index) που παίρνει ως παράμετρο το index του item κι επιστρέφει ένα Rectangle object, του οποίου μας ενδιαφέρουν τα X και Y properties.
Θα φτιάξουμε λοιπόν δύο functions για να διαβάσουμε αυτά τα X και Y.
Private Function GetItemCoordinates(ByVal ListBoxControl As ListBox, ByVal itemIndex As Integer) As Drawing.Point
GetItemCoordinates.X = ListBoxControl.Left + ListBoxControl.Width + 1
GetItemCoordinates.Y = ListBoxControl.GetItemRectangle(itemIndex).Y
End Function
Private Function GetItemCoordinates2(ByVal ListBoxControl As ListBox, ByVal itemIndex As Integer) As Drawing.Point
GetItemCoordinates2.X = ListBoxControl.Left - 1
GetItemCoordinates2.Y = ListBoxControl.GetItemRectangle(itemIndex).Y
End Function
Με την πρώτη (GetItemCoordinates) διαβάζουμε τα X και Y του επιλεγμένου Item του αριστερού listbox και με την δεύτερη (GetItemCoordinates2) του δεξιού. Αντίστοιχα, η PaintMe ρουτίνα μετατρέπεται ως εξής:
Private Sub PaintMe(ByVal fromControl As ListBox, ByVal toControl As ListBox)
Dim g As Graphics = CreateGraphics()
Dim pen2 As New Pen(Color.Red, 2)
Dim p1 As Integer = GetItemCoordinates(fromControl, fromControl.SelectedIndex).X
Dim p2 As Integer = GetItemCoordinates(fromControl, fromControl.SelectedIndex).Y
Dim p3 As Integer = GetItemCoordinates2(toControl, toControl.SelectedIndex).X
Dim p4 As Integer = GetItemCoordinates2(toControl, toControl.SelectedIndex).Y
g.DrawLine(pen2, p1, p2, p3, p4)
End Sub
Αν τρέξουμε το πρόγραμμα (αφού βάλουμε 20 Items στα δύο ListBox) για να δούμε τι έχουμε φτιάξει, θα παρατηρήσουμε ότι δεν έχουμε πετύχει ακριβώς τις συντεταγμένες αρχής και τέλους ως προς το κατακόρυφο άξονα.
Oπότε ας αλλάξουμε τα δύο functions ώστε να επιστρέφουν σωστές τιμές:
Private Function GetItemCoordinates(ByVal ListBoxControl As ListBox, ByVal itemIndex As Integer) As Drawing.Point
GetItemCoordinates.X = ListBoxControl.Left + ListBoxControl.Width + 1
GetItemCoordinates.Y = ListBoxControl.Top + ListBoxControl.GetItemRectangle(itemIndex).Y + (ListBoxControl.GetItemRectangle(itemIndex).Height / 2)
End Function
Private Function GetItemCoordinates2(ByVal ListBoxControl As ListBox, ByVal itemIndex As Integer) As Drawing.Point
GetItemCoordinates2.X = ListBoxControl.Left - 1
GetItemCoordinates2.Y = ListBoxControl.Top + ListBoxControl.GetItemRectangle(itemIndex).Y + (ListBoxControl.GetItemRectangle(itemIndex).Height / 2)
End Function
Επίσης, αργότερα θα πρέπει να προστεθούν έλεγχοι ώστε η γραμμή να ζωγραφίζεται μόνο όταν έχουν επιλεχθεί και τα δύο Items (αριστερό και δεξιό).

Στάδιο 3
Καλύτερα θα είναι να ενοποιήσουμε τα δύο functions αφού κανουν παρόμοια δουλειά. Θα ορίσουμε λοιπόν ένα Enumeration
Private Enum enumSide
Left
Right
End Enum
το οποίο θα χρησιμοποιήσουμε ως παράμετρο στο τελικό function ώστε να του λέμε ποιο control (αριστερό/δεξί) ζητάμε
Private Function GetItemCoordinates(ByVal ListBoxControl As ListBox, ByVal itemIndex As Integer, ByVal side As enumSide) As Drawing.Point
If side = enumSide.Left Then
GetItemCoordinates.X = ListBoxControl.Left + ListBoxControl.Width + 1
Else
GetItemCoordinates.X = ListBoxControl.Left - 1
End If
GetItemCoordinates.Y = ListBoxControl.Top + ListBoxControl.GetItemRectangle(itemIndex).Y + (ListBoxControl.GetItemRectangle(itemIndex).Height / 2)
End Function
Πλέον, μπορούμε να διαγράψουμε την GetItemCoordinates2 και να αλλάξουμε λίγο την PaintMe
Private Sub PaintMe(ByVal fromControl As ListBox, ByVal toControl As ListBox)
Dim g As Graphics = CreateGraphics()
Dim pen2 As New Pen(Color.Navy, 2)
Dim p1 As Integer = GetItemCoordinates(fromControl, fromControl.SelectedIndex, enumSide.Left).X
Dim p2 As Integer = GetItemCoordinates(fromControl, fromControl.SelectedIndex, enumSide.Left).Y
Dim p3 As Integer = GetItemCoordinates(toControl, toControl.SelectedIndex, enumSide.Right).X
Dim p4 As Integer = GetItemCoordinates(toControl, toControl.SelectedIndex, enumSide.Right).Y
g.DrawLine(pen2, p1, p2, p3, p4)
End Sub
Στάδιο 4
Μιας και το ζητούμενο είναι να παράγουμε ζευγάρια από Items, ας κάνουμε ένα πρώτο βήμα προς αυτή την κατεύθυνση. Θα καταχωρούμε κάθε ζευγάρι που ορίζουμε σε ένα ArraList.
Dim Pairs As New ArrayList
To θέμα είναι τι θα βάζουμε σε αυτό το ArrayList. Μια καλή λύση είναι να βάζουμε objects που θα περιέχουν το Index από το επιλεγμένο Item του κάθε ListBox. Ορίζουμε λοιπόν την παρακάτω κλάση:
Private Class Pair
Public fromIndex As Integer
Public toIndex As Integer
Public Sub New(ByVal intFrom As Integer, ByVal intTo As Integer)
fromIndex = intFrom
toIndex = intTo
End Sub
Private Sub New()
'
End Sub
End Class
Έχει δύο public members (θα μπορούσαμε να έχουμε φτιάξει κανονικά properties γι αυτά αλλά ας το αφήσουμε για αργότερα) και φτιάχνουμε κι έναν custom contructor για να μπορούμε να δημιουργούμε ευκολότερα τα objects της κλάσης.
Τώρα, κάθε φορά που κάνουμε click, θα δημιουργούμε ένα ζευγάρι και κατόπιν θα ζωγραφίζουμε όλα τα ζευγάρια
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Pairs.Add(New Pair(lstSource.SelectedIndex, lstDest.SelectedIndex))
DrawPairs()
End Sub
Η DrawPairs καλεί πολλές φορές (μία για κάθε object του ArrayList) την PaintMe που την μετονομάζουμε σε PaintLine πλέον.
Private Sub DrawPairs()
For Each p As Pair In Pairs
PaintLine(p.fromIndex, p.toIndex)
Next
End Sub
Στάδιο 5
Σε αυτό το στάδιο, θα προσθέσουμε την εξής λειτουργικότητα: Θα βάλουμε έναν έλεγχο ώστε να μην μπορούμε να κάνουμε πολλαπλά ζευγάρια με τα ίδια items και θα κάνουμε redraw τις γραμμές ώστε αν έχουμε κυλίσει τα περιεχόμενα των ListBox controls να προσαρμόζονται οι γραμμές στις νέες θέσεις τους.
Ο έλεγχος θα γίνεται από ένα function που θα το ονομάσουμε IsItemUsed και θα επιστρέφει True/False
Private Function IsItemUsed(ByVal itemIndex As Integer) As Boolean
For Each p As Pair In Pairs
If p.fromIndex = itemIndex Then
Return True
End If
Next
Return False
End Function
Για να κάνουμε redraw τις γραμμές, θα πρέπει να καλέσουμε την Clear method του Graphics object. Kατόπιν, μπορούμε να ζωγραφίζουμε τις νέες γραμμές, αφού υπολογίσουμε για όλες τις νέες τους συντεταγμένες.
Θα καθορίσουμε λοιπόν μια νέα ρουτίνα για τον καθαρισμό της φόρμας
Private Sub ClearForm()
Dim g As Graphics = CreateGraphics()
g.Clear(Me.BackColor)
g.Dispose()
End Sub
Ας κάνουμε και την PaintLine παραμετρική ως προς το χρώμα
Private Sub PaintLine(ByVal fromIndex As Integer, ByVal toIndex As Integer, ByVal penColor As Color)
και η εντολή που καθορίζει το χρώμα του pen object γίνεται
Dim pen2 As New Pen(penColor, 2)
Άλλη μια επέμβαση που μπορούμε να κάνουμε είναι όταν υπολογίζουμε το ύψος σε κάθε ζευγάρι συντεταγμένων, να θεωρούμε ότι αν το Ιtem - λόγω scroll - έχει χαθεί, τότε το ύψος θα είναι εκεί που ξεκινάει το ListBox. Με αυτόν τον τρόπο δηλαδή, οι γραμμές δεν θα χάνονται στο άπειρο της φόρμας. Έτσι, προσθέτουμε έναν έλεγχο και πράττουμε τα δέοντα στο τέλος της GetItemCoordinates
Private Function GetItemCoordinates(ByVal ListBoxControl As ListBox, ByVal itemIndex As Integer, ByVal side As enumSide) As Drawing.Point
If side = enumSide.Left Then
GetItemCoordinates.X = ListBoxControl.Left + ListBoxControl.Width + 1
Else
GetItemCoordinates.X = ListBoxControl.Left - 1
End If
GetItemCoordinates.Y = ListBoxControl.Top + ListBoxControl.GetItemRectangle(itemIndex).Y + (ListBoxControl.GetItemRectangle(itemIndex).Height / 2)
If GetItemCoordinates.Y < ListBoxControl.Top Then
GetItemCoordinates.Y = ListBoxControl.Top
ElseIf GetItemCoordinates.Y > ListBoxControl.Top + ListBoxControl.Height Then
GetItemCoordinates.Y = ListBoxControl.Top + ListBoxControl.Height
End If
End Function
Τέλος, ας αλλάξουμε τον κώδικα του button click ώστε να χρησιμοποιεί όλα τα παραπάνω:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If IsItemUsed(lstSource.SelectedIndex) Then
Exit Sub
End If
ClearForm()
Pairs.Add(New Pair(lstSource.SelectedIndex, lstDest.SelectedIndex))
DrawPairs(Color.Navy)
End Sub


Στάδιο 6
Σε αυτό το στάδιο θα κάνουμε μια διόρθωση. Αν κάνετε trace τον κώδικα ή παρατηρήσετε την PaintLine ρουτίνα, θα δείτε ότι καλεί την GetItemCoordinates τέσσερις φορές ενώ θα μπορούσε να την καλεί δύο. Δεν χρειάζεται να την καλούμε μία για το Χ και μία για το Υ, μπορούμε να πάρουμε και τα δύο με μία κλήση. Ας αλλάξουμε πάλι λίγο την PaintLine
Private Sub PaintLine(ByVal fromIndex As Integer, ByVal toIndex As Integer, ByVal penColor As Color)
Dim g As Graphics = CreateGraphics()
Dim pen2 As New Pen(penColor, 2)
Dim pFrom As Point = GetItemCoordinates(lstSource, fromIndex, enumSide.Left)
Dim pTo As Point = GetItemCoordinates(lstDest, toIndex, enumSide.Right)
g.DrawLine(pen2, pFrom.X, pFrom.Y, pTo.X, pTo.Y)
End Sub
Στάδιο 7
Για να βοηθήσουμε τον χρήστη, ας προσθέσουμε στο functionality τη δυνατότητα όταν κάνει click σε κάποιο Item, να επιλέγεται από το απέναντι ListBox το paired Item.
Θα χρειαστεί το SelectedIndexChanged event.
Private Sub lstbox_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles lstSource.SelectedIndexChanged, lstDest.SelectedIndexChanged
If CType(sender, ListBox).Name = "lstSource" Then
For Each p As Pair In Pairs
If p.fromIndex = lstSource.SelectedIndex Then
lstDest.SelectedIndex = p.toIndex
End If
Next
Else
For Each p As Pair In Pairs
If p.ToIndex = lstDest.SelectedIndex Then
lstSource.SelectedIndex = p.fromIndex
End If
Next
End If
End Sub
Θα κάνουμε assign την ίδια ρουτίνα και στα δύο events (ένα από το κάθε control) και ανάλογα πιο control είναι που την καλεί
If CType(sender, ListBox).Name = "lstSource" Then …
Θα κάνουμε ένα iteration μέσα στο ArrayList με τα ζευγάρια για να βρούμε το απέναντι Item.
Στάδιο 8
Ήρθε η ώρα να προσθέσουμε drag-n-drop functionality στη φόρμα. Θα παίρνουμε ένα ListBox Item από τα αριστερά και θα το ρίχνουμε πάνω στο αντίστοιχο στα δεξιά και θα έχουμε ζευγάρι!
Αρχικά, θα πρέπει να ορίσουμε ότι το δεξί ListBox δέχεται drops. Οπότε από τον designer κάνουμε True το property AllowDrop. Κατόπιν ορίζουμε δύο μεταβλητές που θα κρατάνε το Index από το Item που ξεκινάει το drag και το Index από το Item που γίνεται το drop.
Dim indexOfItemUnderMouseToDrop As Integer
Dim indexOfItemUnderMouseToDrag As Integer
Τώρα, χρειαζόμαστε το MouseDown event για να βρούμε σε πιο Item έχει γίνει click και να ξεκινήσουμε τη διαδικασία με τη μέθοδο DoDragDrop. Φυσικά, ισχύουν όλοι οι γνωστοί περιορισμοί, να μην έχουμε άλλο ζευγάρι με το ίδιο Item, κλπ.
Private Sub lstSrc_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles lstSource.MouseDown
If lstSource.SelectedIndex >= 0 Then
indexOfItemUnderMouseToDrag = lstSource.SelectedIndex
If Not IsItemUsed(indexOfItemUnderMouseToDrag) Then
lstSource.DoDragDrop(lstSource.SelectedItem, DragDropEffects.Copy)
End If
End If
End Sub
Όταν φτάνει το dragged Item πάνω στο δεξί ListBox, αλλάζουμε τον mouse cusror
Private Sub lstdest_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstDest.DragEnter
If e.Data.GetDataPresent(DataFormats.StringFormat) Then
e.Effect = DragDropEffects.Copy
End If
End Sub
Και διαβάζουμε σε πιο Item είμαστε από πάνω
Private Sub lstDest_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstDest.DragOver
indexOfItemUnderMouseToDrop = lstDest.IndexFromPoint(lstDest.PointToClient(New Point(e.X, e.Y)))
End Sub
Όλο το ζουμί είναι το IndexFromPoint που μας επιστρέφει το Ιndex του Ιtem που βρίσκεται κάτω από συγκεκριμένες συντεταγμένες, ωστόσο το Point που περιμένει το method είναι σε σχέση με τις συντεταγμένες του control, οπότε μετατρέπουμε τις screen συντεταγμένες σε control συντεταγμένες με τη μέθοδο PointToClient του control. Τέλος, με το DragDrop event που τρέχει όταν ο χρήστης αφήσει το mouse button καθορίζουμε το ζευγάρι των items.
Private Sub lstDest_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstDest.DragDrop
If e.Data.GetDataPresent(DataFormats.StringFormat) Then
AddPair(indexOfItemUnderMouseToDrag, indexOfItemUnderMouseToDrop)
End If
End Sub
Στάδιο 9
Όλα ωραία και καλά μέχρι στιγμής, ωστόσο λείπει κάτι βασικό από αυτό που φτιάχνουμε!
Χρειαζόμαστε όταν γίνεται scroll στα δύο ListBox controls να επανασχεδιάζονται αυτόματα οι γραμμές. Το κακό είναι ότι δεν υπάρχει αντίστοιχο event οπότε… πρέπει να καταφύγουμε στο Win32 API. Ας δούμε πως θα φτιάξουμε ένα νέο listbox που θα μας δίνει αυτό το event.
Ορίζουμε μια νέα κλάση που θα την ονομάσουμε ListBoxEx (από το Extended). Αυτή η κλάση κάνει Inherit το ListBox και θα ορίζει (μέσω ενός delegate) ένα νέο event που θα το ονομάσουμε Scroll. Ο κώδικας είναι πολύ απλός:
Public Class ListBoxEx
Inherits System.Windows.Forms.ListBox
Public Event Scroll As OnScroll
Public Delegate Sub OnScroll(ByVal sender As Object, ByVal e As System.EventArgs)
Const WM_VSCROLL As Integer = &H115
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
MyBase.WndProc(m)
If m.Msg = WM_VSCROLL Then
RaiseEvent Scroll(Me, New System.EventArgs)
End If
End Sub
End Class
Ουσιαστικά το μόνο που χρειάζεται να κάνουμε, είναι Override το WndProc για να πιάσουμε τα μηνύματα που λαμβάνει το control. Συγκεκριμένα μας ενδιαφέρει το WM_VSCROLL που λαμβάνει το Scrollbar του Control οπότε, εκεί κάνουμε Raise το δικό μας event. Το νέο event έχει απλή υλοποίηση, αν και θα μπορούσε να είχε πιο σύνθετη που να δείχνει προς τα πού γίνεται το scroll και κατά πόσο αλλά όλα αυτά δεν μας χρειάζονται στην συγκεκριμένη περίπτωση.
Το μόνο που μένει πλέον είναι να πάμε στον κώδικα της φόρμας και να αλλάξουμε τον κώδικα του designer. Tα
Friend WithEvents lstSource As System.Windows.Forms.ListBox
Friend WithEvents lstDest As System.Windows.Forms.ListBox
σε
Friend WithEvents lstSource As ListBoxEx
Friend WithEvents lstDest As ListBoxEx
κι επίσης τα
Me.lstSource = New System.Windows.Forms.ListBox
Me.lstDest = New System.Windows.Forms.ListBox
σε
Me.lstSource = New ListBoxEx
Me.lstDest = New ListBoxEx
Τρέχοντας τώρα το προγραμματάκι, βλέπουμε να επανασχεδιάζονται οι γραμμές καθώς γίνεται scroll. Μια τελευταία πινελιά είναι να γίνεται το ίδο και όταν κάνουμε scroll με το πληκτρολόγιο μιας και το προηγούμενο event πιάνει τα scroll messages του scrollbar μόνο. Απλά, στο lstbox_SelectedIndexChanged, μετά το τελευταίο End If, προσθέτουμε
ClearForm()
DrawPairs(Color.Navy)
Στάδιο 10
Είμασε πλέον έτοιμοι να σχεδιάσουμε το νέο control. Θα το ονομάσουμε MappingListBoxes. Κάνουμε add ένα νέο User Control, του δίνουμε αυτό το όνομα και τοποθετούμε δύο ListBox controls που τα ονομάζουμε με το ίδιο όνομα όπως και στη φόρμα. Επαναλαμβάνουμε τις αλλαγές που κάναμε προηγουμένως (από System.Windows.Forms.ListBox σε ListBoxEx) και κατόπιν κάνουμε Copy/Paste όλον τον κώδικα (εκτός του Form Designer) από την φόρμα στο User Control. Κάνουμε compile και το UserControl εμφανίζεται στο ToolΒox (στο My User Controls) οπότε φτιάχνουμε μια νέα φόρμα και το ρίχνουμε πάνω.
Στάδιο 11
Τώρα χρειάζεται να κάνουμε μερικές αλλαγές, αφού έχουμε ξεχωρίσει τα ListBox controls από τα buttons που κάνουν τις ενέργειες.
Αρχικά χρειάζεται να ορίσουμε μία μέθοδο που θα την ονομάσουμε AddNewPair η οποία θα καλείται και με τη σειρά της θα κάνει ότι κάναμε παλιά με το Button1_Click. Άρα στη φόρμα
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
MappingListBoxes1.AddNewPair()
End Sub
Και στο UserControl
Public Sub AddNewPair()
AddPair(lstSource.SelectedIndex, lstDest.SelectedIndex)
End Sub
Επίσης, για να μπορούμε να γεμίσουμε τα ListBox controls μέσω κώδικα, χρειάζεται να εκθέσουμε προς τα έξω το Items collection property από το κάθε ListBox cotrol. Αυτό μπορεί να γίνει με δύο properties
Public ReadOnly Property SourceItems() As ListBox.ObjectCollection
Get
Return lstSource.Items
End Get
End Property
Public ReadOnly Property DestItems() As ListBox.ObjectCollection
Get
Return lstDest.Items
End Get
End Property
Και στη φόρμα μας πλέον, μπορούμε να τοποθετήσουμε ένα νέο κουμπί και να γράψουμε κάτι σαν
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
For i As Integer = 0 To 30
TwinListBox1.SourceItems.Add("Source Item " & i.ToString)
TwinListBox1.DestItems.Add("Dest Item " & i.ToString)
Next
End Sub
Στάδιο 12
Δεν έχουμε εξετάσει καθόλου τι γίνεται αν θέλουμε να διαγράψουμε ένα ζευγάρι. Λίγο που το έψαξα είδα ότι είναι εξαιρετικά δύσκολο κάνοντας click πάνω στη γραμμή να πάρεις τα indexes. Γι αυτόν το λόγο θα δώσουμε τη δυνατότητα με ένα method που θα το ονομάσουμε DeletePair να διαγράφουμε το επιλεγμένο ζευγάρι που αντιστοιχεί στο selected item.
Αυτή είναι η ρουτίνα που διαγράφει το ζευγάρι από το ArrayList
Private Sub DeletePair(ByVal sourceIndex As Integer, ByVal destindex As Integer)
If sourceIndex >= 0 AndAlso destindex >= 0 Then
If IsItemUsed(lstSource.SelectedIndex) Then
Dim p As Pair
For Each p In Pairs
If p.fromIndex = lstSource.SelectedIndex Then
Exit For
End If
Next
Pairs.Remove(p)
End If
ClearForm()
DrawPairs(Color.Navy)
End If
End Sub
Αυτή είναι η μέθοδος που την καλεί
Public Sub DeletePair()
DeletePair(lstSource.SelectedIndex, lstDest.SelectedIndex)
End Sub
Και στη φόρμα, άλλο ένα κουμπάκι με κώδικα
Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
TwinListBox1.DeletePair()
End Sub
Είμαστε έτοιμοι!
Το μόνο που μένει είναι βάλουμε μια μικρή πινελιά για ομορφιά… Επειδή οι γραμμές που ζωγραφίζονται έχουν πολλά σκαλοπατάκια, μπορούμε να τις κάνουμε πιο όμορφες αν μετά από κάθε
Dim g As Graphics = CreateGraphics()
προσθέσουμε τη γραμμή
g.SmoothingMode = SmoothingMode.HighQuality
Επίσης, αλλάζουμε λίγο τον τρόπο που ζωγραφίζουμε τη γραμμή ώστε να μην είναι μια απλή ευθεία ανάμεσα στα δύο Items. Το αρχικό DrawLine το μετατρέπουμε σε τρία:
g.DrawLine(pen2, pFrom.X, pFrom.Y, pFrom.X + 5, pFrom.Y)
g.DrawLine(pen2, pFrom.X + 5, pFrom.Y, pTo.X - 5, pTo.Y)
g.DrawLine(pen2, pTo.X - 5, pTo.Y, pTo.X, pTo.Y)

Επίλογος
Με όλα αυτά έχουμε φτιάξει τη βασική έκδοση του Mapping conrtol. Μπορούν να γίνουν διάφορα για να ραφιναριστεί και να εξυπηρετήσει πιο εξειδικευμένες ή διαφορετικές ανάγκες. Εδώ είμαστε και τα κουβεντιάζουμε…