Listbox MultiSelection

Αφορμή για το συγκεκριμένο post ήταν ένα ερώτημα για τη χρήση των listboxes στο freestuff.gr. Αν και το list box στο αρχικό ερώτημα δεν χρειαζόταν παρά single selection, οπότε μπορούσε ο προγραμματιστής να πάρει την τιμή από το property Value, δίνοντας μια απλή λύση, τέθηκε ένα δεύτερο πολύ πιο ενδιαφέρον ερώτημα σχετικά με το πως θα μπορούσε να χρησιμοποιηθεί muti-selection και με πως θα μπορούσαν να αποθηκεύονται/φορτώνονται οι επιλογές.

Μία λύση προτάθηκε από τον ίδιο που έθεσε το ερώτημα:

Να αποθηκεύονται οι επιλεγμένοι indexes σε ένα comma separated values string (csv για συντομία). Με ποιόν άλλον τρόπο όμως θα μπορούσε να αποθηκεύονται οι επιλογές;

Πρίν 1-2 χρόνια που είχα χρειαστεί κάτι αντίστοιχο είχα αποφύγει τα csv strings γιατί ήταν πολύ δύστροπα αφού απαιτούσαν μεγάλη προσοχή στη διαχείριση τους, αλλά κυρίως επειδή είναι ευαίσθητα στον τύπο των δεδομένων. Με τα νούμερα τα πάνε σχετικά καλά, αλλά με string values; Αν περιέχουν “περίεργους χαρακτήρες” όπως εισαγωγικά, τελείες, κόμματα, αλλαγές γραμμών… Αστα να πάνε, άμα είναι να σου βγάλουν την πίστη, να μένει το βύσινο!

Οπότε μια απάντηση που μου φαίνεται προφανής, αφού για μία (1) εγγραφή μπορείς να έχεις πολλές (Ν) επιλογές, είναι: Σε κάποιον άλλο πίνακα συνδεδεμένο πρός τον πρώτο με σχέση 1-Ν.

Finito!… …σχεδόν!

Η ουσία του post δεν είναι τόσο το που αποθηκεύονται οι επιλογές, αν είναι csv, πίνακας, blog object, ή οτιδήποτε άλλο, αλλά το τι είναι αυτό που θα αποθηκεύσεις.

Αυτό που διάβασα και χτύπησε το “καμπανάκι” είναι το εξής:

…οι επιλεγμένοι indexes σε…

Την λίστα με τους επιλεγμένους indexes μπορούμε να τους πάρουμε από το property ItemsSelected το οποίο επιστρέφει ένα collection με αυτούς. Είναι όμως Μέγα λάθος! Δεν ενδιαφέρουν οι indexes (το οποίο πρακτικά σημαίνει η σειρά εμφάνισης) των επιλογών, αλλά ποιές είναι οι επιλεγμένες τιμές του bound column του ListBox. Το γιατί θα το δείξω χρησιμοποιώντας ένα παράδειγμα παρακάτω. Ενημερωτικά απλώς να αναφέρω ότι τις selected values μπορούμε να τις πάρουμε σε VB κάπως έτσι:

'
'Εύρεση επιλεγμένων τιμών σε ένα Listbox
'
Dim item As Variant

For Each item In Control.ItemsSelected
    Debug.Print Control.ItemData(item)
Next item

Να διαπιστώσουμε που φαίνεται το λάθος.

Εστω ότι έχουμε τον πίνακα t1(id: text primary key, descr: text) με τιμές:

"ΚΑ", "ΚΑΦΕΔΕΣ"
"ΑΝ", "ΑΝΑΨΥΚΤΙΚΑ"
"ΧΥ", "ΧΥΜΟΙ"

Τα εμφανίζουμε στο listbox, (φέρνουμε τα δεδομένα από τη βάση μας χωρίς να ορίσουμε κάποια ταξινόμηση, οπότε χρησιμοποιείται το id που είναι primary key). Το αποτέλεσμα είναι κάτι τέτοιο:

"ΑΝΑΨΥΚΤΙΚΑ"
"ΚΑΦΕΔΕΣ"
"ΧΥΜΟΙ"

Επιλέγουμε τα δύο τελευταία (οπότε θα δημιουργηθεί το string “1,2” που περιέχει τους indexes των δύο επιλογών) και το αποθηκεύουμε σε ένα csv string.
Κλέινουμε τη φόρμα, την ξανανοίγουμε, διαβάζουμε το csv και επιλέγονται τα items με indexes 1 και 2 οπότε φαίνονται στο listbox επιλεγμένα και τα δύο! Το αποτέλεσμα είναι κάτι τέτοιο:

"ΑΝΑΨΥΚΤΙΚΑ"
"ΚΑΦΕΔΕΣ" (επιλεγμένο)
"ΧΥΜΟΙ" (επιλεγμένο)

Κλείνουμε τη φόρμα και προσθέτουμε στον πίνακα t1 και τις εγγραφές:

"ΒΟ", "ΒΟΥΤΗΜΑΤΑ"
"ΝΕ", "ΝΕΡΑ"
"ΠΟ", "ΠΟΤΑ"
"ΦΑ", "ΦΑΓΗΤΑ"
"ΓΛ", "ΓΛΥΚΑ"

Ανοίγουμε τη φόρμα, διαβάζουμε το csv και επιλέγονται τα items με indexes 1 και 2, και βλέπουμε τα εξής στο listbox:

"ΑΝΑΨΥΚΤΙΚΑ"
"ΒΟΥΤΗΜΑΤΑ" (επιλεγμένο)
"ΓΛΥΚΑ" (επιλεγμένο)
"ΚΑΦΕΔΕΣ"
"ΝΕΡΑ"
"ΠΟΤΑ"
"ΦΑΓΗΤΑ"
"ΧΥΜΟΙ"

Ωπα! Κάτι πήγε στραβά! Είχαμε επιλέξει “ΚΑΦΕΔΕΣ” και “ΧΥΜΟΙ” (και που να το θυμόμαστε ειδικά μετά από καιρό), αλλά εμφανίζεται ΒΟΥΤΗΜΑΤΑ και ΓΛΥΚΑ, γιατί εξαρτόμαστε από τη σειρά εμφάνισης (η οποία δεν μας εγγυάται κανένας ότι δεν θα αλλάξει) και όχι από τις τιμές.

Τι θα ήταν σωστότερο;

Οταν πάμε να δημιουργήσουμε το csv string να χρησιμοποιήσουμε τον κώδικα[1] που βρίσκει τις επιλεγμένες τιμές του listbox, που θα μας δημιουργήσει το string “ΚΑ,ΧΥ”, και όταν φορτώνουμε το csv να ψάχνουμε ποιά items έχουν bounded values “ΚΑ” και “ΧΥ” και αφού πάρουμε τον αντίστοιχο index να τα επιλέξουμε με Control.Selected(index) = True

Βέβαια, επειδή ο διάβολος έχει πολλά ποδάρια, να υπενθυμίσουμε ότι μιλάμε για multiselection. Αν κάποιος πάει να τα χρησιμοποιήσει και δεν δουλεύει όπως πρέπει, ας κάνει τον κόπο να ελέγξει δύο πράγματα για το Listbox:

  1. είναι multiselect και
  2. δεν είναι Locked!