Traduzione a cura di Fabrizio Angius [qtsolutions -A-T- gmx.net]
English TOC.

7. La rubrica

Questa sara' la nostra prima applicazione reale: una rubrica. Progetteremo ed implementeremo le classi di base e l'interfaccia utente in questo capitolo.

Iniziamo aprendo un progetto C++ vuoto nel Designer di Qt. Ho deciso di chiamare il mio progetto adbook.pro, ovviamente potete dargli il nome che preferite.

La prima cosa che vogliamo fare e' aggiungere una finestra principale (Main Window). La finestra che useremo dovra' essere vuota, per cui assicuratevi di deselezionare tutte le opzioni nel wizard mostrato nella figura 7-1.

Il wizard per una Main Window senza nessuna opzione selezionata

Figura 7-1 Il wizard per una Main Window senza nessuna opzione selezionata.

Ora cambiate il nome del form chiamandolo frmMain e impostate come titolo (caption) 'Address Book'.

Invece di connettere pulsanti o altro direttamente agli slot come abbiamo fatto fino ad ora useremo i gruppi di azioni. In questo modo avremmo un unico segnale per gestire eventi multipli, come una voce di menu, una scorciatoia da tastiera e un pulsante della barra degli strumenti. Aggiungete quattro azioni usando il pulsante "new action" (evidenziato nella figura 7-2) in base a quanto mostrato nella tabella 7-1. Per fare cio', fate semplicemente click sul pulsante "new action" e cambiate l'azione usando l'editor delle proprieta'. Le azioni finali sono mostrate nella figura 7-2.

Il carattere & indica a Qt che deve sottolineare il carattere successivo e usarlo come scorciatoia da tastiera.

Il pulsante 'new action'

Figura 7-2 Il pulsante 'new action'.

name text menuText
aExit Exit E&xit
aAddContact Add Contact &Add Contact
aEditContact Edit Contact &Edit Contact
aRemoveContact Remove Contact &Remove Contact

Tabella 7-1

Le azioni

Figura 7-3 Le azioni.

Fate click destro allinterno del form per mostrare il menu contestuale. Da questo, selezionate la voce "add menu entry". Fatelo due volte in modo da avere un form con due voci di menu.

Ora dobbiamo rinominare le voci di menu. Nelle prime versioni 3.x di Qt questo veniva fatto facendo click destro sulle voci di menu e selezionando l'opzione relativa dal menu contestuale. Nelle versioni successive basta un singolo click per selezionare la voce di menu e il nome puo' essere impostato nell'editor delle proprieta'. In ogni caso, rinominate le voci come "&File" e "&Contacts" partendo da sinistra.

Ora trascinate semplicemente le azioni sui menu. Assegnate l'azione aExit al menu File e le altre azioni al menu Contacts. La figura 7-4 mostra il risultato.

Il menu Contacts

Figura 7-4 Il menu Contacts.

A questo punto aggiungete una list view al form e chiamatela lvContacts. Cambiate la proprieta' allColumnsShowFocus impostandola su True. Fatto cio', fate click destro sulla list view e selezionate la voce "edit". Eliminate tutti gli elementi nella lista (figura 7-5) e create le colonne "Name", "E-Mail" e "Phone Number" (figura 7-6).

Gli elementi nella list view

Figura 7-5 Gli elementi nella list view.

Le colonne della list view

Figura 7-6 Le colonne della list view.

Per fare in modo che la list view diventi il widget principale del form, selezionate il form e applicate un layout a griglia. Nelle proprieta', impostate il valore di layoutMargin a zero per fare in modo che la list view occuppi tutta la finestra. Il form finale viene mostrato nella figura 7-7.

Un'anteprima della finestra principale

Figura 7-7 Un'anteprima della finestra principale.

Ora dobbiamo aggiungere un po' di funzionalita' al progetto. iniziamo aggiungendo i seguenti slot privati al form: init(), addContact(), editContact() e removeContact(). La figura 7-8 mostra la finestra dell'editor di slot al completo.

Gli slot

Figura 7-8 Gli slot.

Nell'editor delle azioni, fate click sul pulsante "connect current action" e collegate i segnali come mostrato nella tabella 7-2. Per verificare se avete fatto tutto bene, fate click destro sul form e selezionate "Connections" dal menu contestuale. Dovrebbe apparire una finestra simile a quella della figura 7-9. (Notate che l'aspetto della finestra cambia nelle varie versioni di Qt, mentre il contenuto e' sempre lo stesso in tutte le versioni di Qt che ho visto.)

