Newsgroup: it.comp.lang.visual-basic
Titolo: [VB2008] listbox SelectedIndexChanged

ciao a tutti,
ho una form con due listbox collegate, nel senso che io vorrei popolare 
la prima e poi in base al valore selezionato dall'utente nella prima 
popolo la seconda.
La prima la popolo con una query tipo select * from provincia che ho 
messo nell'evento Load della form.
La seconda la popolo con select * from comune where 
provincia=textbox1.SelectedValue.ToString e l'ho messa nel metodo 
SelectedIndexChanged della listbox1.
Mi succede che quando faccio il load della form, ovvero quando popolo la 
listbox1 con il risultato della query viene chiamata due volte il metodo 
SelectedIndexChanged ma "senza valore" quindi la seconda query non puo' 
essere eseguita e si genera un errore.
Come posso evitare che venga invocato il metodo SelectedIndexChanged 
mentre popolo la listbox1?
Sto forse sbagliando il metodo per intercettare la selezione dell'utente 
sulla listbox?
ciao
Mauro

 essere eseguita e si genera un errore.
Bisognerebbe avere un'idea un po' più precisa del tuo codice e di come
carichi le liste:
a mano? tramite databinding? come?
Ad esempio, questa semplice form con due listbox caricate a mano, si
comporta correttamente:
Public Class frmListBox
   Private Sub frmListBox_Load(ByVal sender As System.Object, ByVal e
As System.EventArgs) _
      Handles MyBase.Load
      For i As Integer = 1 To 50
         ListBox1.Items.Add(String.Format("Item {0:00}", i))
      Next
   End Sub
   Private Sub ListBox1_SelectedIndexChanged(ByVal sender As
System.Object, ByVal e As System.EventArgs) _
      Handles ListBox1.SelectedIndexChanged
      Dim x As String = Convert.ToString(ListBox1.SelectedItem)
      For i As Integer = 1 To 10
         ListBox2.Items.Add(String.Format("{0}.{1:00}", x, i))
      Next
      ListBox2.Items.Add("---")
   End Sub
End Class

 a mano? tramite databinding? come?
Le carico in questo modo:
1. creo un oggetto listbox item cos� definito
Public Class list_box_item
     Private myId As String
     Private myName As String
     Public Sub New(ByVal strId As String, ByVal strName As String)
         Me.myId = strId
         Me.myName = strName
     End Sub 'New
     Public ReadOnly Property id() As String
         Get
             Return myId
         End Get
     End Property
     Public ReadOnly Property name() As String
         Get
             Return myName
         End Get
     End Property
     'Public Overrides Function ToString() As String
     '    Return myName
     'End Function
End Class
2. faccio una query sul db e popolo la datagrid con
Dim arlist As New ArrayList()
While reader.Read()
   ' Creo un array di oggetti da usare come datasource
   arlist.Add(New list_box_item(reader(0).ToString(), reader(1).ToString()))
End While
lb.DataSource = arlist
lb.DisplayMember = "name"
lb.ValueMember = "id"
qualche idea?
Posso postare un codice completo che genera il problema se vuoi ...
grazie, ciao
Mauro

 qualche idea?
Quello che mi viene in mente, e che andrebbe tenuta presente, è che
l'evento SelectedIndexChanged può essere scatenato anche per segnalare
che la selezione viene "pulita" (ovvero che SelectedIndex è passato a
-1); bisognerebbe ricordarsi sempre di prendere in considerazione quel
caso nel codice dell'evento:
If lista.SelectedIndex>-1 Then
   'c'e' un elemento selezionato, facciamo qualcosa
Else
  'Nessuna riga evidenziata, facciamo qualcos'altro (o non facciamo
niente)
End If

 caso nel codice dell'evento:
Grazie della segnalazione, non la sapevo.
Ho provato ma non funziona lo stesso.
Ti allego il codice minimo per riprodurre il problema. Devi creare una 
form con una listbox di nome ListBox1 e un pulsante di nome Button1
Vedrai che quando premi il pulsante la funzione 
ListBox1_SelectedIndexChanged viene chiamata due volte, una prima volta 
quando nella listbox compaiono delle stringhe 
WindowsApplication1.list_box_item e una seconda volta quando compaiono 
le scritte "corrette"
Anche aggiungendo il controllo se selected_index � maggiore di -1 non 
cambia niente ... :-( ... viene sempre chiamata due volte
Pero' la mia query fallisce la prima volta che popolo la listbox.
A questo punto non capisco se sbaglio io qualcosa o se c'e' un altro 
metodo per fare due listbox "collegate" ...
grazie per l'aiuto
ciao
Mauro
Segue codice:
Public Class Form1
     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As 
System.EventArgs) Handles Button1.Click
         Dim lista As New ArrayList
         'Popolo la listbox
         For i As Integer = 1 To 10
             lista.Add(New list_box_item("Chiave" & i.ToString, "Valore" 
& i.ToString))
         Next
         ListBox1.DataSource = lista
         ListBox1.DisplayMember = "name"
         ListBox1.ValueMember = "id"
     End Sub
     Private Sub ListBox1_SelectedIndexChanged(ByVal sender As 
System.Object, ByVal e As System.EventArgs) Handles 
ListBox1.SelectedIndexChanged
         MsgBox("Chiamato")
     End Sub
End Class
Public Class list_box_item
     Private myId As String
     Private myName As String
     Public Sub New(ByVal strId As String, ByVal strName As String)
         Me.myId = strId
         Me.myName = strName
     End Sub 'New
     Public ReadOnly Property id() As String
         Get
             Return myId
         End Get
     End Property
     Public ReadOnly Property name() As String
         Get
             Return myName
         End Get
     End Property
