Le avventure in VB.Net di un principiante ex-VB6 - 22
a cura di Oscar Zanin e Diego Cattaruzza (requisiti: Visual Basic Express e SqlServer)

Premessa
Nell'articolo precedente sono state illustrate alcune correzioni di difetti riscontrati nel lungo intervallo tra la ventesima e la ventunesima puntata, ma lo sviluppo del progetto PrimiPassi non è andato oltre l'implementazione del menu per aprire la form Clienti. In compenso, erano state fornite le immagini di altre form, praticamente uguali a quelle già sviluppate, che i lettori avrebbero dovuto provare a creare, come compito per casa.

In questo articolo faremo la correzione dei compiti, illustrando le nostre versioni - questo non significa che le vostre siano sbagliate: magari avete aggiunto qualche funzionalità cui noi non abbiamo pensato, o fatto meglio qualcosa che noi abbiamo fatto peggio: avete il blog di Diego per farcelo sapere e comunicarlo alla community.

Prima delle nuove form, verranno illustrati alcuni piccoli miglioramenti alle classi Formattazione, ConversioneTipo e ConvalidaDati.

I due nuovi metodi della classe Formattazione
Per evitare errori nel parsing del dato passato al metodo Formatta, causati dalla presenza di punti separatori di migliaia o del simbolo dell'euro, sono stati aggiunti i due metodi seguenti:

  Public Shared Function EliminaPuntiMigliaia(ByVal valore As String) As String
    If valore.Contains(".") Then valore = valore.Replace(".", "")
    Return valore
  End Function
  Public Shared Function EliminaSimboloEuro(ByVal valore As String) As String
    If valore.StartsWith("\'80 ") Then valore = valore.Remove(0, 2)
    If valore.StartsWith("-\'80 ") Then valore = valore.Remove(1, 2)
    Return EliminaPuntiMigliaia(valore)
  End Function

Il nome dei metodi è sufficientemente autoesplicativo, né il codice richiede spiegazioni, dato che usa metodi di String ampiamente illustrati nell'Help.
Piuttosto, è interessante vedere il nuovo metodo Formatta, che è stato modificato laddove è utile far uso dei nuovi metodi:

  Public Shared Function Formatta(ByVal valore As String, ByVal formato As TipoFor) As String
    If ControlloValorizzazioneStringa.IsNullOrTrimEmpty(valore) = True Then
      Return valore
    End If

    Select Case formato
      Case TipoFor.Data  'Data
        Return DateTime.Parse(valore).ToString("dd/MM/yyyy")
      Case TipoFor.Percentuale  'Percentuale
        Return Single.Parse(valore).ToString("0.00")
      Case TipoFor.Migliaia_senza_decimali  'Migliaia senza decimali
        Return Long.Parse(EliminaPuntiMigliaia(valore)).ToString("#,###")
      Case TipoFor.Migliaia_con_decimali 'Migliaia con decimali
        Return Double.Parse(EliminaPuntiMigliaia(valore)).ToString("#,##0.00")
      Case TipoFor.Valuta  'Valuta
        Return Decimal.Parse(EliminaSimboloEuro(valore)).ToString("\'80 #,##0.00")
      Case Else
        Return valore
    End Select

  End Function

I nuovi metodi risultano molto utili anche altrove, e precisamente nelle classi ConversioneTipo e ConvalidaDati.
Nella prima, oltre alla nuova direttiva per riferire la classe appema modificata:

Imports APP.UI.Formattazione

Vengono modificati tutti i metodi tranne StringToDate:

  Public Shared Function StringToInteger(ByVal dato As String) As Integer
    If String.IsNullOrEmpty(dato) Then
      Return Integer.MaxValue
    Else
      Return Integer.Parse(EliminaPuntiMigliaia(dato))
    End If
  End Function

  Public Shared Function StringToLong(ByVal dato As String) As Long
    If String.IsNullOrEmpty(dato) Then
      Return Long.MaxValue
    Else
      Return Long.Parse(EliminaPuntiMigliaia(dato))
    End If
  End Function

  Public Shared Function StringToSingle(ByVal dato As String) As Single
    If String.IsNullOrEmpty(dato) Then
      Return Single.MaxValue
    Else
      Return Single.Parse(EliminaPuntiMigliaia(dato))
    End If
  End Function

  Public Shared Function StringToDouble(ByVal dato As String) As Double
    If String.IsNullOrEmpty(dato) Then
      Return Double.MaxValue
    Else
      Return Double.Parse(EliminaPuntiMigliaia(dato))
    End If
  End Function

  Public Shared Function StringToDecimal(ByVal dato As String) As Decimal
    If String.IsNullOrEmpty(dato) Then
      Return Decimal.MaxValue
    Else
      Return Decimal.Parse(EliminaSimboloEuro(dato))
    End If
  End Function

Nella classe ConvalidaDati, vengono usati i nuovi metodi nei casi di parsing di stringhe numeriche:

Imports APP.UI.Formattazione

