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

Premessa
Nella puntata precedente era stata completata l'illustrazione della FrmRicerca, la form di dialogo utilizzata da molte altre form del progetto. Il codice della FrmRicerca era stato profondamente modificato in seguito alle critiche espresse nella puntata numero 12. Di conseguenza Oscar ha dovuto adeguare tutte le altre form del progetto.

E' così emerso un difetto della progettazione di FrmRicerca, rimediando al quale si è aggiunta una qualità.
E' stato anche scoperto un errore di Diego (uno statement If fuori posto).

In questo articolo si descrive il difetto, il rimedio, la caratteristica aggiunta e la correzione dell'errore.

Il difetto
Per una delle configurazioni della FrmRicerca, la sorgente dati della griglia non era una sola tabella, ma un set di record risultante da una query di più tabelle correlate. In altre parole, la proprietà InfoRicerca.NomeTabella non poteva essere solo il nome di una tabella, ma anche una espressione query più complessa.

Quindi, tanto per cominciare, quella proprietà doveva cambiare nome. Adesso si chiama SorgenteDati. Ecco la nuova struttura InfoRicerca:

Public Structure InfoRicerca
  ''' <summary>
  ''' Titolo per la FrmRicerca
  ''' </summary>
  Dim TitoloForm As String
  ''' <summary>
  ''' Nome della tabella di riferimento o
  ''' espressione query di selezione tra più tabelle
  ''' </summary>
  Dim SorgenteDati As String
  ''' <summary>
  ''' Ordinamento dei dati visualizzati nella griglia
  ''' </summary>
  Dim Ordinamento As String
  ''' <summary>
  ''' Insieme delle strutture di informazioni per i criteri di filtro
  ''' </summary>
  ''' <remarks>vedi struttura Criterio</remarks>
  Dim Criteri As List(Of Criterio)
  ''' <summary>
  ''' Insieme delle strutture di informazioni per le colonne della griglia
  ''' </summary>
  ''' <remarks>vedi struttura Colonna</remarks>
  Dim Colonne As List(Of Colonna)
End Structure

Questa modifica comporta anche un cambiamento nella struttura Criterio, per la quale diventava necessario un ulteriore membro, NomeTabella. Se, infatti, InfoRicerca.SorgenteDati è una tabella, allora per ciascun Criterio varrà la stessa tabella; ma se abbiamo a che fare con una query, occorre indicare a quale tabella fa riferimento un Criterio. Ecco quindi la nuova struttura Criterio:

