Φτιάχνοντας ένα mapping control

Έχουν δημοσιευτεί 25 Ιανουαρίου 06 12:25 μμ | KelMan 

Σε συνέχεια του 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. Μπορούν να γίνουν διάφορα για να ραφιναριστεί και να εξυπηρετήσει πιο εξειδικευμένες ή διαφορετικές ανάγκες. Εδώ είμαστε και τα κουβεντιάζουμε…

 

Δημοσίευση στην κατηγορία:

Σχόλια:

# cap said on Φεβρουαρίου 16, 2006 1:43 μμ:
Επειδή όταν χάσει το focus η φόρμα χάνονται και οι γραμμούλες, να προσθέσω οτι θα πρέπει να κάνουμε override την paint για να μπορέσουμε να έχουμε πάντα τις γραμμούλες:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
DrawPairs()
End Sub
Έχει απενεργοποιηθεί η προσθήκη σχολίων από ανώνυμα μέλη

Search

Go

Συνδρομές