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

Premessa
Nell'articolo precedente è stata illustrata la parte della FrmRicerca che interagisce con le altre form, in modo da terminare lo sviluppo della FrmStati.
In questa puntata si completa lo sviluppo della FrmRicerca.

Ultimazione della FrmRicerca
Per finire il disegno della form, aggiungiamo una DataGridView:

Campi privati
Come abbiamo visto nell'articolo precedente, il costruttore riceve una struttura InfoRicerca e invoca due metodi per la configurazione dei controlli nella form, cioè alcune Label e alcune ComboBox, e delle colonne della DataGridView. Per le ComboBox, è opportuno inizializzare un vettore di riferimenti. Ecco quindi i campi privati:

  Private mInfoRicerca As InfoRicerca ' struttura di informazioni ricevuta dalla form
  Private mArrCombo() As ComboBox ' vettore di riferimenti alle combobox dei criteri
  Private mRigaSelezionata As Integer = -1 ' puntatore alla riga selezionata

Oltre al vettore di riferimenti alle Combobox, vediamo la struttura InfoRicerca e un puntatore alla riga selezionata nella griglia, dalla quale bisogna trarre i valori da restituire nella proprietà CampiChiave.

Metodi di configurazione: Label e ComboBox
Il primo metodo chiamato nel costruttore, PreparaCriteri, crea e dispone Label e ComboBox destinate all'impostazione dei criteri di ricerca:

  Private Sub PreparaCriteri()

    If mInfoRicerca.Criteri.Count > 0 Then

      Const xLabel As Integer = 13 ' location.x della prima label
      Const xCombo As Integer = 141 ' location.x della prima combo
      Const ctlWidth As Integer = 121 ' size.width di label e combo
      Const ctlHeight As Integer = 21 ' size.height di label e combo
      Const ctlStep As Integer = 256 ' scostamento da un controllo all'altro dello stesso tipo
      Const yRiga1 As Integer = 61 ' location.y dei controlli della prima riga
      Const yRiga2 As Integer = 103 ' location.y dei controlli della seconda riga

Se esistono criteri, vengono impostate le costanti di disegno dei controlli. Usare le costanti è utile per molte ragioni: è più facile ricordare il nome che il valore ed è più comodo modificare (eventualmente fosse necessario) il valore in un unico posto piuttosto che cercare tutte le ricorrenze di quel valore.

      ' ridimensiono opportunamente il vettore dei riferimenti alle combobox
      Array.Resize(mArrCombo, mInfoRicerca.Criteri.Count)

      ' per ciascun criterio
      For i As Integer = 0 To mInfoRicerca.Criteri.Count - 1

        ' stabilisco la riga in cui saranno posizionati label e combobox
        Dim y As Integer = If(i < 3, yRiga1, yRiga2)

Ridimensionato il vettore dei riferimenti alle ComboBox e impostato il ciclo di scansione del vettore dei criteri passato nella struttura InfoRicerca, si calcola la posizione verticale della coppia di controlli che si sta per creare (ricordiamo che i criteri possibili sono al massimo sei, disposti su due righe di tre).

        ' LABEL
        ' istanzio una label
        Dim aLbl As New Label
        ' ne imposto alcune proprietà
        With aLbl
          .Location = New System.Drawing.Point(xLabel + ctlStep * (i Mod 3), y)
          .Name = "lblCrit" & i.ToString
          .Size = New System.Drawing.Size(ctlWidth, ctlHeight)
          .Font = New Font(aLbl.Font.FontFamily, 10)
          .Text = mInfoRicerca.Criteri(i).Etichetta
          .TextAlign = ContentAlignment.MiddleRight
          .Visible = True
        End With
        ' e la aggiungo all'insieme dei controlli della form
        Me.Controls.Add(aLbl)

        ' COMBOBOX
        ' istanzio una Combobox
        Dim aCmb As New ComboBox
        ' ne imposto alcune proprietà
        With aCmb
          .Location = New System.Drawing.Point(xCombo + ctlStep * (i Mod 3), y)
          .Name = "cmbCrit" & i.ToString
          .AutoCompleteMode = AutoCompleteMode.SuggestAppend
          .AutoCompleteSource = AutoCompleteSource.ListItems
          .Size = New System.Drawing.Size(ctlWidth, ctlHeight)
          .Sorted = True
          .DropDownStyle = ComboBoxStyle.DropDown
          .TabIndex = i + 1
          .Visible = True
        End With
        ' lo aggiungo all'insieme dei controlli della form
        Me.Controls.Add(aCmb)
        ' e ne assegno il riferimento all'elemento iesimo del vettore
        mArrCombo(i) = aCmb