Public Structure Criterio
  ''' <summary>
  ''' Testo della Label descrittiva del criterio
  ''' </summary>
  ''' <remarks></remarks>
  Dim Etichetta As String
  ''' <summary>
  ''' Tipo di origine dei dati per l'elenco della combobox
  ''' </summary>
  ''' <remarks>vedi enumerato TipoOrigineDati. 
  ''' Se Filtro, Valori deve avere un elemento; 
  ''' se ElencoValori, Valori deve avere questo elenco</remarks>
  Dim OrigineDati As TipoOrigineDati
  ''' <summary>
  ''' Nome del campo i cui valori vengono visualizzati
  ''' </summary>
  Dim NomeCampoVisualizzato As String
  ''' <summary>
  ''' Nome del campo i cui valori vengono utilizzati
  ''' </summary>
  Dim NomeCampoValore As String
  ''' <summary>
  ''' Tipo Sql del campo
  ''' </summary>
  Dim TipoCampoValore As SqlDbType
  ''' <summary>
  ''' Indica se deve essere validata la scelta fatta tramite la ComboBox
  ''' </summary>
  Dim ControlloValidita As Boolean
  ''' <summary>
  ''' Tipo di confronto per l'operazione di filtro
  ''' </summary>
  ''' <remarks>vedi enumerato TipoConfronto</remarks>
  Dim Confronto As TipoConfronto
  ''' <summary>
  ''' Nome della tabella di riferimento
  ''' </summary>
  ''' <remarks>Opzionale. Se InfoRicerca.SorgenteDati è una query, è obbligatorio</remarks>
  Dim NomeTabella As String
  ''' <summary>
  ''' Elementi dell'elenco della Combobox, se OrigineDati è diverso da DatiTabella
  ''' </summary>
  ''' <remarks>Opzionale. Se OrigineDati è Filtro, Valori deve avere un elemento; 
  ''' se OrigineDati è ElencoValori, Valori deve avere questo elenco</remarks>
  Dim Valori As String()

  Sub New(ByVal etichetta As String, ByVal origineDati As TipoOrigineDati, _
          ByVal nomeCampoVisualizzato As String, ByVal nomeCampoValore As String, _
          ByVal tipoCampoValore As SqlDbType, ByVal controlloValidita As Boolean, _
          ByVal confronto As TipoConfronto, Optional ByVal nomeTabella As String = Nothing, _
          Optional ByVal valori As String() = Nothing)

    Me.Etichetta = etichetta
    Me.OrigineDati = origineDati
    Me.NomeCampoVisualizzato = nomeCampoVisualizzato
    Me.NomeCampoValore = nomeCampoValore
    Me.TipoCampoValore = tipoCampoValore
    Me.ControlloValidita = controlloValidita
    Me.Confronto = confronto
    Me.NomeTabella = nomeTabella
    Me.Valori = valori
  End Sub
End Structure

Naturalmente, avendo aggiunto un membro, si deve adeguatamente modificare il costruttore.

La qualità
Nell'apportare le modifiche sopra descritte, a Oscar è venuta la buonissima idea di arricchire l'enumerato TipoConfronto aggiungendo un membro IniziaPer, corrispondente alla clausola "LIKE criterio%". Così è stato naturale, per Diego, aggiungere un membro Contiene:

Public Enum TipoConfronto
  Uguale
  Maggiore
  MaggioreUguale
  Minore
  MinoreUguale
  IniziaPer
  Contiene
End Enum

Conseguenze
La prima cosa di cui preoccuparsi, quando la FrmRicerca riceve la struttura InfoRicerca in base alla quale configurarsi, è stabilire se SorgenteDati è il nome di una tabella o una query di selezione.
Si aggiunge quindi un campo:

  Private mIsQuery As Boolean = False ' InfoRicerca.SorgenteDati è una query di selezione

Questo campo va adeguatamente valorizzato nel costruttore:

    If mInfoRicerca.SorgenteDati.StartsWith("SELECT") Then mIsQuery = True

Nel metodo PopolaCombo la variabile tabella viene valorizzata a seconda del valore di mIsQuery:

    Dim tabella As String = If(mIsQuery, _
                               mInfoRicerca.Criteri(numeroCombo).NomeTabella, _
                               mInfoRicerca.SorgenteDati)

Nel metodo PreparaGriglia viene trattato in modo più congruo la visibilità di una colonna:

        If mInfoRicerca.Colonne(c).Visibile = True Then
          DgvDati.Columns(c).Width = mInfoRicerca.Colonne(c).Larghezza
        Else
          DgvDati.Columns(c).Visible = False
        End If

Nel metodo PopolaGriglia la variabile stringaSQL viene anch'essa valorizzata a seconda del valore di mIsQuery:

    Dim stringaSQL As String = String.Empty

    If mIsQuery = False Then ' se la sorgente dati è una tabella
      ' preparo la query di selezione dei dati che verrà costruita
      stringaSQL = "SELECT "
      ' aggiungo l'elenco dei campi da esporre nella griglia
      For c As Integer = 0 To mInfoRicerca.Colonne.Count - 1
        stringaSQL &= mInfoRicerca.Colonne(c).NomeCampo & ", "
      Next
      ' aggiungo la clausola From e l'inizio della clausola Where
      stringaSQL &= stringaSQL.Substring(0, (stringaSQL.Length - 2)) & _
                  " FROM " & mInfoRicerca.SorgenteDati
    Else
      ' se la sorgente dati è una query di selezione
      stringaSQL = mInfoRicerca.SorgenteDati
    End If
    ' aggiungo la clausola WHERE
    stringaSQL &= " WHERE "

La variabile operatoriConfronto contiene due nuovi elementi, per i corrispondenti membri IniziaPer e Contiene dell'enumerato TipoConfronto:

        Dim operatoriConfronto As String() = {" = @", " > @", " >= @", " < @", " <= @", _
                                              " LIKE @", " LIKE @"}

Sono ovviamente uguali, però cambia il modo di applicarli, quando è il momento di aggiungere il relativo parametro:

      'Aggiungo il parametro al Command
      Select Case mInfoRicerca.Criteri(c).Confronto
        Case TipoConfronto.IniziaPer
          cmd.Parameters.AddWithValue("@" & mInfoRicerca.Criteri(c).NomeCampoValore, _
                                      mArrCombo(c).Text.Trim & "%")
        Case TipoConfronto.Contiene
          cmd.Parameters.AddWithValue("@" & mInfoRicerca.Criteri(c).NomeCampoValore, _
                                      "%" & mArrCombo(c).Text.Trim & "%")
        Case Else
          cmd.Parameters.AddWithValue("@" & mInfoRicerca.Criteri(c).NomeCampoValore, _
                                      mArrCombo(c).Text.Trim)
      End Select
      ' imposto il tipo del parametro
      cmd.Parameters("@" & mInfoRicerca.Criteri(c).NomeCampoValore).SqlDbType = _
                      mInfoRicerca.Criteri(c).TipoCampoValore

Lo statement End If corrispondente alla If mArrCombo(c).Text.Trim <> String.Empty deve essere spostato subito prima dello statement Next corrispondente al For c As Integer = 0 To mInfoRicerca.Criteri.Count - 1.
E' questo l'errore di Diego, che aveva lasciato questo End If prima del controllo di validità, dimenticandosi l'appunto appositamente segnatosi per la 'cosa da fare' e poi non fatta.
Dopo aver posto riparo a questo errore, sono stati aggiunti commenti agli statement End If e Next troppo lontani dal rispettivo statement If o For.

Nel metodo BtnCerca_Click della FrmStati, ovviamente, cambia il nome del membro della struttura InfoRicerca:

    ir.SorgenteDati = "Stati"

Ma anche l'aggiunta del criterio:

    ir.Criteri.Add(New Criterio("Stato", TipoOrigineDati.DatiTabella, "Stato", "Stato", _
                                SqlDbType.VarChar, False, TipoConfronto.IniziaPer))

Come vedete, si sfrutta proprio uno dei tipi di confronto appena aggiunti.

Le preferenze estetiche di Oscar e Diego
Secondo Diego, la dimensione delle form non dovrebbe superare il formato 800x600. Secondo Oscar, bisogna stare il più comodi possibile, infatti la sua FrmRicerca è grande circa 980x540. Di conseguenza, il metodo PreparaCriteri contiene, adesso, un certo numero di costanti in più, per permettere a entrambi di avere ciascuno la propria configurazione preferita.
Queste impostazioni potrebbero anche essere rese persistenti in un file di impostazioni, conseguente a un menu appositamente predisposto per gestire le impostazioni dell'utente. Si vedrà più avanti, se aggiungerlo.
Intanto, qui trovate le impostazioni delle costanti di Oscar, mentre quelle di Diego sono nel metodo PreparaCriteri presente nel codice allegato all'articolo, che si ritiene superfluo riportare qui (ovviamente, cambiano leggermente le righe in cui vengono usate queste costanti).

      Const xLabel As Integer = 9 ' location.x della prima label
      Const xCombo As Integer = 12 ' location.x della prima combo
      Const ctlWidth As Integer = 310 ' size.width di label e combo
      Const ctlHeight As Integer = 21 ' size.height di label e combo
      Const ctlStep As Integer = 316 ' scostamento da un controllo all'altro dello stesso tipo
      Const yRiga1Label As Integer = 48 'Location.y delle label della prima riga
      Const yRiga1Combo As Integer = 67 'Location.y delle combo della prima riga
      Const yRiga2Label As Integer = 93 'Location.y delle label della seconda riga
      Const yRiga2Combo As Integer = 112 'Location.y delle combo della seconda riga
      Const labelAlignment As ContentAlignment = ContentAlignment.MiddleLeft ' allineamento label

In questo modo, potete anche voi impostare le misure che preferite.

Conclusione
In questa puntata è stato illustrato come si è posto rimedio a una mancanza nello sviluppo della struttura InfoRicerca, fornendola di una ulteriore caratteristica che rende la FrmRicerca più versatile. Si è anche riparato un errore sorto in fase di collaudo.
Nelle prossime puntate verranno illustrate altre form (come dicono nei trailers: 'Non mancate!' :o))).

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.