Tutti abbiamo avuto a che fare almeno una volta con il controllo DataGrid. Questo controllo permette di associare in maniera semplice sorgenti di dati, presentarli all?utente, ed ha inoltre la capacità di gestire le manipolazioni dati per poi aggiornarle sul database. Queste funzionalità in parte sono gestite automaticamente, in parte sviluppate dal programmatore. Quello che spiegherò, è come inserire dei controlli lato server quando si vogliono modificare i dati di una riga del DataGrid. Cominciamo a descrivere l'esempio che ho preparato. Nella prima figura è visualizzato un DataGrid che mostra delle tipiche informazioni che possono essere associate ad un utente.
Andiamo ora a vedere com?è stato costruito questo controllo.
<asp:DataGrid ID="dgUtenti" CssClass="grid" Runat="server" AutoGenerateColumns="False">
<Columns>
<asp:BoundColumn Visible="False" DataField="id" HeaderText="ID"></asp:BoundColumn> <asp:BoundColumn DataField="nome" HeaderText="Nome"></asp:BoundColumn> <asp:BoundColumn DataField="cognome" HeaderText="Cognome"></asp:BoundColumn> <asp:BoundColumn DataField="Sesso" HeaderText="Sesso"></asp:BoundColumn> <asp:BoundColumn DataField="Eta" HeaderText="Età"></asp:BoundColumn>
<asp:TemplateColumn HeaderText="Occupazione">
<ItemTemplate> <asp:Label Runat=server text='<%#Databinder.Eval(Container.Dataitem, "Occupazione")%>' ID="Occ">
</asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList Runat="server" ID="dropOccupazione"></asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="Ubicazione">
<ItemTemplate>
<asp:Label Runat=server text='<%# "Regione: <b>" & Databinder.Eval(Container.Dataitem, "Regione").tolower & "</b><br>Provincia: <b>" & Databinder.Eval(Container.Dataitem, "Citta").tolower & "</b><br>Comune: <b>" & Databinder.Eval(Container.Dataitem, "Comune").tolower & "</b>"%>' ID="Ubi">
</asp:Label>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList Runat="server" AutoPostBack="True" OnSelectedIndexChanged="drpRegioni_SelectedIndexChanged"
ID="drpRegioni"></asp:DropDownList>
<asp:DropDownList Runat="server" AutoPostBack="True" OnSelectedIndexChanged="drpProvince_SelectedIndexChanged" ID="drpProvice"></asp:DropDownList>
<asp:DropDownList Runat="server" ID="drpComuni"></asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:EditCommandColumn ButtonType="LinkButton" UpdateText="Conferma" CancelText="Annulla" EditText="Modifica"></asp:EditCommandColumn>
<asp:BoundColumn Visible="False" DataField="idcomuneresidenza" ReadOnly="True" HeaderText="istat_comune"></asp:BoundColumn>
</Columns>
</asp:DataGrid>
Sono state associate le colonne relative all?ID, nome, cognome, sesso, età ed idcomuneresidenza, nel classico modo in cui si effettua l?associazione dei dati tra sorgente e controllo (ho utilizzato il
Property Builder dalla modalità
Design).
Prima di passare oltre voglio accennare che le
BoundColumn in fase di
Edit vengono trasformate in
Textbox in maniera automatica utilizzando l?evento
OnEditCommand ed impostado opportunamente un indice.
Torniamo all'esempio. La colonna
"Occupazione" in fase di modifica dei dati non deve essere modificata tramite una Textbox, ma deve essere permesso all?utente di cambiare occupazione scegliendo il nuovo valore da un menù a tendina generato tramite una query sul database; quindi la gestione è un po? diversa.
Ecco che entrano in gioco le
TemplateColunm.
Le
TemplateColumn sono colonne "personalizzabili" che vengono implementate dallo sviluppatore ed in questo caso ci servono per il nostro scopo. Come potete vedere dal codice, queste colonne "speciali" presentano due sottoelementi: l?
ItemTemplate e l?
EditItemTemplate. Come quasi si capisce solo leggendo il nome, il primo contiene il testo che viene visualizzato durante l?associazione con la sorgente dati, mentre il secondo visualizza le informazioni contenute in esso in fase di modifica. Infatti è proprio nell?
EditItemTemplate che sono andato ad inserire la
DropDownList.
L'
EditCommandColumn è invece la colonna particolare del DataGrid che permette la gestione degli eventi
OnEditCommand,
OnUpdateCommand,
OnCancelCommand e la renderizzazione dei bottoni.
Vediamo subito il codice che gestisce questi tre eventi.
Private Sub dgUtenti_EditCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgUtenti.EditCommand
'Inizio
dgUtenti.EditItemIndex = e.Item.ItemIndex
Bind_Grid()
End Sub
Private Sub dgUtenti_CancelCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgUtenti.CancelCommand
'Inizio
dgUtenti.EditItemIndex = -1
Bind_Grid()
End Sub
Private Sub dgUtenti_UpdateCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs) Handles dgUtenti.UpdateCommand
'Inizio
dgUtenti.EditItemIndex = -1
Bind_Grid()
End Sub
Potete vedere che nel metodo
dgutenti_EditCommand si imposta solamente la proprietà
EditItemIndex, che rappresenta l?id della riga da modificare, con l?id della riga selezionata. In questo modo quando il metodo
Bind_Grid() va a riassociare i dati, viene letta la proprietà appena impostata ed automaticamente vengono generate le Textbox per le colonne DataBound.
Per fare in modo che una colonna non venga influenzata in fase di modifica basta mettere la proprietà
ReadOnly a
true.
<asp:BoundColumn Visible="False" DataField="idcomuneresidenza" ReadOnly="True" HeaderText="istat_comune"></asp:BoundColumn>
Ecco il risultato quando si clicca sul pulsante modifica:
Le celle della riga sono state modificate inserendo un controllo Textbox correttamente riempito.
Il metodo
Bind_Grid() non fa altro che eseguire una query sul database per il recupero dei dati. Ecco il codice:
Private Sub Bind_Grid()
Dim tabellaUtenti As DataTable
'Controllo la sessione per verificare se e' presente la tabella
tabellaUtenti = Session("tblUsers")
If tabellaUtenti Is Nothing Then
Dim objConn As New SqlConnection
objConn.ConnectionString() = strConn
Dim objCommand As New SqlCommand
objCommand.CommandText = "CARICA_UTENTI"
objCommand.CommandType = CommandType.StoredProcedure
objCommand.Connection = objConn
Dim objAdapter As New SqlDataAdapter
objAdapter.SelectCommand = objCommand
tabellaUtenti = New DataTable
Try
Try
objAdapter.Fill(tabellaUtenti)
Finally
objConn.Dispose()
End Try
Catch ex As Exception
lblMessage.Text = "Errore"
Finally
objConn = Nothing
objAdapter = Nothing
End Try
'Metto la tabella in sessione
Session("tblUsers") = tabellaUtenti
End If
'Associo la tabella al datagrid
dgUtenti.DataSource = tabellaUtenti
dgUtenti.DataBind()
End Sub
Prendiamo ora in esame la nostra
DropDownlist relativa alla colonna
"Occupazione". Per far in modo che compaia il famoso menù a tendina, è necessario intervenire su un altro evento del DataGrid:
OnItemDataBound. Questo evento viene scatenato subito dopo l?evento
OnItemCreated e questi due eventi permettono la creazione e il collegamento di tutti i dati presenti nel
DataGrid.
Se noi lasciassimo il codice com?è stato descritto fin ora, il risultato sarebbe il seguente:
Il menù a tendina non avrebbe nessun valore.
Andiamo quindi a implementare il metodo dell'evento
OnItemDatabound.
Private Sub dgUtenti_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles dgUtenti.ItemDataBound
lblMessage.Text = lblMessage.Text & e.Item.ItemType.ToString & "
"
If e.Item.ItemType = ListItemType.EditItem Then
Dim DRV As DataRowView = CType(e.Item.DataItem, DataRowView)
'------------------------------
'Carico la dropdownlist relativa all'occupazione
Bind_Drop_Occupazione(sender, e, DRV)
'------------------------------
'Acquisisco l'istat della regione che sta in una boundcolumn non visibile del datagrid
'Splitto il codice per risalire alla regione provicnia e comune
Dim istC = CStr(DRV("idcomuneresidenza")) 'istat del comune
Dim istP = Left(istC, 5)
Dim istR = Left(istP, 2) 'istat della regione
istP = Right(istP, 3) 'istat della provincia
'------------------------------
'Carico la dropdownlist relativa alla regione
Bind_Drop_Regioni(sender, e, DRV, istR)
'------------------------------
'Carico la dropdownlist relativa alla provincia
Bind_Drop_Province(sender, e, DRV, istR, istP)
'------------------------------
'Carico la dropdownlist relativa al comune
Bind_Drop_Comuni(sender, e, DRV, istP, istC)
'------------------------------
End If
End Sub
Come prima cosa andiamo ad intercettare la situazione che ci interessa, ovvero quando viene creata la riga
EditItem.
La proprietà
ItemType dell'oggetto
Item ci dice che tipo di Item (riga) è stata creata. Come prima istruzione del metodo, infatti ho fatto stampare a video
ItemType per vedere che cosa fa il metodo.
Il risultato è il seguente:
Possiamo vedere che vengono create ed associate le righe dell'
Header, Item, AlternatinItem e Footer (che non si vede).
Quando andiamo a cliccare su modifica, durante l'
OnItemDataBound, si andrà a creare ed associare al DataGrid la riga
EditItem come mostrato qui sotto, ed è allora che dovremmo referenziare ed associare il nostro controllo.
Ecco il motivo per cui e' stata inserita la condizione:
If e.Item.ItemType = ListItemType.EditItem Then Se avessimo tralasciato questa condizione avremmo ottenuto un errore in quanto saremmo andati a referenziare un oggetto inesistente nell'Header, che è la prima riga collegata.
Referenziamo quindi la riga editata come
DataRowView tramite il casting:
Dim DRV As DataRowView = CType(e.Item.DataItem, DataRowView)e richiamiamo un metodo che esegue il Bind della
DropDownList:
Private Sub Bind_Drop_Occupazione(ByRef sender As Object, ByRef e As System.Web.UI.WebControls.DataGridItemEventArgs, ByRef DRV As DataRowView)
Dim Current As String = DRV("Occupazione")
Dim DDLOCC As DropDownList = CType(e.Item.Cells(5).Controls(1), DropDownList)
Dim item As ListItem
DDLOCC.DataSource = CreaElencoOccupazioni()
DDLOCC.DataTextField = "Occupazione"
DDLOCC.DataValueField = "id"
DDLOCC.DataBind()
item = DDLOCC.Items.FindByText(Current)
If Not item Is Nothing Then item.Selected = True
End Sub
Spieghiamo nel dettaglio il metodo.
Si dichiara una variabile Current a cui si assegna il valore della colonna
"Occupazione" della
DataRowView; nel nostro caso in Current verrà assegnato il valore Impiegato.
Dim Current As String = DRV("Occupazione")Si dichiara un oggetto di tipo
DropDownList e si fa il casting al tipo del controllo presente nella riga editata (la Dropdownlist) che si trova nella cella 5, controllo 1.
Dim DDLOCC As DropDownList = CType(e.Item.Cells(5).Controls(1), DropDownList)(vedremo in seguito che è possibile usare il metodo
FindControl se si conosce l'ID del controllo). La cella 5 è la colonna dove sono contenuti gli oggetti che riguardano l'occupazione, controllo 1 è l?unico controllo presente nell'
EditItemTemlate (la drop vuota).
Si dichiara un oggetto di tipo
ListItem che referenzierà l'elenco delle occupazioni.
Si associano i dati impostando il DataSource e le proprietà della
DropDownList, e si effettua il Bind dei dati.
Il metodo
CreaElencoOccupazioni() recupera l'elenco dal database:
Private Function CreaElencoOccupazioni() As DataTable
Dim objConn As New SqlConnection
objConn.ConnectionString() = strConn
Dim objCommand As New SqlCommand
objCommand.CommandText = "CARICA_OCCUPAZIONI"
objCommand.CommandType = CommandType.StoredProcedure
objCommand.Connection = objConn
Dim objAdapter As New SqlDataAdapter
objAdapter.SelectCommand = objCommand
Dim tabellaOccupazioni As New DataTable
Try
Try
objAdapter.Fill(tabellaOccupazioni)
Finally
objConn.Dispose()
End Try
Catch ex As Exception
lblMessage.Text = "Errore"
Finally
objConn = Nothing
objAdapter = Nothing
End Try
Return tabellaOccupazioni
End Function
Si procede ora con la ricerca e la selezione dell'elemento originale editato. In questo modo quando la
DropDownList verrà renderizzata come primo elemento del controllo verrà visualizzato quello originale:
item = DDLOCC.Items.FindByText(Current)
If Not item Is Nothing Then item.Selected = True
Per ulteriori informazioni è possibile consultare il seguente Tip:
Come selezionare un valore in una DropdownList ?Finalmente la Combo Box è collegata e puntata sul valore corretto.
Ora spingiamoci oltre. Fino ad ora abbiamo tralasciato la colonna
"Ubicazione" che visualizza la regione, la provincia ed il comune di residenza dell'utente. Al contrario della colonna
"Occupazione", l?Edit della colonna ubicazione deve tener conto che se l?utente cambia regione, gli elenchi della provincia e dei comuni si devono aggiornare.
Vediamo quindi come gestire il
Postback del controllo all'interno del DataGrid.
Prima di tutto per poter visualizzare la regione, la provincia ed il comune corretti, ho utilizzato la colonna nascosta
idcomuneresidenza che ha il codice
ISTAT del Comune, da cui ricavo quello della
Provincia e della
Regione. (Ovviamente sul databse ho 3 tabelle con le regioni, province, comuni e relativo codice ISTAT).
Il procedimento di associazione e puntamento dei dati è lo stesso che abbiamo applicato per visualizzare la
DropDownList relativa alla colonna occupazione.
Protected Sub Bind_Drop_Regioni(ByRef sender As Object, ByRef e As System.Web.UI.WebControls.DataGridItemEventArgs, ByRef DRV As DataRowView, Optional ByRef istr As String = "")
Dim DDLREG As DropDownList = CType(e.Item.FindControl("drpRegioni"), DropDownList)
Dim item As ListItem
DDLREG.DataSource = CreaElencoRegioni()
DDLREG.DataTextField = "regione"
DDLREG.DataValueField = "istat_regione"
DDLREG.DataBind()
If istr <> "" Then
item = DDLREG.Items.FindByValue(istr)
If Not item Is Nothing Then item.Selected = True
End If
End Sub
Protected Sub Bind_Drop_Province(ByRef sender As Object, ByRef e As System.Web.UI.WebControls.DataGridItemEventArgs, ByRef DRV As DataRowView, ByRef istR As String, Optional ByVal istP As String = "")
Dim DDLPRO As DropDownList = CType(e.Item.FindControl("drpProvice"), DropDownList)
Dim item As ListItem
DDLPRO.DataSource = CreaElencoProvince(istR)
DDLPRO.DataTextField = "citta"
DDLPRO.DataValueField = "istat_provincia"
DDLPRO.DataBind()
If istP <> "" Then
item = DDLPRO.Items.FindByValue(istP)
If Not item Is Nothing Then item.Selected = True
End If
End Sub
Protected Sub Bind_Drop_Comuni(ByRef sender As Object, ByRef e As System.Web.UI.WebControls.DataGridItemEventArgs, ByRef DRV As DataRowView, Optional ByRef istP As String = "", Optional ByVal istC As String = "")
Dim DDLCOM As DropDownList = CType(e.Item.FindControl("drpComuni"), DropDownList)
Dim item As ListItem
DDLCOM.DataSource = CreaElencoComuni(istP)
DDLCOM.DataTextField = "comune"
DDLCOM.DataValueField = "istat_comune"
DDLCOM.DataBind()
If istC <> "" Then
item = DDLCOM.Items.FindByValue(istC)
If Not item Is Nothing Then item.Selected = True
End If
End Sub
Come potete vedere il procedimento è lo stesso, cambiano solamente alcuni parametri passati ai metodi che permettono di recuperare set di dati specifici. Se io scelgo Lazio, è inutile prelevare tutte le province d?Italia. Andrò a selezionare solamente quelle del Lazio.
Per gestire i
Postback di questi controlli è necessario implementare il metodo dell?evento
OnSelectedIndexChanged ed impostare l'
Autopostback a
true. In questo modo appena seleziono, ad esempio, una diversa regione vado ad intercettare il valore del nuovo elemento e ricarico gli elenchi delle province e dei comuni.
Ecco il codice che viene eseguito quando cambio regione:
Protected Sub drpRegioni_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
'Prendo il riferimento del datagrid
Dim dg As DataGrid = DirectCast(Page.FindControl("dgUtenti"), DataGrid)
'Prendo l'id della riga che e' stata editata
Dim idEditItem As Int32 = dg.EditItemIndex()
'Prendo il riferimento della drop delle province
Dim drpP As DropDownList = CType(dg.Items(idEditItem).FindControl("drpProvice"), DropDownList)
'Prendo il riferimento della drop dei comuni
Dim drpC As DropDownList = CType(dg.Items(idEditItem).FindControl("drpComuni"), DropDownList)
If sender.selectedvalue = "00" Then
'Non ho selezionato regioni (ho selezionato campo vuoto) e devo nascondere la provincia ed il comune
drpP.Visible = False
drpC.Visible = False
Else
'Devo cambiare l'elenco delle province e mascherare quella dei comuni.
drpP.DataSource = CreaElencoProvince(sender.selectedvalue)
drpP.DataTextField = "citta"
drpP.DataValueField = "istat_provincia"
drpP.DataBind()
drpC.Visible = False
End If
End Sub
Per poter aggiornare l?elenco delle province è necessario avere il riferimento al controllo che è contenuto del DataGRid.
Per cui si deve prima referenziare il DataGrid (che fa parte dei controlli della pagina), utilizzando il metodo
FindControl della classe
Page;
Dim dg As DataGrid = DirectCast(Page.FindControl("dgUtenti"), DataGrid)Si intercetta la riga che sta nello stato di Edit:
Dim idEditItem As Int32 = dg.EditItemIndex()Si ricerca la
DropDownList utilizzando lo stesso metodo che abbiamo utilizzato per referenziare il DataGrid, ma applicato non alla pagina, ma allo stesso DataGrid.
Dim drpP As DropDownList = CType(dg.Items(idEditItem).FindControl("drpProvice"), DropDownList)A questo punto si effettuano I dovuti controlli e si aggiornano le liste.
Per le province si applica lo stesso procedimento.
Protected Sub drpProvince_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
'Prendo il riferimento del datagrid
Dim dg As DataGrid = DirectCast(Page.FindControl("dgUtenti"), DataGrid)
'Prendo l'id della riga che e' stata editata
Dim idEditItem As Int32 = dg.EditItemIndex()
'Prendo il riferimento della drop dei comuni
Dim drpC As DropDownList = CType(dg.Items(idEditItem).FindControl("drpComuni"), DropDownList)
If sender.selectedvalue = "000" Then
'Non ho selezionato province (ho selezionato campo vuoto) e devo nascondere il comune
drpC.Visible = False
Else
'Devo cambiare l'elenco dei comuni.
drpC.DataSource = CreaElencoComuni(sender.selectedvalue)
drpC.DataTextField = "comune"
drpC.DataValueField = "istat_comune"
drpC.DataBind()
drpC.Visible = True
End If
End Sub
I metodi:
CreaElencoProvince(sender.selectedvalue)
CreaElencoComuni(sender.selectedvalue)Non fanno altro che recuperare i dati dal Database.
Private Function CreaElencoProvince(ByRef IstatRegione As String) As DataTable
Dim objConn As New SqlConnection
objConn.ConnectionString() = strConn
Dim objCommand As New SqlCommand
objCommand.Parameters.Add(New SqlClient.SqlParameter("@ID", IstatRegione))
objCommand.CommandText = "CARICA_ELENCO_PROVINCE_BYIDREG"
objCommand.CommandType = CommandType.StoredProcedure
objCommand.Connection = objConn
Dim objAdapter As New SqlDataAdapter
objAdapter.SelectCommand = objCommand
Dim tabellaP As New DataTable
Try
Try
objAdapter.Fill(tabellaP)
Finally
objConn.Dispose()
End Try
Catch ex As Exception
lblMessage.Text = "Errore"
Finally
objConn = Nothing
objAdapter = Nothing
End Try
Return tabellaP
End Function
Private Function CreaElencoComuni(ByRef IstatProvincia As String) As DataTable
Dim objConn As New SqlConnection
objConn.ConnectionString() = strConn
Dim objCommand As New SqlCommand
objCommand.Parameters.Add(New SqlClient.SqlParameter("@ID", IstatProvincia))
objCommand.CommandText = "CARICA_ELENCO_COMUNI_BYIDPRO"
objCommand.CommandType = CommandType.StoredProcedure
objCommand.Connection = objConn
Dim objAdapter As New SqlDataAdapter
objAdapter.SelectCommand = objCommand
Dim tabellaR As New DataTable
Try
Try
objAdapter.Fill(tabellaR)
Finally
objConn.Dispose()
End Try
Catch ex As Exception
lblMessage.Text = "Errore"
Finally
objConn = Nothing
objAdapter = Nothing
End Try
Return tabellaR
End Function
Una volta effettuate tutte le modifiche si potranno salvare i dati nella sorgente implementando il metodo
dgUtenti_UpdateCommand.
Come avete potuto vedere, il DataGrid è un oggetto molto estendibile. Naturalmente è necessario scrivere dell'ulteriore codice per ottimizzarlo ma possiamo ottenere in cambio di questa fatica delle applicazioni molto più intuitive e interattive da sottoporre all'utente finale che spesso sa poco di Informatica.
Questo esempio è utile come base per poter implementare ulteriori funzionalità, per esempio, rendering della Fotografie di un dipendente partendo da un campo del database binario BLOB, oppure renderizzare al posto della data di creazione in formato testo il controllo
DateTimePicker. Di spazio per sbizzarrirvi ce n'è a dismisura, quindi ora la palla passa a voi!