Una calcolatrice a nastro in Visual Basic 2005 - seconda parte
a cura di Mario De Ghetto (requisiti: VB2005)

Il codice del form Calcolatrice.vb
Dopo aver esaminato attentamente la classe clsNastro è possibile fare la stessa operazione con il codice del form.
La prima parte, come al solito, contiene l'intestazione della classe form e le dichiarazioni delle variabili. Le variabili hanno le seguenti finalità:

  Public Class Calcolatrice
      Private flagPuntoDecimale As Boolean = False
      Private cNastro As clsNastro
      Private Valore As Double = 0
      ' dichiara un oggetto PrintDocument di nome documento (per anteprima)
      Private WithEvents documento As New System.Drawing.Printing.PrintDocument

A seguire, viene gestito l'evento Load del form Calcolatrice che viene scatenato al caricamento del form stesso. Nel gestore di questo evento vengono effettuate due operazioni: la creazione dell'istanza di classe clsNastro, passandogli il riferimento alla ListBox, e la chiamata al metodo InizializzaPrintPreviewDialog che sarà analizzato in seguito.

    Private Sub Calcolatrice_Load(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles MyBase.Load
        cNastro = New clsNastro(Me.Nastro)
        InizializzaPrintPreviewDialog()
    End Sub

A questo punto viene riportato il codice di gestione dell'evento Click dei dieci pulsanti numerici (da 0 a 9) che semplicemente chiamano il metodo premiTastoNumerico, passando un carattere che indica il tasto premuto.

    Private Sub Tasto1_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto1.Click
        premiTastoNumerico("1")
    End Sub

    Private Sub Tasto2_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto2.Click
        premiTastoNumerico("2")
    End Sub

    Private Sub Tasto3_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto3.Click
        premiTastoNumerico("3")
    End Sub

    Private Sub Tasto4_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto4.Click
        premiTastoNumerico("4")
    End Sub

    Private Sub Tasto5_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto5.Click
        premiTastoNumerico("5")
    End Sub

    Private Sub Tasto6_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto6.Click
        premiTastoNumerico("6")
    End Sub

    Private Sub Tasto7_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto7.Click
        premiTastoNumerico("7")
    End Sub

    Private Sub Tasto8_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto8.Click
        premiTastoNumerico("8")
    End Sub

    Private Sub Tasto9_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto9.Click
        premiTastoNumerico("9")
    End Sub

    Private Sub Tasto0_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto0.Click
        premiTastoNumerico("0")
    End Sub

Ecco quindi il codice del metodo premiTastoNumerico. Questo metodo accoda la corrispondente cifra, ricevuta come parametro, sposta nuovamente il focus sul display e posiziona il cursore nel punto più a destra dell'ultima cifra inserita:

    Private Sub premiTastoNumerico(ByVal tastoNumerico As String)
        Display.Text = Display.Text & tastoNumerico
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Un frammento di codice interessante è quello riportato nel seguito. Il gestore dell'evento KeyPress del display permette di verificare, mentre il focus è impostato sul display, se il tasto premuto sia uno di quelli relativi a cifre numeriche. Nel caso la verifica fosse positiva, sarà accodata la cifra al valore già presente nel display.
Questo metodo realizza una funzione estremamente utile: infatti permette di inserire velocemente dei numeri utilizzando il tastierino della tastiera fisica, piuttosto che obbligare l'utente a fare clic sui pulsanti numerici del tastierino presente sul form.
Si noti che il metodo chiamato nel caso di tasti numerici è lo stesso presente nei gestori degli eventi click dei corrispondenti pulsanti da zero a nove.
In questo stesso gestore dell'evento KeyPress sono state codificate le azioni da eseguire nel caso di altri tasti premuti: i tasti delle operazioni (+, -, *, /), il punto decimale, il tasto di eguaglianza (=) e perfino il tasto Invio che viene parificato all'inserimento di un segno di eguaglianza. Questo significa che per stampare il totale dell'operazione è possibile premere il tasto "=" così come è sufficiente premere Invio.
Infine, l'istruzione Case Else permette di escludere qualsiasi altro tasto premuto, per evitare che nel display venga fatto il tentativo di inserire caratteri non ammessi, come caratteri alfabetici, simboli non corretti e così via. Infatti l'istruzione e.KeyChar = "" non fa altro che "buttare via" il carattere che è stato premuto, per evitare che il gestore della pressione dei tasti di default, che viene comunque chiamato subito dopo il termine di questo codice, tenti di interpretare un tasto non ammesso.

    Private Sub Display_KeyPress(ByVal sender As Object, _
                ByVal e As System.Windows.Forms.KeyPressEventArgs) _
                Handles Display.KeyPress
        Dim tasto As New Control
        Select Case e.KeyChar
            Case "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"
                premiTastoNumerico(e.KeyChar)
            Case "."
                Call Me.PuntoDecimale_Click(sender, e)
            Case "+"
                Call Me.TastoPiu_Click(sender, e)
            Case "-"
                Call Me.TastoMeno_Click(sender, e)
            Case "*"
                Call Me.TastoPer_Click(sender, e)
            Case "/"
                Call Me.TastoDiviso_Click(sender, e)
            Case "="
                Call Me.TastoUguale_Click(sender, e)
            Case Environment.NewLine
                Call Me.TastoUguale_Click(sender, e)
            Case Else
                e.KeyChar = ""
        End Select
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Nel tastierino numerico sono presenti altri due elementi che si riferiscono a elementi numerici che devono essere aggiunti al numero digitato e pertanto anche questo codice viene riportato qui di seguito. Il primo accoda tre zeri (per indicare le migliaia, per esempio):

    Private Sub Tasto00_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles Tasto00.Click
        Display.Text = Display.Text & "000"
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Il secondo metodo accoda il simbolo del punto decimale. Si noti che il punto decimale viene accodato solo se l'indicatore flagPuntoDecimale non è già attivato (True). Nel caso tale indicatore fosse ancora impostato a False, viene aggiunto il punto decimale e impostato a True, per impedire l'inserimento di più punti decimali nello stesso valore.

    Private Sub PuntoDecimale_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles PuntoDecimale.Click
        If flagPuntoDecimale = False Then
            Display.Text = Display.Text & ","
            flagPuntoDecimale = True
        End If
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Altri due gestori di eventi Click permettono, rispettivamente, di eseguire le operazioni relative alla pressione dei pulsanti C e CE:

    Private Sub TastoC_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoC.Click
        Display.Text = ""
        flagPuntoDecimale = False
        Me.Display.Focus()
    End Sub

    Private Sub TastoCE_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoCE.Click
        Call cNastro.AzzeraNastro()
        Call cNastro.SvuotaNastro()
        Call cNastro.AggiornaNastro()
        Display.Text = ""
        flagPuntoDecimale = False
        Valore = 0
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Il gestore dell'evento Click del pulsante TastoCancellaUno (il pulsante presente sulla destra del display) verifica se il carattere meno significativo della stringa presente nel display è un punto decimale. In caso positivo, viene reimpostato a False il corrispondente indicatore. In ogni caso viene eliminato il carattere più a destra e aggiornato il display.

    Private Sub TastoCancellaUno_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoCancellaUno.Click
        If Display.Text.Substring(Display.Text.Length - 1, 1) = "," Then
            flagPuntoDecimale = False
        End If
        Display.Text = Display.Text.Substring(0, Display.Text.Length - 1)
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Nel caso vengano premuti i pulsanti relativi alle operazioni matematiche (+, -, *, /), viene effettuata una chiamata al metodo eseguiOperazione, al quale viene passato il simbolo come parametro:

    Private Sub TastoPiu_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoPiu.Click
        Call eseguiOperazione("+")
    End Sub

    Private Sub TastoMeno_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoMeno.Click
        Call eseguiOperazione("-")
    End Sub

    Private Sub TastoPer_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoPer.Click
        Call eseguiOperazione("*")
    End Sub

    Private Sub TastoDiviso_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoDiviso.Click
        Call eseguiOperazione("/")
    End Sub

Viene quindi riportato il codice del metodo eseguiOperazione, dove è possibile notare che nella prima parte vengono eseguite le operazioni matematiche specifiche del simbolo ricevuto come parametro e viene inserito nel nastro di carta il valore e il simbolo corrispondente, mentre nella seconda parte viene svuotato il display, reimpostato a False l'indicatore del punto decimale e spostato il focus sul display:

    Private Sub eseguiOperazione(ByVal operatore As String)
        Select Case operatore
            Case "+"
                Valore += CType(Display.Text, Double)
                Call cNastro.Inserisci(Display.Text, "+")

            Case "-"
                Valore -= CType(Display.Text, Double)
                Call cNastro.Inserisci(Display.Text, "-")

            Case "*"
                Valore *= CType(Display.Text, Double)
                Call cNastro.Inserisci(Display.Text, "*")

            Case "/"
                Valore /= CType(Display.Text, Double)
                Call cNastro.Inserisci(Display.Text, "/")

        End Select
        Display.Text = ""
        flagPuntoDecimale = False
        Me.Display.Focus()
    End Sub

L'ultimo gestore di evento Click che interessa il tastierino numerico del form è quello relativo al tasto di eguaglianza (TastoUguale). Il codice che segue:

    Private Sub TastoUguale_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoUguale.Click
        Call cNastro.Inserisci(Valore.ToString, "=")
        Display.Text = Valore
        Valore = 0
        flagPuntoDecimale = False
        Call cNastro.SvuotaNastro()
        Call cNastro.AggiornaNastro()
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Le funzionalità di salvataggio su file e di recupero del contenuto del nastro di carta da un file di testo sono piuttosto interessanti, perché permettono di memorizzare permanentemente anche lunghe sequenze di operazioni per poterle verificare in seguito o magari per inviarle via posta elettronica a un collega.
Il salvataggio su file di testo è realizzato con il gestore dell'evento Click del pulsante TastoSalva, riportato qui di seguito. Una volta impostate le proprietà del controllo SaveFileDialog1, viene mostrata la finestra di dialogo per ottenere il nome del file su cui salvare, dopo di che viene semplicemente chiamato il metodo SalvaNastro della classe clsNastro:

    Private Sub TastoSalva_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoSalva.Click
        Dim nomeFile As String = ""
        SaveFileDialog1.InitialDirectory = "c:\"
        SaveFileDialog1.FileName = "C:\prova.txt"
        SaveFileDialog1.Filter = "file di testo (*.txt)|*.txt"
        SaveFileDialog1.FilterIndex = 1
        SaveFileDialog1.RestoreDirectory = True
        If SaveFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
            nomeFile = Me.SaveFileDialog1.FileName
            cNastro.SalvaNastro(nomeFile)
        End If
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Allo stesso modo il gestore dell'evento Click del pulsante TastoRecupera, imposta le proprietà del controllo OpenFileDialog1, apre la relativa finestra di dialogo per la scelta del file da ricaricare e chiama il metodo RecuperaNastro della classe clsNastro:

    Private Sub TastoRecupera_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TastoRecupera.Click
        OpenFileDialog1.InitialDirectory = "c:\"
        OpenFileDialog1.FileName = "C:\prova.txt"
        OpenFileDialog1.Filter = "file di testo (*.txt)|*.txt"
        OpenFileDialog1.FilterIndex = 1
        OpenFileDialog1.RestoreDirectory = True

        If OpenFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
            Dim nomeFile As String = Me.OpenFileDialog1.FileName
            cNastro.RecuperaNastro(nomeFile)
        End If
        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

Per le funzionalità di anteprima di stampa è stato utilizzato un codice molto simile a quello mostrato nell'esempio del capitolo 9, nel paragrafo che ha trattato il controllo PrintPreviewDialog.
Il metodo InizializzaPrintPreviewDialog e i gestori degli eventi Click del pulsante TastoAnteprima e PrintPage dell'istanza documento, sono quindi stati adattati con poche modifiche.

    ' inizializza il controllo
    Private Sub InizializzaPrintPreviewDialog()

        ' imposta dimensione, posizione e nome
        Me.PrintPreviewDialog1.ClientSize = _
                      New System.Drawing.Size(400, 500)
        Me.PrintPreviewDialog1.Location = New System.Drawing.Point(29, 29)
        Me.PrintPreviewDialog1.Name = "PrintPreviewDialog1"

        ' imposta la dimensione minima della finestra di dialogo
        Me.PrintPreviewDialog1.MinimumSize = _
                      New System.Drawing.Size(375, 250)

        ' imposta la proprietà UseAntiAlias a True per permettere
        ' al sistema operativo di utilizzare l'effetto antialiasing
        Me.PrintPreviewDialog1.UseAntiAlias = True
    End Sub

    Private Sub TastoAnteprima_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles TastoAnteprima.Click

        Me.TestoDaStampare.Text = cNastro.componiNastro

        ' imposta la proprietà PrintPreviewDialog.Document all'oggetto
        ' PrintDocument selezionato dall'utente
        PrintPreviewDialog1.Document = documento

        ' chiama il metodo ShowDialog che scatenerà l'evento
        ' PrintPage del documento
        PrintPreviewDialog1.ShowDialog()

        Me.Display.Focus()
        Me.Display.SelectionStart = Me.Display.Text.Length
    End Sub

La modifica più consistente è quella presente appunto nel gestore dell'evento PrintPage dell'istanza documento. Infatti il testo da stampare non è, in questo caso, una stringa fissa e preimpostata, ma è il risultato ottenuto dal metodo Function formattaStampa, al quale è stato passato il contenuto della Label nascosta presente sul form:

    Private Sub documento_PrintPage(ByVal sender As Object, _
        ByVal e As System.Drawing.Printing.PrintPageEventArgs) _
            Handles documento.PrintPage

        ' Qui va inserito il codice per disegnare la pagina.
        ' Questo codice sarà chiamato da PrintPreviewDialog.Show

        ' Il seguente codice prepara un semplice messaggio
        ' sul documento creato nella finestra di dialogo
        Dim text As String = formattaStampa(Me.TestoDaStampare.Text)
        Dim printFont As New System.Drawing.Font _
            ("Courier New", 14, System.Drawing.FontStyle.Regular)
        e.Graphics.DrawString(text, printFont, _
            System.Drawing.Brushes.Black, 500, 100)
    End Sub

Infine viene riportato proprio il metodo Function formattaStampa che si accolla l'onere di:

      Private Function formattaStampa(ByVal testoNastro As String) As String
          Dim expreg As New Regex(Environment.NewLine)
          Dim arrayStampa() As String = _
                            Regex.Split(testoNastro, Environment.NewLine)
          Dim riga As String = ""
          Dim spazi As String = "                      "   ' 22 spazi
          Dim i As Integer

          For i = 0 To arrayStampa.GetUpperBound(0)
              riga = arrayStampa(i)
              riga = spazi & riga
              riga = riga.Substring(riga.Length - 22, 22)
              arrayStampa(i) = riga
          Next

          testoNastro = arrayStampa(0)
          For i = 1 To arrayStampa.GetUpperBound(0)
              testoNastro &= Environment.NewLine & arrayStampa(i)
          Next
          Return testoNastro
      End Function
  End Class

La calcolatrice funzionante è quella mostrata in Figura 12.2.


Figura 12.2 La calcolatrice a nastro funzionante.

In Figura 12.3, invece, è presentata la finestra di anteprima di stampa, con zoom pari al 100%. In tale finestra è possibile modificare il fattore di zoom, scorrere le pagine e impostare la visualizzazione su più pagine (per documenti multipagina) e stampare sulla stampante di sistema predefinita.


Figura 12.3 La finestra di anteprima di stampa.

NOTA DELL'AUTORE
Il presente capitolo è un capitolo inedito, in quanto escluso dalla pubblicazione per motivi di spazio. Avrebbe dovuto essere pubblicato come ultimo capitolo del libro i cui riferimenti sono riportati nel seguito della presente pagina.

Su questo sito (in area Download) e su quello dell'autore (www.deghetto.it) saranno pubblicati anche tutti gli esempi del libro, compreso l'esempio di questo capitolo inedito. Gli esempi sono forniti in sorgente e in eseguibile. Per utilizzare gli esempi è necessario avere almeno una copia di Visual Basic 2005 Express.

Chi dovesse trovare errori o imprecisioni di qualunque genere è invitato gentilmente a comunicarlo a mezzo e-mail all'autore, sul forum "dotnet" di questo sito oppure sul blog dell'autore.

  "VISUAL BASIC 2005 e il Framework .NET 2.0"
  Autore: Mario De Ghetto
  Numero 15 - Bimestrale, maggio 2006 - Distribuzione Parrini
  Collana "Guide Pratiche" di Computer Magazine
  Codice 9771825062009

  Editore:
  Future Media Italy
  Via Asiago, 45 - 20128 Milano
  Tel. 02-252916.1 - fax 02-26005121

  Arretrati:
  tel. 02-252916209 - fax 02-26005512
  dal lunedì al venerdì: 9.00/12.30 - 13.30/17.30
  e-mail arretrati@futureitaly.it

  Distribuzione per l'edicola:
  Parrini & C. SpA
  Viale Forlanini 23 - 20134 Milano
  Via Vitorchiano 81 - 00189 Roma