Sender Signal Slot (sempre uno di frmMain)
aExit activated() close()
aAddContact activated() addContact()
aEditContact activated() editContact()
aRemoveContact activated() removeContact()

Tabella 7-2

Le connessioni

Figura 7-9 Le connessioni.

Il prossimo passo consiste nel creare un dialog per inserire un nuovo contatto o per modificarne uno esistente. La figura 7-10 mostra l'aspetto del dialog una volta terminato.

Il dialog per modificare un contatto

Figura 7-10 Il dialog per modificare un contatto.

Le istruzioni per creare questo dialog sono minime dato che tutti i dettagli sono gia' stati descritti nel capitolo 5.

Iniziate aggiungendo un nuovo dialog al progetto. Chiamatelo dlgContact e impostate "Contact" come titolo della finestra. Aggiungete due pulsanti e chiamateli pbOk e pbCancel. ora aggiungete un group box, tre label e tre line edit. Chiamate questi ultimi leName, leEMail e lePhone. Non dimenticatevi i due spacer mostrati nella figura 7-11. Impostate anche il testo dei pulsanti e delle label come mostrato nella figura.

I widget prima di impostare il layout

Figura 7-11 I widget prima di impostare il layout.

Ora proseguiamo con i layout. Selezionate i line edit e applicate un layout verticale. Fate lo stesso con le label. Quindi selezionate i pulsanti e lo spacer orizzontale e applicate un layout orizzontale. Infine selezionate il frame e applicate un layout orizzontale per poi selezionare il form e applicarne uno verticale. Non ci resta che ridimensionare il dialog per farlo apparire piu' piacevole.

Passiamo a segnali e slot. Non dobbiamo fare altro che collegare i segnali clicked() dei pulsanti agli slot accept e reject del dialog. Fatto cio', il dialog e' pronto. Ecco dimostrato come sia facile creare un dialog semplice.

Prima di poter aggiungere del codice alla finestra principale ci serve una struttura dati per mantenere i contatti. In effetti non e' necessaria per quello che faremo in questo capitolo, ma ci servira' successivamente. Aggiungete quindi un file header C++ al progetto e inseriteci il codice dell'esempio 7-1 per poi salvarlo come contact.h.

#ifndef CONTACT_H
#define CONTACT_H

#include <qstring.h>

class Contact
{
public:
  QString name,
    eMail,
    phone;
};

#endif

Esempio 7-1

Per poter usare questa struttura dalla finestra principale dobbiamo includerla. Inseriremo i contatti in una QValueList, quindi dobbiamo dichiarare la lista come QValueList<Contact> m_contacts nell'elenco delle variabili di classe. Ci sono ancora tre file da includere, il dialog - dlgcontact.h, un message box - qmessagebox.h e la classe line edit - qlineedit.h. Se non includiamo l'header della classe line edit non sara' possibile accedere ai membri della classe dal codice della finestra principale, dato che quest'ultima non contiene dei line edit. Tutti i widget in una finestra, dialog oppure qudget composti verranno inclusi automaticamente. I file da includere e le dichiarazioni sono riassunti nella figura 7-12.

I file da includere e le dichiarazioni

Figura 7-12 I file da includere e le dichiarazioni.

Infine un po' di codice "attivo". Iniziamo inizializzando il dialog nello slot init(). Il codice mostrato nell'esempio 7-2 svuota semplicemente la list view. In realta' non e' necessario, ma serve a mostrare dove va inserito il codice per l'inizializzazione.

void frmMain::init()
{
  // Svuota la lista
  lvContacts->clear();
}

Esempio 7-2

Ora aggiungiamo il codice per poter inserire dei contatti. L'esempio 7-3 mostra il codice; e' abbastanza intuitivo e mostra come si possono utilizzare efficacemente i dialog. Prima vanno inizializzati, quindi si mostrano e se l'utente lo ha "accettato" si verificano i dati e si procede. Nel nostro caso si tratta di aggiungere un contatto alla lista.