' ... omissis ...
Public Class ConvalidaDati

  Public Shared Function Convalida(ByVal valore As String, ByVal nomeCampo As String, _
                                   ByVal tipoCampo As TipoConv, ByVal obbligatorio As Boolean) _
                                   As Boolean

      ' ... omissis ...

      Case TipoConv.Migliaia_senza_decimali  'Migliaia senza decimali
      ' ... omissis ...
          Longc = Long.Parse(EliminaPuntiMigliaia(valore))
      ' ... omissis ...
      Case TipoConv.Migliaia_con_decimali  'Migliaia con decimali
      ' ... omissis ...
          Doublec = Double.Parse(EliminaPuntiMigliaia(valore))
      ' ... omissis ...
      Case TipoConv.Valuta  'Valuta

        Dim Decc As Decimal
        Try
          Decc = Decimal.Parse(EliminaSimboloEuro(valore))
      ' ... omissis ...
      'Migliaia senza decimali maggiore di zero
      Case TipoConv.Migliaia_senza_decimali_maggiore_di_zero  
      ' ... omissis ...
          Longc = Long.Parse(EliminaPuntiMigliaia(valore))
      ' ... omissis ...
      'Migliaia senza decimali maggiore o uguale a zero
      Case TipoConv.Migliaia_senza_decimali_maggiore_o_uguale_a_zero  
      ' ... omissis ...
          Longc = Long.Parse(EliminaPuntiMigliaia(valore))
      ' ... omissis ...
      'Migliaia con decimali maggiore di zero
      Case TipoConv.Migliaia_con_decimali_maggiore_di_zero  
      ' ... omissis ...
          Doublec = Double.Parse(EliminaPuntiMigliaia(valore))
      ' ... omissis ...
      'Migliaia con decimali maggiore o uguale a zero
      Case TipoConv.Migliaia_con_decimali_maggiore_o_uguale_a_zero  
      ' ... omissis ...
          Doublec = Double.Parse(EliminaPuntiMigliaia(valore))
      ' ... omissis ...
      Case TipoConv.Valuta_maggiore_di_zero  'Valuta maggiore di zero

        Dim Decc As Decimal
        Try
          Decc = Decimal.Parse(EliminaSimboloEuro(valore))
      ' ... omissis ...
      Case TipoConv.Valuta_maggiore_o_uguale_a_zero  'Valuta maggiore o uguale a zero

        Dim Decc As Decimal
        Try
          Decc = Decimal.Parse(EliminaSimboloEuro(valore))
      ' ... omissis ...
  End Function
End Class

La classe di dettaglio
Se avete diligentemente seguito la serie, e non scopiazzato, avrete certo notato come la form Fornitori sia molto simile alla form Client. Di conseguenza avete immaginato che, come è stato fatto per la form Clienti (vedi puntata 18), anche per la form Fornitori è necessario implementare una classe di dettaglio, di nome FornitoriRiferimenti, la cui struttura è speculare alla tabella Fornitori_Riferimenti.
Quindi, facciamo clic destro sulla cartella Dettagli nel Solution Explorer e aggiungiamo la nuova classe, il cui semplicissimo codice è il seguente (si omette il classico codice interno alle proprietà):

Namespace Dettagli

  Public Class FornitoriRiferimenti

    Dim mCodice As Integer
    Public Property Codice() As Integer
      ' ... omissis ...
    End Property

    Dim mRif As String = String.Empty
    Public Property Rif() As String
      ' ... omissis ...
    End Property

    Dim mTel As String = String.Empty
    Public Property Tel() As String
      ' ... omissis ...
    End Property

    Dim mFax As String = String.Empty
    Public Property Fax() As String
      ' ... omissis ...
    End Property

    Dim mCel As String = String.Empty
    Public Property Cel() As String
      ' ... omissis ...
    End Property

    Dim mMail As String = String.Empty
    Public Property Mail() As String
      ' ... omissis ...
    End Property

  End Class

End Namespace

La form FornitoriRif
Se avete intuito di dover implementare, prima di cominciare lo sviluppo della form Fornitori, la classe di dettaglio, avete certo dedotto che è necessaria anche una form accessoria per la griglia dei riferimenti. Come avete verificato dall'immagine proposta nel 'compito per casa' affidatovi nella puntata precedente, è una form del tutto simile alla FrmClientiRif. Quindi possiamo farne una copia, preoccupandoci di cambiare il nome della classe della form e della classe di dettaglio (FornitoriRif invece di ClientiRif).
Date queste minime differenze, non si ritiene necessario esporre l'immagine e il codice della form.

La form Fornitori
Come per la form Clienti, anche per la form Fornitori Oscar e Diego implementano una interfaccia differente. Nell'articolo precedente è illustrata quella di Oscar, qui potete vedere le due immagini relative a quella di Diego (fare doppio clic per vederle a grandezza naturale):

Dopo aver disegnato l'interfaccia come preferite, dovreste uniformare i nomi dei controlli alle nostre versioni. Quindi, per velocizzare il disegno dell'interfaccia, dato l'alto numero di controlli, vengono forniti i file di design in due distinti file di testo, FrmFornitori.designer.vb.txt e ozFrmFornitori.designer.vb.txt.