Vengono create e aggiunte la Label e la ComboBox. Quindi si compila l'elenco della ComboBox, in base al tipo di origine dati passato in InfoRicerca.

        Select Case mInfoRicerca.Criteri(i).OrigineDati

          Case TipoOrigineDati.Filtro

            If mInfoRicerca.Criteri(i).Valori.Count > 0 Then
              ' il membro Valori deve avere almeno un elemento
              mArrCombo(i).Items.Add(mInfoRicerca.Criteri(i).Valori(0).ToString)
              ' pre-imposto il testo
              mArrCombo(i).SelectedIndex = 0
              ' disabilito la combobox
              mArrCombo(i).Enabled = False
              ' imposto il 'promemoria' per pre-caricare la griglia
              DgvDati.Tag = TipoOrigineDati.Filtro
            End If

Se il criterio per il quale si stanno creando i controlli deve costituire un filtro, il suo valore è indicato nel primo elemento del membro Valori, deve essere esposto dalla ComboBox, che deve essere disabilitata. Poiché in caso esista un filtro è oppportuno che la griglia appaia precompilata, all'apertura della form, si stabilisce questa necessità valorizzando il Tag della griglia.

          Case TipoOrigineDati.ElencoValori

            If mInfoRicerca.Criteri(i).Valori.Count > 0 Then
              ' il membro Valori deve contenere gli elementi per la lista
              For v As Integer = 0 To mInfoRicerca.Criteri(i).Valori.Count - 1
                mArrCombo(i).Items.Add(mInfoRicerca.Criteri(i).Valori(v).ToString)
              Next
              ' svuoto Text
              mArrCombo(i).SelectedIndex = -1
            End If

Se gli elementi della ComboBox sono tratti da un elenco, questo deve essere presente nel membro Valori. La ComboBox non deve esporre alcun valore, all'apertura (Text vuoto).

          Case TipoOrigineDati.DatiTabella

            PopolaCombo(i)
            ' svuoto Text
            mArrCombo(i).SelectedIndex = -1
        End Select
      Next
    End If

  End Sub

Se gli elementi della ComboBox devono essere letti dal database, si richiama il metodo PopolaCombo, illustrato in seguito. Anche in questo caso la ComboBox non deve esporre alcun valore.

  Private Sub PopolaCombo(ByVal numeroCombo As Integer)

    ' variabili di appoggio
    Dim campoVisualizzato As String = mInfoRicerca.Criteri(numeroCombo).NomeCampoVisualizzato
    Dim campoValore As String = mInfoRicerca.Criteri(numeroCombo).NomeCampoValore
    Dim tabella As String = mInfoRicerca.NomeTabella

    ' istanza e preparazione del Command
    Dim cmd As SqlCommand = New SqlCommand
    cmd.Connection = SqlHelper.ConnessioneDatabase
    If campoValore = campoVisualizzato Then
      cmd.CommandText = "SELECT DISTINCT " & campoValore & _
                        " FROM " & tabella & " ORDER BY " & campoVisualizzato
    Else
      cmd.CommandText = "SELECT DISTINCT " & campoValore & ", " & campoVisualizzato & _
                        " FROM " & tabella & " ORDER BY " & campoVisualizzato
    End If

    ' istanza e uso di DataAdapter, DataSet e DataView
    Dim adp As New SqlDataAdapter(cmd)
    Dim dst As New DataSet
    adp.Fill(dst, "Dati")
    Dim dv As New DataView(dst.Tables(0))

    ' impostazione della combobox
    mArrCombo(numeroCombo).DataSource = dv
    mArrCombo(numeroCombo).DisplayMember = campoVisualizzato
    mArrCombo(numeroCombo).ValueMember = campoValore
  End Sub

