|
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:

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:
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
 |
|