Quanto al codice, è praticamente identico a quello della FrmClienti, con i dovuti adeguamenti (per cui non viene illustrato nuovamente: potete fare riferimento alla puntata 19). Sono allegate entrambe le versioni, così potete fare un confronto anche sugli stili di programmazione di Oscar e di Diego.

I Tipi di Pagamento e le Scadenze: concetti
La FrmTipiPag è un'altra form di tipo master-details. Anche per essa bisogna implementare una classe di dettaglio, PagamentiScadenze, e una form accessoria, FrmTipiPagScad, per insermenti e modifiche.
Per comodità, si riporta l'immagine pubblicata nella puntata precedente:

Come potete intuire dalla figura, l'identificativo è la descrizione stessa del tipo di pagamento. Si possono anche indicare: l'eventuale giorno fisso giunto il quale il cliente dovrà effettuare il pagamento, l'indicazione se il pagamento sarà a fine mese e quella se il tipo di pagamento è appoggiato in banca.

Quest'ultimo campo deve essere spuntato nei casi in cui il pagamento deve "passare" per la banca dell'utente, come per esempio nel caso di un bonifico bancario. Così facendo, nelle fatture, si controlla il valore del campo booleano e se è vero si propongono automaticamente i dati bancari dell'utente (presi da una tabella dei parametri di programma).

La parte dettaglio è costituita dalla griglia delle scadenze, che hanno la funzione di indicare a quanti giorni e in che percentuale effettuare le tranche di pagamento. Se per un pagamento in rimessa diretta ha poco senso (si devono infatti impostare i giorni a 0 e la percentuale a 100), ne ha molto più nel caso esemplificato in figura.

Gestendo correttamente le scadenze si potrà costruire uno scadenziario dei pagamenti.

La classe PagamentiScadenze
La classe PagamentiScadenze riflette la struttura della tabella Pagamenti_dettaglio (vedi puntata 18) e va posta nella cartella Dettagli.

Namespace Dettagli

  Public Class PagamentiScadenze

    Dim mPagam As String = String.Empty
    Public Property Pagam() As String
      ' ... omissis ...
    End Property

    Dim mScad As Integer
    Public Property Scadenza() As Integer
      ' ... omissis ...
    End Property

    Dim mPerc As Single
    Public Property Perc() As Single
      ' ... omissis ...
    End Property

  End Class

End Namespace

La form FrmTipiPagScad
Questa è l'immagine in fase di progettazione (con la LblPagam che è invisibile nella figura pubblicata nella puntata precedente).

Del codice, del tutto simile alle altre form accessorie già viste, si riporta solo una parte significativa del metodo di controllo della validità dei dati:

    If Single.Parse(TxtPerc.Text) > 100 Then
      Messaggi.Avviso("La percentuale non può essere superiore a 100 !", "Attenzione")
      Return False
    End If

La form FrmTipiPag
Di questa form, il cui codice è quasi identico alle altre form master-detail già implementate, vale la pena evidenziare il metodo DatiValidi:

  Private Function DatiValidi() As Boolean
    If Convalida(TxtPagam.Text, "Tipo di pagamento", TipoConv.Testo, True) = False Then
      Return False
    End If
    If Convalida(TxtGiornoFisso.Text, "Giorno fisso", _
                 TipoConv.Migliaia_senza_decimali_maggiore_di_zero, False) = False Then
      Return False
    End If

    If DgvScad.RowCount < 1 Then
      Messaggi.Avviso("Per salvare deve essere presente almeno una scadenza di pagamento. " & _
        "Si ricorda che per le rimesse dirette a vista si dovrà inserire una scadenza di " & _
        "pagamento a zero giorni con percentuale di pagamento al 100%.", "Attenzione")
      Return False
    End If

    'Controllo che il totale delle percentuali di pagamento sia uguale a 100
    Dim totperc As Single = 0

    For i As Integer = 0 To DgvScad.RowCount - 1
      totperc += Convert.ToSingle(DgvScad.Rows(i).Cells("Percentuale").Value)
    Next i
    If totperc <> 100 Then
      Messaggi.Avviso( _
        "Per salvare il totale delle percentuali di pagamento deve essere uguale a 100.", _
        "Attenzione")
      Return False
    End If

    Return True
  End Function

Il testo dei messaggi di avviso dovrebbe essere sufficiente a capire il resto del codice della procedura.

Conclusione
In questo articolo sono stati illustrati alcuni piccoli miglioramenti alle classi Formattazione, ConversioneTipo e ConvalidaDati, nonché le form master-detail con relative form accessorie e classi di dettaglio, oggetto dei compiti per casa assegnati nella puntata precedente.
Nella prossima puntata si completeranno i menu relativi alle form illustrate in questo articolo e si proporrà altro codice di perfezionamento.

Il codice di PrimiPassi sviluppato fino a questo momento è come al solito disponibile in area download.
Anche per questa puntata, Diego mette a disposizione nel suo blog un post cui scrivere critiche, suggerimenti, richieste di chiarimento.