void frmMain::addContact()
{
  // Crea un nuovo dialog
  dlgContact dlg( this );

  // Mostralo e attendi l'Ok o il Cancel
  if( dlg.exec() == QDialog::Accepted )
  {
    // Abbiamo un Ok
    Contact c;

    // Recupera le informazioni dal dialog
    c.name = dlg.leName->text();
    c.eMail = dlg.leEMail->text();
    c.phone = dlg.lePhone->text();

    // Controlla se ci sono nomi duplicati
    for( QValueList<Contact>::iterator it = m_contacts.begin(); it != m_contacts.end(); ++it )
    {
    if( (*it).name == c.name )
      {
        // Avvisa se ci sono duplicati e ritorna
        QMessageBox::warning( this, tr("Duplicate"), tr("The person ") + (*it).name + tr(" allready exists.") );
        return;
      }
    }
    // Nessun duplicato, aggiungi il contatto alla lista e mostralo nella listview
    m_contacts.append( c );
    lvContacts->insertItem( new QListViewItem( lvContacts, c.name , c.eMail, c.phone ) );
  }
}

Esempio 7-3

E' un bel pezzo di codice, quindi ecco le osservazioni. I link puntano alla documentazione ufficiale di Qt, in questo modo potete seguirli per ulteriori chiarimenti.

Come prima cosa creiamo un'istanza della nostra sottoclasse di QDialog, dlg. Con l'invocazione del metodo exec() attendiamo che il dialog venga chiuso e che ritorni un valore. Ci interessa sapere se il dialog e' stato accettato dall'utente, ecco perche' c'e' quell'if.

Inizializziamo un'istanza di Contact, c, con le informazioni prese dal dialog. Prendiamo il testo dei line edit con il metodo text().

Quindi usiamo una classe container di Qt, QValueList. Qt e' compatibile con la STL, ma i container di Qt sono piu' versatili. Se troviamo un duplicato mostriamo un messaggio di errore usando il metodo warning() della classe QMessageBox.

Per concludere, inseriamo il contatto nella lista e aggiungiamo un nuovo QListViewItem alla list view.

Continuiamo con il codice nell'esempio 7-4. Ci permette di rimuovere dei contatti dalla lista. Notate come sia facile rimuovere un elemento da una list view. Grazie a Qt, non c'e' alcun bisogno di preoccuparsi della gestione della memoria.

void frmMain::removeContact()
{
  // Ottieni un puntatore all'elemento corrente
  QListViewItem *item = lvContacts->currentItem();

  // Verifica se e' valido
  if( item )
  {
// Cerca l'elemento corrispondente nella lista interna
    for( QValueList<Contact>::iterator it = m_contacts.begin(); it != m_contacts.end(); ++it )
    {
      if( (*it).name == item->text(0) )
      {
        // Trovato - ora va rimosso dalla lista e dalla list view
        m_contacts.remove( it );
        delete item;
        return;
      }
    }
  }
}

Esempio 7-4

Ora ci manca solo il codice per un'ultimo slot. L'esempio 7-5 mostra il codice da usare per modificare un contatto. Puo' essere visto come una variante dei casi di inserimento e rimozione. Prima cerca l'elemento selezionato, quindi imposta il dialog e lo mostra. Se tutto e' andato bene modifica l'elemento nella lista. L'algoritmo per trovare elementi duplicati potrebbe meritare un breve commento. Se il nome e' stato cambiato, dobbiamo impedire che ci siano degli elementi con lo stesso.

void frmMain::editContact()
{
  // Ottieni un puntatore all'elemento selezionato
  QListViewItem *item = lvContacts->currentItem();
  
  // Verifica che sia valido
  if( item )
  {
    // Cerca nella lista interna
    for( QValueList<Contact>::iterator it = m_contacts.begin(); it != m_contacts.end(); ++it )
    {
      if( (*it).name == item->text(0) )
      {
        // trovato - mostralo nel dialog
        dlgContact dlg( this );
        
        dlg.leName->setText( (*it).name );
        dlg.leEMail->setText( (*it).eMail );
        dlg.lePhone->setText( (*it).phone );
        
        // Mostra il dialog e attendi il risultato
        if( dlg.exec() == QDialog::Accepted )
        {
          // Cerca eventuali duplicati controllando il numero di occorrenze
          int occurencies = 0;
          for( QValueList<Contact>::iterator i2 = m_contacts.begin(); i2 != m_contacts.end(); ++i2 )
            if( (*i2).name == dlg.leName->text() )
              occurencies++;
          
          // Se il nome e' cambiato non ci devono essere duplicati
          if( (dlg.leName->text() != (*it).name && occurencies > 0) || (occurencies > 1) )
          {
            // Avvisa l'utente e ritorna
            QMessageBox::warning( this, tr("Duplicate"), tr("The person ") + dlg.leName->text() + tr(" allready exists.") );
            return;
          }
          
          // Aggiorna la lista interna
          (*it).name = dlg.leName->text();
          (*it).eMail = dlg.leEMail->text();
          (*it).phone = dlg.lePhone->text();
          
          // Aggiorna la list view
          item->setText( 0, (*it).name );
          item->setText( 1, (*it).eMail );
          item->setText( 2, (*it).phone );
        }
        
        return;
      }
    }
  }
}

