Guida al Visual Basic .NET
Capitolo 77° - DataGridView Parte I
Introduzione
DataGridView è uno dei controlli più potenti e grandi del Framework .NET. Consente la visualizzazione di dati in una tabella
ed è per questo motivo fortemente correlato all'uso dei database, anche se naturalmente supporta, tramite la proprietà DataSource,
il binding di qualsiasi oggetto:
Come avete visto c'è una marea di membri, e un numero consistente di questi sono dedicati ad impostare l'"estetica" del controllo.
Ma tutto questo non è nulla se confrontato alla quantità di eventi che DataGridView espone (e al numero di disturbi mentali che è
solita causare nei programmatori sani). Un classico esempio di gestionale
La DataGridView è un controllo usatissimo soprattutto in quei noiosissimi programmi che qualcuno chiama gestionali, e che un gran numero
di poveri programmatori è costretto a scrivere per guadagnarsi il pane quotidiano. Per questo motivo, il prossimo esempio sarà
particolarmente realistico. Andremo a scrivere un'applicazione per gestire clienti e ordini. CREATE TABLE `customers` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `FirstName` char(150) DEFAULT NULL, `LastName` char(150) DEFAULT NULL, `Address` char(255) DEFAULT NULL, `PhoneNumber` char(30) DEFAULT NULL, `RegistrationDate` date NOT NULL, `AccountType` int(5) DEFAULT '0', PRIMARY KEY (`ID`) ); CREATE TABLE `orders` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `CustomerID` int(11) NOT NULL, `ItemName` char(255) NOT NULL, `ItemPrice` float unsigned NOT NULL, `ItemCount` int(10) unsigned DEFAULT '1', `CreationDate` date NOT NULL, `Settled` tinyint(1) NOT NULL, PRIMARY KEY (`ID`) );
E, per completezza, bisogna aggiungere anche le rispettive rappresentazioni tabulari in un nuovo dataset (si tratta solo di ricopiare). Imports MySql.Data.MySqlClient 'Prima di iniziare: ' - MainDatabase è un'istanza di AppDataSet, ossia il ' dataset tipizzato creato mediante l'editor che contiene le ' due tabelle. ' - bsOrders è il BindingSource che useremo come sorgente ' dati per dgvOrders. Ricordatevi che: ' bsOrders.DataSource = MainDatabase ' bsOrders.DataMember = "Orders" o MainDatabase.Orders ' - AllowUserToAddRows = False per entrambi i datagridview Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;") Dim Adapter As New MySqlDataAdapter() Try Connection.Open() Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection) Adapter.Fill(MainDatabase.Customers) Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection) Adapter.Fill(MainDatabase.Orders) Catch Ex As Exception MessageBox.Show("Impossibile connettersi al database!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error) Finally Connection.Close() End Try 'Imposta la sorgente dati di dgvCustomers. Quando questa 'proprietà viene impostata, crea automaticamente tutte 'le colonne necessarie, ne imposta il tipo e 'l'intestazione. Per questo motivo, tutte le colonne che 'andremo ad usare iniziano ad esistere solo dopo questa 'riga di codice. Tutte quelle che erano state definite 'prima vengono eliminate. dgvCustomers.DataSource = MainDatabase.Customers 'Nasconde la prima e la settima colonna, ossia l'ID e 'l'AccountType. La prima non deve essere visibile poiché 'contiene dati che non riguardano l'utente, mentre 'l'ultima la nascondiamo perchè avevamo detto di 'voler visualizzare il tipo di account con un'icona dgvCustomers.Columns(0).Visible = False dgvCustomers.Columns(6).Visible = False 'Crea una nuova colonna di datagridview adatta a contenere 'immagini. Esistono molti tipi di colonna (button, combobox, 'linklabel, image, eccetera...), di cui la più comune 'è una che contiene solo testo. Il tipo di una 'colonna indica il tipo di dati che tutte le celle ad essa 'sottostanti devono contenere. In questo caso vogliamo 'che l'ultima colonna contenga una piccola immagine 'indicante il livello dell'account (ci saranno tre livelli) Dim Img As New DataGridViewImageColumn 'Imposta l'immagine di default Img.Image = imgAccountTypes.Images(0) 'E l'intestazione della colonna Img.HeaderText = "AccountLevel" 'Quindi la aggiunge a quelle esistenti dgvCustomers.Columns.Add(Img) 'Poi cicla attraverso tutte le righe, controllando il 'contenuto della settima cella di ogni riga (ossia il 'valore corrdispondente ad AccountType, un numero intero), 'e imposta il contenuto dell'ottava prelevando la 'rispettiva immagine dall'imagelist. 'Ricordate che la colonna di indice 6, pur essendo 'nascosta, esiste comunque For Each Row As DataGridViewRow In dgvCustomers.Rows Row.Cells(7).Value = imgAccountTypes.Images(CInt(Row.Cells(6).Value)) Next 'Imposta come sorgente di dati di dgvOrders il binding 'source bsOrders, specificato in precedenza dgvOrders.DataSource = bsOrders 'E nasconde le prime due colonne, ossia CustomerID e ID dgvOrders.Columns(0).Visible = False dgvOrders.Columns(1).Visible = False 'Impone di visualizzare solo le tuple di ID pari a -1, 'ossia nessuna bsOrders.Filter = "ID=-1" End Sub Private Sub dgvCustomers_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles dgvCustomers.KeyDown 'Intercettiamo la pressione di un pulsante quando l'utente 'si trova nell'ultima colonna (quella dell'icona) If dgvCustomers.CurrentCell.ColumnIndex = 7 Then 'Ottiene il valore di AccountType Dim CurValue As Int32 = CInt(dgvCustomers.CurrentRow.Cells(6).Value) 'Se è stato premuto +, aumenta il valore di 1 'Se è stato premuto -, lo decrementa di 1 If e.KeyCode = Keys.Oemplus Then CurValue += 1 ElseIf e.KeyCode = Keys.OemMinus Then CurValue -= 1 End If 'Fa in modo di non andare oltre i limit imposti If CurValue < 0 Then CurValue = 0 ElseIf CurValue > 2 Then CurValue = 2 End If 'Quindi imposta il nuovo valore di AccountType dgvCustomers.CurrentRow.Cells(6).Value = CurValue 'E la corrispondente nuova immagine dgvCustomers.CurrentCell.Value = imgAccountTypes.Images(CurValue) End If End Sub 'L'evento DataError viene generato ogniqualvolta l'utente 'non rispetti i vincoli imposti dal database. Ad esempio, 'viene generato se un campo marcato come NOT NULL viene 'lasciato vuoto, o se una data non è valida, o 'se un numero è troppo grande o troppo piccolo, 'oppure ancora se non viene soddisfatto il vincolo di 'unicità, eccetera... 'In questi casi, se il programmatore non gestisce l'evento, 'appare una finestra di default che riporta tutto il testo 'dell'eccezione e vi assicuro che non è una bella cosa Private Sub dgvCustomers_DataError(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewDataErrorEventArgs) Handles dgvCustomers.DataError Dim Result As DialogResult 'Riporta l'errore all'utente, e lascia scegliere se 'modificare i dati incompatibili oppure annullare 'le modifiche e cancellare la riga Result = MessageBox.Show("Si ? verificato un errore di compatibilit? dei dati immessi. Messaggio:" & _ Environment.NewLine & e.Exception.Message & Environment.NewLine & _ "E' possibile che dei dati mancanti compromettano il database. Premere S? per modificare opportunamente " & _ "tali valori, o No per cancellare la riga.", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) If Result = Windows.Forms.DialogResult.Yes Then 'Annulla questo evento: non viene generata la 'finestra di errore e.Cancel = True 'Pone il cursore sulla casella corrente e obbliga 'ad iniziare l'edit mode. Il valore booleano tra 'parentesi indica di selezionare l'intero contenuto 'della cella corrente dgvCustomers.BeginEdit(True) Else 'Annulla l'eccezione e l'evento, quindi cancella 'la riga corrente e.ThrowException = False e.Cancel = True 'Le righe "nuove", ossia quelle in cui non è 'stato salvato ancora nessun dato, non possono essere 'eliminate (così dice il datagridview...) If Not dgvCustomers.CurrentRow.IsNewRow Then dgvCustomers.Rows.Remove(dgvCustomers.CurrentRow) End If End If End Sub 'Questa procedura aggiorna la ListView con alcuni dettagli 'sullo stati dei pagamenti Private Sub RefreshPreview(ByVal CustomerID As Int32) lstPreview.Items.Clear() 'Cerca il cliente Dim Customer As AppDataSet.CustomersRow = _ MainDatabase.Customers.FindByID(CustomerID) 'Se non esiste, esce If Customer Is Nothing Then Exit Sub End If Dim TotalPaid, TotalUnpaid As Single Dim Delay As TimeSpan 'Notate che qui agiamo direttamente sul dataset, 'perchè contiene campi tipizzati, e ci consente di 'utilizzare meno operatori di cast For Each Order As AppDataSet.OrdersRow In MainDatabase.Orders 'Conta solo gli ordini associati a un cliente If Order.CustomerID <> CustomerID Then Continue For End If 'Se l'ordine è stato pagato, aggiunge il 'totale alla variabile TotalPaid, altrimenti a 'TotalUnpaid. 'Se l'ordine non è stato pagato, inoltre, 'calcola il ritardo maggiore nel pagamento If Order.Settled Then TotalPaid += Order.ItemPrice * Order.ItemCount Else TotalUnpaid += Order.ItemPrice * Order.ItemCount If Date.Now - Order.CreationDate > Delay Then Delay = Date.Now - Order.CreationDate End If End If Next Dim Item1 As New ListViewItem Item1.Text = "Ammontare pagato" Item1.SubItems.Add(String.Format("{0:N2}?", TotalPaid)) Dim Item2 As New ListViewItem Item2.Text = "Oneri futuri" Item2.SubItems.Add(String.Format("{0:N2}?", TotalUnpaid)) Dim Item3 As New ListViewItem Item3.Text = "Ritardo pagamento" Item3.SubItems.Add(CInt(Delay.TotalDays) & " giorni") Dim DaysLimit As Int32 'Un diverso tipo di account permette un maggior ritardo 'nei pagamenti... Select Case Customer.AccountType Case 0 DaysLimit = 60 Case 1 DaysLimit = 90 Case 2 DaysLimit = 120 Case Else DaysLimit = 60 End Select 'Se il cliente ha superato il limite con almeno uno 'dei suoi ordini, la riga viene colorata in rosso If Delay.TotalDays > DaysLimit Then Item3.ForeColor = Color.Red End If lstPreview.Items.Add(Item1) lstPreview.Items.Add(Item2) lstPreview.Items.Add(Item3) End Sub 'Evento generato quando l'utente si posizione su una riga Private Sub dgvCustomers_RowEnter(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvCustomers.RowEnter 'Se si tratta di una riga valida... If e.RowIndex < dgvCustomers.Rows.Count And e.RowIndex >= 0 Then Dim CurID As Int32 = CInt(dgvCustomers.Rows(e.RowIndex).Cells(0).Value) 'Aggiorna il filtro di bsOrders, per visualizzare solo gli 'ordini di quel dato cliente bsOrders.Filter = "CustomerID=" & CurID 'E aggiorna la listview RefreshPreview(CurID) End If End Sub 'Quando una cella di dgvOrders viene modificata, aggiorna 'la listview... Private Sub dgvOrders_CellEndEdit(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvOrders.CellEndEdit Try RefreshPreview(CInt(dgvCustomers.CurrentRow.Cells(0).Value)) Catch Ex As Exception End Try End Sub End Class
Ed ecco come potrebbe presentarsi:
C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...
|