Per valorizzare il testo del Command necessario per estrarre i dati per la ComboBox (del cui riferimento si passa l'indice), si controlla se sono necessari uno o due campi (differenza tra campoValore e campoVisualizzato). Quindi vengono istanziati gli oggetti necessari alla popolazione della DataView con la quale viene valorizzata la sorgente della ComboBox, di cui si impostano ValueMember e DisplayMember.

Metodi di configurazione: la DataGridView
Il secondo metodo chiamato nel costruttore, PreparaGriglia, aggiunge le colonne che formano la griglia e ne imposta i formati.

  Private Sub PreparaGriglia()
 
     ' controllo se la form va ridotta in altezza
     If mInfoRicerca.Criteri.Count < 4 Then
       ' sposto più in alto la griglia
       DgvDati.Top -= 42
       ' e riduco l'altezza
       Me.Height -= 42
    End If

Dapprima si imposta la posizione verticale della griglia e, di conseguenza, l'altezza della form, a seconda del numero di righe impegnate da Label e ComboBox. Quindi si scandisce il membro Colonne della struttura InfoRicerca.

    If mInfoRicerca.Colonne.Count > 0 Then

      ' pre-imposto un minimo di larghezza di intestazione
      DgvDati.RowHeadersWidth = 20

      ' per ogni colonna
      For c As Integer = 0 To mInfoRicerca.Colonne.Count - 1

        ' aggiungo una colonna del tipo giusto
        If mInfoRicerca.Colonne(c).CheckBox = False Then
          DgvDati.Columns.Add(New DataGridViewTextBoxColumn)
        Else
          DgvDati.Columns.Add(New DataGridViewCheckBoxColumn)
        End If

        ' ne imposto le proprietà
        DgvDati.Columns(c).Name = mInfoRicerca.Colonne(c).NomeCampo
        DgvDati.Columns(c).HeaderText = mInfoRicerca.Colonne(c).Intestazione
        DgvDati.Columns(c).AutoSizeMode = DataGridViewAutoSizeColumnMode.None
        If mInfoRicerca.Colonne(c).Visibile = True Then
          DgvDati.Columns(c).Width = mInfoRicerca.Colonne(c).Larghezza
        Else
          DgvDati.Columns(c).Width = 0
        End If

Dopo aver aggiunto il tipo richiesto di colonna a seconda del membro CheckBox della struttura Colonna, se ne impostano alcune proprietà, tra cui la larghezza, a seconda del membro Visibile.

        Select Case mInfoRicerca.Colonne(c).TipoCampo

          Case SqlDbType.Char, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, _
               SqlDbType.Text, SqlDbType.VarChar
            'Testi
            DgvDati.Columns(c).DefaultCellStyle.Alignment = _
                                                          DataGridViewContentAlignment.MiddleLeft

          Case SqlDbType.BigInt, SqlDbType.Int, SqlDbType.SmallInt
            'Numeri senza decimali
            DgvDati.Columns(c).DefaultCellStyle.Format = "#,###"
            DgvDati.Columns(c).DefaultCellStyle.Alignment = _
                                                         DataGridViewContentAlignment.MiddleRight

          Case SqlDbType.Decimal, SqlDbType.Float, SqlDbType.Money, SqlDbType.Real, _
               SqlDbType.SmallMoney
            'Numeri con decimali
            DgvDati.Columns(c).DefaultCellStyle.Format = "#,###.00"
            DgvDati.Columns(c).DefaultCellStyle.Alignment = _
                                                         DataGridViewContentAlignment.MiddleRight

          Case SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.SmallDateTime
            'Date
            DgvDati.Columns(c).DefaultCellStyle.Format = "dd/MM/yyyy"
            DgvDati.Columns(c).DefaultCellStyle.Alignment = _
                                                        DataGridViewContentAlignment.MiddleCenter

          Case SqlDbType.Time
            'Ore
            DgvDati.Columns(c).DefaultCellStyle.Format = "HH:mm"
            DgvDati.Columns(c).DefaultCellStyle.Alignment = _
                                                        DataGridViewContentAlignment.MiddleCenter

          Case SqlDbType.Bit
            'Booleani

        End Select

Successivamente si imposta formato e allineamento delle celle di questa colonna, a seconda del tipo del campo esposto.

        DgvDati.Columns(c).ReadOnly = True
      Next

      ' imposto lo stile di default per il valore nullo nelle celle della griglia
      DgvDati.DefaultCellStyle.NullValue = ""

      ' nel caso esista almeno un criterio pre-impostato (filtro), popolo la griglia
      If DgvDati.Tag IsNot Nothing AndAlso _
        DirectCast(DgvDati.Tag, TipoOrigineDati) = TipoOrigineDati.Filtro Then PopolaGriglia()

    End If ' If mInfoRicerca.Colonne.Count
  End Sub

Infine si imposta la condizione di sola lettura, il valore nullo e, se è il caso di pre-popolare la griglia, si invoca il metodo PopolaGriglia:

  Private Sub PopolaGriglia()

    ' istanzio un oggetto Connection e tento di aprirlo
    Dim cnn As New SqlConnection
    cnn = SqlHelper.ConnessioneDatabase

    Try
      cnn.Open()
    Catch ex As Exception
      Messaggi.Errore("Non è stato possibile aprire il database. Impossibile proseguire", _
                      "Errore")
      BtnEsci.PerformClick()
    End Try

    ' istanzio un oggetto Command
    Dim cmd As New SqlCommand

    ' preparo la query di selezione dei dati che verrà costruita
    Dim stringaSQL As String = "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.NomeTabella & " WHERE "

Nel metodo che popola la griglia si crea e si apre la connessione, si crea il Command e si comincia a costruire la query parametrica per la selezione dei dati. La prima parte è semplice: nomi dei campi richiesti e della tabella. Le cose si fanno interessanti al momento di costruire la clausola Where:

    ' flag per interporre " AND " tra un criterio e l'altro nella clausola Where
    Dim piuDiUnParametro As Boolean = False
    ' scandisco i criteri per trattare i parametri
    For c As Integer = 0 To mInfoRicerca.Criteri.Count - 1

      ' prima aggiungo il criterio alla stringa sql
      If mArrCombo(c).Text.Trim <> String.Empty Then
        Dim operatoriConfronto As String() = {" = @", " > @", " >= @", " < @", " <= @"}
        If piuDiUnParametro Then
          stringaSQL &= " AND "
        Else
          piuDiUnParametro = True
        End If
        stringaSQL &= mInfoRicerca.Criteri(c).NomeCampoValore & _
                      operatoriConfronto(mInfoRicerca.Criteri(c).Confronto) & _
                      mInfoRicerca.Criteri(c).NomeCampoValore & " "
      End If

Prima di scandire il membro Criteri della struttura InfoRicerca, si prepara il flag piuDiUnParametro, utile per interporre l'operatore AND tra un criterio e l'altro.
Se la ComboBox corrispondente ha il Text valorizzato, si prepara il vettore degli operatori di confronto. La costruzione della stringa del criterio risulta quindi particolarmente semplificata.

      If mInfoRicerca.Criteri(c).ControlloValidita = True Then

        Dim tipoConvalida As TipoConv

        Select Case mInfoRicerca.Criteri(c).TipoCampoValore

          Case SqlDbType.Char, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, _
               SqlDbType.Text, SqlDbType.VarChar
            'Testi
            tipoConvalida = TipoConv.Testo

          Case SqlDbType.BigInt, SqlDbType.Int, SqlDbType.SmallInt
            'Numeri senza decimali
            tipoConvalida = TipoConv.Migliaia_senza_decimali

          Case SqlDbType.Decimal, SqlDbType.Float, SqlDbType.Money, SqlDbType.Real, _
               SqlDbType.SmallMoney
            'Numeri con decimali
            tipoConvalida = TipoConv.Migliaia_con_decimali

          Case SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, _
               SqlDbType.SmallDateTime
            'Date
            tipoConvalida = TipoConv.Data

        End Select

        If Convalida(mInfoRicerca.Criteri(c).Valori(0), mInfoRicerca.Criteri(c).Etichetta, _
                     tipoConvalida, False) = False Then
          Messaggi.Errore("Criterio " & mInfoRicerca.Criteri(c).Etichetta & " non valido", _
                          "Errore nei criteri di ricerca")
          BtnEsci.PerformClick()
        End If
      End If

Se è richiesto il controllo della validità del valore esposto dalla ComboBox, si valorizza la variabile tipoConvalida a seconda del tipo del campo, e si fa buon uso del metodo Convalida della classe Convalidadati. In caso di mancata convalida, si esce dalla procedura.

      ' aggiungo il parametro al Command
      cmd.Parameters.AddWithValue("@" & mInfoRicerca.Criteri(c).NomeCampoValore, _
                                  mArrCombo(c).Text.Trim)
      cmd.Parameters("@" & mInfoRicerca.Criteri(c).NomeCampoValore).SqlDbType = _
                      mInfoRicerca.Criteri(c).TipoCampoValore
    Next

    ' completo la stringa sql con l'ordinamento
    stringaSQL &= "ORDER BY " & mInfoRicerca.Ordinamento

    ' imposto le proprietà del Command
    cmd.CommandText = stringaSQL
    cmd.Connection = cnn

    ' ne ottengo un DataReader
    Dim dtr As SqlDataReader = cmd.ExecuteReader()

Aggiunto il parametro al Command e terminato il ciclo, si completa la query di selezione con la clausola di ordinamento, la si assegna alla CommandText del Command, che viene connesso e usato per creare il DataReader necessario per la lettura dei dati da esporre nella griglia.

    While dtr.Read()

      ' compilo un vettore di valori per le celle
      Dim arrayValori(mInfoRicerca.Colonne.Count) As Object
      For c As Integer = 0 To mInfoRicerca.Colonne.Count - 1

        ' a seconda del tipo di campo
        Select Case mInfoRicerca.Colonne(c).TipoCampo

          Case SqlDbType.Char, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar, _
               SqlDbType.Text, SqlDbType.VarChar
            'Testi
            arrayValori(c) = dtr.GetString(c)

          Case SqlDbType.BigInt, SqlDbType.Int, SqlDbType.SmallInt
            'Numeri senza decimali
            arrayValori(c) = dtr.GetInt32(c)

          Case SqlDbType.Decimal, SqlDbType.Float, SqlDbType.Money, SqlDbType.Real, _
               SqlDbType.SmallMoney
            'Numeri con decimali
            arrayValori(c) = dtr.GetDecimal(c)

          Case SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.SmallDateTime, _
               SqlDbType.Time
            'Date
            arrayValori(c) = dtr.GetDateTime(c)

          Case SqlDbType.Bit
            'Booleani
            arrayValori(c) = dtr.GetBoolean(c)

        End Select
      Next

      ' preparo la riga nuova e la aggiungo
      Dim rigaNuova As New DataGridViewRow
      rigaNuova.CreateCells(DgvDati, arrayValori)
      DgvDati.Rows.Add(rigaNuova)
    End While

    ' chiudo gli oggetti
    dtr.Close()
    cnn.Close()
  End Sub

Per ogni riga, si prepara un vettore di valori per le celle, che vengono letti uno a uno con il metodo adatto, a seconda del tipo di campo. Con questo vettore si valorizza una nuova DataGridViewRow che viene aggiunta alla griglia. Terminata la compilazione di quest'ultima, si chiudono DataReader e Connection e si esce dalla procedura.

Altri metodi di servizio usati da pulsanti
L'utente imposta i criteri tramite le ComboBox e applica il filtro tramite il pulsante BtnFiltra, che vedremo in seguito.
Se non è soddisfatto del risultato può svuotare le ComboBox per reimpostare i criteri.

  ''' <summary>
  ''' svuota le Text delle combobox
  ''' </summary>
  Private Sub SvuotaCombo()
    For Each cmb As ComboBox In Me.Controls.OfType(Of ComboBox)()
      ' solo di quelle non 'filtro', che sono disabilitate
      If cmb.Enabled = True Then
        cmb.SelectedIndex = -1
      End If
    Next
  End Sub

  ''' <summary>
  ''' Svuota la griglia e resetta il puntatore alla riga selezionata
  ''' </summary>
  Private Sub SvuotaGriglia()
    If Not (DgvDati.Rows.Count < 1) Then
      DgvDati.Rows.Clear()
      DgvDati.Refresh()
      mRigaSelezionata = -1
    End If
  End Sub

I commenti dovrebbero essere sufficienti a comprendere il codice.
Questi metodi sono usati dal metodo per il pulsante Svuota:

  Private Sub BtnSvuota_Click(ByVal sender As System.Object, _
                              ByVal e As System.EventArgs) Handles BtnSvuota.Click
    SvuotaCombo()
    SvuotaGriglia()
  End Sub

Il metodo SvuotaGriglia viene usato prima del metodo PopolaGriglia, nel metodo per il pulsante Filtra:

  Private Sub BtnConferma_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BtnConferma.Click
    If DgvDati.Rows.Count < 1 Then
      Exit Sub
    End If

    If mRigaSelezionata < 0 Then
      Messaggi.Avviso("Non è stata selezionata alcuna riga", "Errore")
    Else
      Dim riga As DataGridViewRow = DgvDati.Rows(mRigaSelezionata)
      For c As Integer = 0 To DgvDati.Columns.Count - 1
        If mInfoRicerca.Colonne(c).Chiave = True Then
          mCampiChiave.Add(riga.Cells(c).Value.ToString)
        End If
      Next
      Me.DialogResult = Windows.Forms.DialogResult.OK
    End If
  End Sub

Metodi per la compilazione dei risultati
L'utente sceglie una riga della griglia:

  Private Sub DgvDati_RowEnter(ByVal sender As System.Object, _
                               ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) _
                               Handles DgvDati.RowEnter
    mRigaSelezionata = e.RowIndex
  End Sub

Viene così valorizzato il puntatore alla riga selezionata. A questo punto, l'utente conferma la scelta:

  Private Sub BtnConferma_Click(ByVal sender As System.Object, _
                                ByVal e As System.EventArgs) Handles BtnConferma.Click
    If DgvDati.Rows.Count < 1 Then
      Exit Sub
    End If

    If mRigaSelezionata < 0 Then
      Messaggi.Avviso("Non è stata selezionata alcuna riga", "Errore")
    Else
      Dim riga As DataGridViewRow = DgvDati.Rows(mRigaSelezionata)
      For c As Integer = 0 To DgvDati.Columns.Count - 1
        If mInfoRicerca.Colonne(c).Chiave = True Then
          mCampiChiave.Add(riga.Cells(c).Value.ToString)
        End If
      Next
      Me.DialogResult = Windows.Forms.DialogResult.OK
    End If
  End Sub

Se vengono superati i controlli di errore, viene compilato il vettore mCampiChiave, che verrà restituito alla form chiamante attraverso la proprietà CampiChiave, come abbiamo già visto nell'articolo precedente.
Si può infine concludere impostando a OK il risultato del dialogo, il che chiude la form.

L'utente può anche rinunciare:

  Private Sub BtnEsci_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) Handles BtnEsci.Click
    Me.DialogResult = Windows.Forms.DialogResult.Cancel
  End Sub