End Class

          ListBox1.ValueMember = "id"
Il problema è qui
quando cambi le property xxMember stai cambiando i parametri di data
binding, la stessa cosa succede quando imposti DataSource; in pratica
succede che la lista viene svuotata e caricata due volte:
questo: ListBox1.DataSource = lista
ti carica la lista usando il metodo list_box_item.ToString ereditato
da Object
questo: ListBox1.DisplayMember = "name"
rinfresca la lista usando adesso la property decisa da te
Morale: devi invertire l'ordine delle chiamate
Prima imposti tutti i parametri di DataBinding, e solo come ultima
cosa imposti il DataSource
ListBox1.DisplayMember = "name"
ListBox1.ValueMember = "id"
ListBox1.DataSource = lista

 cosa imposti il DataSource
Grandissimo !!!
Adesso non viene fatto + il lavoro due volta ma una sola. Grazie infinite!
Ultima domanda .... non c'e' modo di "saltare" il primo 
selectedIndexChanged dovuto al caricamento dati?
Mi spiego meglio ... se ricordi tutto � nato per fare due listbox collegate.
Io vorrei caricare la prima e poi SOLO quando l'utente sceglie un valore 
caricare i dati nella seconda.
Ma con il mio codice quando popolo la prima viene generato l'evento 
SelectedIndexChanged e quindi viene popolata la seconda con il primo 
valore della prima.
C'e' modo di evitarlo?
ancora grazie infinite
ciao
Mauro
P.s.
Sarebbe anche da segnalare anche a microsoft questa cosa visto che nella 
msdn loro mostrano di caricare prima il datasource e poi il databinding? :-)
http://msdn.microsoft.com/en-us/library/system.windows.forms.listcontrol.valuemember.aspx

 C'e' modo di evitarlo?
Qui temo devi intervenire tu a manina, puoi fare in un paio di modi:
a) Vecchio stile, usando il classico flag booleano; lo setti a True
all'inizio del Load e lo rimetti False alla fine; nel
SelectedIndexChanged racchiudi tutto in una IF conseguente
b) Disattivando il gestore di evento della lista fintanto che non
completi il Load; ovvero:
   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) _
      Handles MyBase.Load
      'Per disattivare il delegate dell'evento
      RemoveHandler ListBox1.SelectedIndexChanged _
         , AddressOf ListBox1_SelectedIndexChanged
      '[...] qui tutto il resto del codice
      'Alla fine:
      'Per partire senza nessuna selezione
      ListBox1.SelectedIndex = -1
      'Per riattivare il delegate dell'evento
      AddHandler ListBox1.SelectedIndexChanged _
         , AddressOf ListBox1_SelectedIndexChanged
   End Sub

 Qui temo devi intervenire tu a manina, puoi fare in un paio di modi:
[...]
grazie infinite, sei stato gentilissimo ... e risolutivo :-) !!!
ciao
Mauro

     End Sub
Scusami, ho ancora un problema.
Io questo controllo lo dovrei implementare in una funzione di una classe 
all'interno di una sub a cui passo per riferimento la listbox da popolare.
Public Sub query_to_listbox(ByRef lb As ListBox, ByVal sql As String)
Ho provato con
RemoveHandler lb.SelectedIndexChanged, AddressOf lb.SelectedIndexChanged
ma mi da errore:
Errore	3	L'operando 'AddressOf' deve essere il nome di un metodo, senza 
parentesi.
Posso rimuovere l'handler da dentro la classe o devo per forza farlo 
dalla form che usa il mio metodo?
ciao
Mauro


Come ti segnala il messaggio, il secondo parametro è l'indirizzo delle
funzione che fungerà da "consumer" dell'evento, e non l'evento stesso
come hai provato a passare tu.
Dato che quella funzione (<nome della lista>_SelectedIndexChanged
(sender as object ecc....)) è privata nella form, devi fare
l'operazione dal codice della form
Una alternativa è quella di passare l'EventHandler alla funzione:
Public Sub query_to_listbox(ByRef lb As ListBox, ByVal sql As String,
ByVal eh As EventHandler)
   RemoveHandler lb.SelectedIndexChanged, eh
   '...
   AddHandler lb.SelectedIndexChanged, eh
End Sub
che andrà quindi chiamata con
oggetto.query_to_listbox(ListBox1,"select xxx", AddressOf
ListBox1_SelectedIndexChanged)
Per funzionare, funziona, ma non è che mi piace tanto come approccio,
perchè rende la classe un po' troppo "imparentata" con il
comportamento della specifica form in questione, e quindi poco
riutilizzabile
A dire il vero non è nemmeno tanto bello che le venga passato il
listbox da riempire; rendi il metodo molto più neutro se ti limiti a
fargli restituire una lista di oggetti list_box_item, che poi ogni
finestra si preoccuperà di caricare nel controllo più indicato.

 finestra si preoccuper� di caricare nel controllo pi� indicato.
Eh, si, me ne sto rendendo conto :-( .... solo che ero partito con la 
classe per fare una cosa sola (e farla bene) e adesso vedo che ci sono 
N-mila possibilit� ulteriori che non avevo previsto ma che potrebbero 
tornare utili.
In effetti avevo anche gi� implementato il metodo query_to_array, ma il 
query_to_textbox mi semplificava ancora di piu' la scrittura del codice 
... per� effettivamente � utilizzabile in un ristretto numero di casi.
grazie per tutti i consigli
ciao
Mauro