Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - DataGridView  Parte II

Guida al Visual Basic .NET

Capitolo 78° - DataGridView Parte II

<< Precedente Prossimo >>
Manipolazione dei dati

Nell'esempio precedente, l'utente poteva modificare ed eventualmente cancellare dati esistenti, ma, ancora una volta, ho tralasciato di implementare l'aggiunta. In questo caso, però, aver lasciato la possibilità di agire liberamente sui dati aggiunti avrebbe causato non pochi danni, poiché gli ID sono tutti nascosti e il controllo datagridview non implementa nessun tipo di autoincremento per i valori numerici: aggiungendo nuove righe, l'utente non avrebbe potuto influire sulle celle ID, che sarebbero rimaste vuote e avrebbero causato sempre lo stesso errore (avendolo noi gestito nel modo che sapete, l'unica scelta possibile sarebbe stata quella di cancellare l'ultima riga e perciò non si sarebbe potuto aggiungere nulla in ogni caso). In poche parole, bisogna intervenire a livello di codice.
Possiamo correggere in modo elegante aggiungendo due ContextMenu con un solo elemento "Aggiungi cliente" o "Aggiungi ordine" ed associare ciascuno dei due a uno dei datagridview. Per aggiungere un nuovo cliente basta agire direttamente sulla tabella customer, richiamando AddCustomersRow e lasciando tutti i parametri vuoti (con la data di default), poiché nessuno di essi è specificato come NOT NULL nella struttura della tabella. Per l'ordine, invece, non è possibile seguire la stessa strada, poiché quasi tutti gli attributi non possono essere null. Per questo creeremo una nuova finestra di dialogo di nome CreateOrderDialog con quest'aspetto:

NewOrderDialog.jpg
e con questo semplice codice:

Public Class CreateOrderDialog

    Private CustomerID As Int32
    Private _NewOrder As AppDataSet.OrdersRow
    
    'Restituisce una nuova riga con gli attributi impostati
    'nel dialog
    Public ReadOnly Property NewOrder() As AppDataSet.OrdersRow
        Get
            Return _NewOrder
        End Get
    End Property

    'Per creare un nuovo ordine ci serve l'ID del cliente ad
    'esso associato, perciò dobbiamo costringere il chiamante
    '(ossia noi stessi XD) a passarci questo dato in qualche
    'modo. In questo caso, sovrascriviamo il metodo ShowDialog
    'mediante shadowing:
    Public Shadows Function ShowDialog(ByVal CustomerID As Int32) As DialogResult
        Me.CustomerID = CustomerID
        Return MyBase.ShowDialog()
    End Function

    Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
        If String.IsNullOrEmpty(txtItemName.Text) Then
            MessageBox.Show("Specificare una descrizione valida del prodotto!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
            Exit Sub
        End If

        If nudItemPrice.Value = 0.0F Then
            MessageBox.Show("Prezzo non valido!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
            Exit Sub
        End If

        _NewOrder = My.Forms.Form1.MainDatabase.Orders.NewOrdersRow()
        With _NewOrder
            .CustomerID = Me.CustomerID
            .ItemName = txtItemName.Text
            .ItemPrice = nudItemPrice.Value
            .ItemCount = nudItemCount.Value
            .CreationDate = dtpCreationDate.Value
            .Settled = chbSettled.Checked
        End With
        
        Me.DialogResult = System.Windows.Forms.DialogResult.OK
        Me.Close()
    End Sub

    Private Sub Cancel_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Cancel_Button.Click
        Me.DialogResult = System.Windows.Forms.DialogResult.Cancel
        Me.Close()
    End Sub

End Class

Tenendo conto del nuovo dialog appena scritto, il codice del form diventerebbe:

Imports MySql.Data.MySqlClient
Public Class Form1
        
    '...
    
    Private Sub strAddCustomer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strAddCustomer.Click
        MainDatabase.Customers.AddCustomersRow("", "", "", "", Date.Now, 0)
    End Sub

    Private Sub strAddOrder_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strAddOrder.Click
        If dgvCustomers.CurrentRow Is Nothing Then
            Exit Sub
        End If

        Dim CID As Int32 = CInt(dgvCustomers.CurrentRow.Cells(0).Value)
        Dim OrderDialog As New CreateOrderDialog()

        If OrderDialog.ShowDialog(CID) = Windows.Forms.DialogResult.OK Then
            MainDatabase.Orders.AddOrdersRow(OrderDialog.NewOrder)
            RefreshPreview(CID)
        End If
    End Sub
    
End Class

Manca ancora un'ultima cosa per terminare il programma, ossia il salvataggio. Possiamo, ad esempio, salvare tutto alla chiusura del form:

Public Class Form1
    '...
    
    Private Sub Form1_FormClosing(ByVal sender As System.Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles MyBase.FormClosing
        Dim Connection As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;")
        Dim Adapter As New MySqlDataAdapter()
        Dim Builder As MySqlCommandBuilder

        Try
            Connection.Open()
            Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Connection)
            Builder = New MySqlCommandBuilder(Adapter)
            Adapter.Update(MainDatabase.Customers)

            Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Orders;", Connection)
            Builder = New MySqlCommandBuilder(Adapter)
            Adapter.Update(MainDatabase.Orders)

        Catch Ex As Exception
            If MessageBox.Show("Impossibile connettersi al database per il salvataggio. Proseguire nella chiusura? Tutti i dati non salvati andranno persi.", Me.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Error) = Windows.Forms.DialogResult.No Then
                e.Cancel = True
            End If
        Finally
            Connection.Close()
        End Try
    End Sub
    
End Class

P.S.: ricordatevi di riassegnare le immagini all'ultima cella dopo che le righe sono state riordinate (è possibile ordinare automaticamente i dati cliccando sull'intestazione di una colonna).

Stampa

Il passo successivo di un gestionale consiste, di norma, nel voler stampare i dati che si gestiscono. Esistono parecchi componenti già pronti per stampare un datagridview, nonché molti strumenti integrati nell'IDE (reports), acquistabili e non, e anche un discreto numero di "scorciatoie", che non fanno altro che disegnare il controllo su un qualche supporto e delegarne la stampa ad un'altra applicazione. Potete scegliere liberamente di usare uno dei metodi sopracitati senza sprecarvi più di tanto nel codice. In ogni caso, la soluzione che propongo potrebbe essere utile per ripassare l'uso di Graphics:

Public Class Form1

    '...
    
    'Indici dei campi da stampare
    Private PrintingFields() As Int32 = {1, 2, 3, 4, 5, 7}

    Private Sub PrintDoc_PrintPage(ByVal sender As System.Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDoc.PrintPage
        'Indice della prima riga della prima pagina
        Static RowIndex As Int32 = 0
        'Indica se si tratta della prima pagina
        Static FirstPage As Boolean = True

        'Offset orizzontale
        Dim CellOffset As Int32 = e.MarginBounds.X
        'Offset verticale
        Dim Y As Int32 = e.MarginBounds.Y
        'Larghezza totale colonne
        Dim TotalWidth As Int32 = 0

        'Se è la prima pagina, stampa le intestazioni
        If FirstPage Then
            For Each Column As DataGridViewColumn In dgvCustomers.Columns
                'Stampa solo gli header dei campi contemplati
                If Array.IndexOf(PrintingFields, Column.Index) < 0 Then
                    Continue For
                End If

                e.Graphics.DrawString(Column.HeaderText, dgvCustomers.ColumnHeadersDefaultCellStyle.Font, New SolidBrush(dgvCustomers.ColumnHeadersDefaultCellStyle.ForeColor), CellOffset, Y)
                CellOffset += Column.Width
                TotalWidth += Column.Width
            Next
            Y += dgvCustomers.ColumnHeadersHeight
        End If

        'Stampa le righe
        For I As Int32 = RowIndex To dgvCustomers.RowCount - 1
            Dim Row As DataGridViewRow = dgvCustomers.Rows(I)

            CellOffset = e.MarginBounds.X
            'Ottiene lo stile delle celle
            Dim Style As DataGridViewCellStyle
            'Distingue fra quelle pari e dispari
            If Row.Index Mod 2 = 0 Then
                Style = dgvCustomers.DefaultCellStyle
            Else
                Style = dgvCustomers.AlternatingRowsDefaultCellStyle
            End If

            'Se nessun font particolare è specificato, prende
            'quello del controllo
            If Style.Font Is Nothing Then
                Style.Font = dgvCustomers.Font
            End If
            'Se nessun colore particolare è specificato (di
            'default valore argb=(0,0,0,0)) prende quello del
            'controllo
            If Style.ForeColor.A = 0 Then
                Style.ForeColor = dgvCustomers.ForeColor
            End If

            'Disegna una striscia del colore di sfondo della riga
            e.Graphics.FillRectangle(New SolidBrush(Style.BackColor), CellOffset, Y, TotalWidth, dgvCustomers.RowTemplate.Height)
            'E la contorna con un tratto nero
            e.Graphics.DrawRectangle(Pens.Black, CellOffset, Y, TotalWidth, dgvCustomers.RowTemplate.Height)

            For Each Cell As DataGridViewCell In Row.Cells
                'Stampa solo gli attributi contemplati
                If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < 0 Then
                    Continue For
                End If

                'Se la cella contiene un'immagime, la stampa
                If Cell.ValueType Is GetType(Image) Then
                    Dim Img As Image = Cell.Value
                    e.Graphics.DrawImage(Img, CellOffset + dgvCustomers.Columns(Cell.ColumnIndex).Width  2 - Cell.Value.Width  2, Y + dgvCustomers.CurrentRow.Height  2 - Img.Height  2)
                Else
                    Dim Height As Int32
                    Dim StrVal As String

                    'Formatta la data in forma compatta
                    If Cell.ValueType Is GetType(Date) Then
                        StrVal = CType(Cell.Value, Date).ToShortDateString()
                    Else
                        StrVal = Cell.Value.ToString()
                    End If
                    'Calcola l'altezza del testo per centrarlo
                    Height = Cell.MeasureTextHeight(e.Graphics, StrVal, Style.Font, 500, TextFormatFlags.Default)

                    'Stampa il testo
                    e.Graphics.DrawString(StrVal, Style.Font, New SolidBrush(Style.ForeColor), CellOffset, Y + dgvCustomers.RowTemplate.Height  2 - Height  2)
                End If

                'Si sposta alla prossima ascissa
                CellOffset += dgvCustomers.Columns(Cell.ColumnIndex).Width

                'Per tutte le celle tranne l'ultima, disegna una linea
                'verticale di separazione dalla cella successiva
                If Array.IndexOf(PrintingFields, Cell.ColumnIndex) < PrintingFields.Length - 1 Then
                    e.Graphics.DrawLine(Pens.Black, CellOffset - 4, Y, CellOffset - 4, Y + dgvCustomers.RowTemplate.Height)
                End If
            Next

            'Aumenta l'ordinata
            Y += dgvCustomers.RowTemplate.Height
        Next

        If e.HasMorePages Then
            FirstPage = False
        Else
            FirstPage = True
            RowIndex = 0
        End If
    End Sub

    'strPrint è un sottoelemento del context menu
    Private Sub strPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles strPrint.Click
        Dim PDialog As New PrintDialog

        PDialog.Document = PrintDoc
        If PDialog.ShowDialog = Windows.Forms.DialogResult.OK Then
            PrintDoc.PrinterSettings = PDialog.PrinterSettings
            'Ridimensiona le colonne per far stare i testi in modo
            'corretto. N.B.: ho impostato la larghezza minima della
            'colonna Address a 190 pixel
            dgvCustomers.AutoResizeColumns()
            PrintDoc.Print()
        End If
    End Sub
End Class

Risultato:

DataGridViewPrint.jpg

Validazione dell'input

E' possibile convalidare l'input dell'utente o indicare che alcuni dati sono erronei utilizzando l'evento CellValidating o RowValidating:

Private Sub dgvCustomers_CellValidating(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles dgvCustomers.CellValidating
    If e.ColumnIndex > 0 And e.ColumnIndex < 5 Then
        With dgvCustomers.Item(e.ColumnIndex, e.RowIndex)
            If String.IsNullOrEmpty(e.FormattedValue.ToString()) Then
                .ErrorText = "Il campo non dovrebbe essere lasciato vuoto"
            Else
                .ErrorText = Nothing
            End If
        End With
    End If
End Sub


DataGridViewValidate.jpg

<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...