Viene semplicemente impostato a Cancel il risultato del dialogo.

Problema dei pulsanti di default
Una finestra di dialogo ha, di solito, un pulsante OK e un pulsante Cancel (annulla); infatti espone le proprietà AcceptButton e CancelButton, per ricevere i riferimenti ai Button Ok e Cancel, che vengono 'premuti' se l'utente preme i tasti Invio o Escape, rispettivamente.

Però le due proprietà ricevono oggetti Button, non ToolStripButton, come i pulsanti Esci e Conferma. Si è resa quindi necessaria una emulazione del comportamento richiesto attraverso la gestione dei tasti.

Come forse vi ricordate dall'articolo precedente, è stata impostata a True la proprietà KeyPreview della FrmRicerca. E' stato fatto proprio a questo scopo:

  Private Sub FrmRicerca_KeyDown(ByVal sender As Object, _
                                 ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
    If e.KeyCode = Keys.Escape Then
      BtnEsci.PerformClick()
    ElseIf e.KeyCode = Keys.Enter Then
      BtnConferma.PerformClick()
    End If
  End Sub

Come vedete, se il tasto premuto è Invio o Escape, si 'preme' il relativo pulsante (metodo PerformClick), scatenando l'evento che porta alla chiusura della form con l'appropriato DialogResult.

Conclusione
In questa puntata si è completato lo sviluppo della FrmRicerca, una form di dialogo che verrà utilizzata da molte altre form di cui è composto questo progetto, delle quali tratteremo nelle prossime puntate.

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.