Esempio 7-5

Prima di poter avere un'applicazione funzionante, dobbiamo aggiungere una banale funzione main(). Aggiungete un nuovo file C++ chiamato main.cpp e inseritevi il codice dell'esempio 7-6. Il codice mostra una finestra e attende che questa venga chiusa.

#include <qapplication.h>

#include "frmmain.h"

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  frmMain *m = new frmMain();
  m->show();

  a.connect( &a, SIGNAL(lastWindowClosed()), &a, SLOT(quit()) );

  return a.exec();
}

Esempio 7-6

La figura 7-13 mostra l'applicazione in esecuzione. Per compilare il tutto, eseguite qmake adbook.pro && make da una console, quindi eseguite il file prodotto e divertitevi.

L'applicazione in esecuzione

Figura 7-13 L'applicazione in esecuzione.

Main window, dialog e widget

Molti dei nuovi utenti Qt restano confusi nel vedere quanti metodi ci sono per mostrare una finestra con alcuni widget. Il primo esempio di Qt usa direttamente un widget dal main(), mentre altri utilizzano dei dialog. Aggiungete la possibilita' di usare una main window e la confusione sara' totale.

Per farla breve, utilizzate le main window in applicazioni grosse e i dialog negli altri casi. Se vi interessano le mie spiegazioni continuate a leggere.

La possibilita' di usare un widget direttamente dal main() e' molto utile in fase di testing dei widget e per scrivere applicazioni molto piccole senza del vero e proprio codice attivo. Appena avete la necessita' di usare slot per azioni come aggiungere un nuovo elemento, cancellare una lista o altro, sarebbe consigliablie abbandonare l'approccio del widget.

Le applicazioni basate su dialog sono grandiose dato che sono facili da comporre e che possono essere usate per eseguire operazioni semplici senza utilizzare una main window. Non e' necessario limitare un'applicazione basata su dialog ad un unico dialog, ma se avete bisogno di avere un dialog che mostri un "documento" su cui altri dialog lavorano, dovreste considerare realmente la possibilita' di utilizzare una main window.

Come e' stato detto nel paragrafo precedente, l'approccio basato su main window e' consigliabile appena si rende necessario lavorare su un "documento". Questo non significa necessariamente un documento di testo, come in un moderno editor di testo di tipo WYSIWYG (What You see Is What You Get - letteralmente "quello che vedete e' quello che ottenete"), ma piu' in generale una collezione di dati (come nel design pattern MVC, model - view - controller). Un esempio di "documento" cosi' inteso e' l'elenco di contatti usato nella rubrica di questo capitolo. Ci sono molteplici vantaggi nell'utilizzo di una main window. Quello che mi piace di piu' e' la possibilita' di creare facilmente applicazioni che lavorino contemporaneamente su piu' documenti creando semplicemente piu' istanze della main window. Questo significa che ogni documento ottiene una propria finestra ed e' anche l'approccio raccomandato dalla ricerca sull'interazione uomo-computer. Fino a poco fa la maggior parte delle applicazioni di questo tipo (come Office) utilizzavano un'interfaccia MDI (molti documenti in un'unica finestra), mentre nelle versioni piu' recenti utilizzano un'interfaccia SDI (una finestra per ogni documento). Utilizzando una main window realizzerete proprio un'interfaccia SDI.

Riassunto

Il codice sorgente per questo capitolo puo' essere scaricato qui.

In questo capitolo si e' visto come viene costruita un'intera applicazione utilizzando una main window e un dialog per lavorare con il "documento" presentato nella finestra principale. E' stata anche chiarita la differenza tra main window e dialog.

Esercizi

  1. Come si possono aggiungere, rimuovere o cambiare elementi in una list view?
  2. Per quale tipo di applicazione utilizzereste una main window, un dialog o un widget? Un word processor? Un'interfaccia per ispell? Un'applicazione di disegno? Un rinominatore di file? Perche'?
  3. Nell'applicazione della rubrica, aggiungete un campo chiamato ICQ# ad ogni contatto e mostratelo nella list view.

This is a part of digitalfanatics.org and is valid XHTML.