Sur cette page

Carnet d'adresses

L'exemple du carnet d'adresses montre comment utiliser des modèles proxy pour afficher différentes vues sur les données d'un modèle unique.

Carnet d'adresses affichant les contacts et les adresses

Cet exemple fournit un carnet d'adresses qui permet de regrouper les contacts par ordre alphabétique en 9 groupes : ABC, DEF, GHI, ... ..., VW, ..., XYZ. Pour ce faire, on utilise plusieurs vues sur le même modèle, chacune étant filtrée à l'aide d'une instance de la classe QSortFilterProxyModel.

Vue d'ensemble

Le carnet d'adresses contient 5 classes : MainWindow AddressWidget , TableModel, NewAddressTab et AddDialog. La classe MainWindow utilise AddressWidget comme widget central et fournit les menus File et Tools.

Classes et éléments principaux du carnet d'adresses

La classe AddressWidget est une sous-classe de QTabWidget utilisée pour manipuler les 10 onglets affichés dans l'exemple : les 9 onglets du groupe alphabétique et une instance de NewAddressTab. La classe NewAddressTab est une sous-classe de QWidget qui n'est utilisée que lorsque le carnet d'adresses est vide, ce qui incite l'utilisateur à ajouter des contacts. AddressWidget interagit également avec une instance de TableModel pour ajouter, modifier et supprimer des entrées dans le carnet d'adresses.

TableModel est une sous-classe de QAbstractTableModel qui fournit l'API modèle/vue standard pour accéder aux données. Elle contient une liste des contacts ajoutés. Cependant, ces données ne sont pas toutes visibles dans un seul onglet. Au lieu de cela, QTableView est utilisé pour fournir 9 vues différentes des mêmes données, en fonction des groupes de l'alphabet.

QSortFilterProxyModel est la classe responsable du filtrage des contacts pour chaque groupe de contacts. Chaque modèle proxy utilise un QRegularExpression pour filtrer les contacts qui n'appartiennent pas au groupe alphabétique correspondant. La classe AddDialog est utilisée pour obtenir des informations de l'utilisateur pour le carnet d'adresses. Cette sous-classe QDialog est instanciée par NewAddressTab pour ajouter des contacts et par AddressWidget pour ajouter et modifier des contacts.

Nous commencerons par examiner l'implémentation de la classe TableModel.

Définition de la classe TableModel

La classe TableModel fournit une API standard pour accéder aux données de sa liste de contacts en sous-classant QAbstractTableModel. Les fonctions de base qui doivent être mises en œuvre à cet effet sont les suivantes : rowCount(), columnCount(), data(), headerData(). Pour que TableModel soit modifiable, il doit fournir des implémentations des fonctions insertRows(), removeRows(), setData() et flags().

struct Contact
{
    QString name;
    QString address;

    bool operator==(const Contact &other) const
    {
        return name == other.name && address == other.address;
    }
};

inline QDataStream &operator<<(QDataStream &stream, const Contact &contact)
{
    return stream << contact.name << contact.address;
}

inline QDataStream &operator>>(QDataStream &stream, Contact &contact)
{
    return stream >> contact.name >> contact.address;
}

class TableModel : public QAbstractTableModel
{
    Q_OBJECT

public:
    TableModel(QObject *parent = nullptr);
    TableModel(const QList<Contact> &contacts, QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent) const override;
    int columnCount(const QModelIndex &parent) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    const QList<Contact> &getContacts() const;

private:
    QList<Contact> contacts;
};

Deux constructeurs sont utilisés, un constructeur par défaut qui utilise TableModel's own QList<Contact> et un qui prend QList<Contact> comme argument, pour des raisons de commodité.

Mise en œuvre de la classe TableModel

Nous implémentons les deux constructeurs tels qu'ils sont définis dans le fichier d'en-tête. Le second constructeur initialise la liste des contacts du modèle avec la valeur du paramètre.

TableModel::TableModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

TableModel::TableModel(const QList<Contact> &contacts, QObject *parent)
    : QAbstractTableModel(parent), contacts(contacts)
{
}

Les fonctions rowCount() et columnCount() renvoient les dimensions du modèle. Alors que la valeur de rowCount() varie en fonction du nombre de contacts ajoutés au carnet d'adresses, la valeur de columnCount() est toujours égale à 2 car nous n'avons besoin d'espace que pour les colonnes Nom et Adresse.

int TableModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : contacts.size();
}

int TableModel::columnCount(const QModelIndex &parent) const
{
    return parent.isValid() ? 0 : 2;
}

La fonction data() renvoie soit un nom, soit une adresse, en fonction du contenu de l'index de modèle fourni. Le numéro de ligne stocké dans l'index du modèle est utilisé pour référencer un élément dans la liste des contacts. La sélection est gérée par la fonction QItemSelectionModel, qui sera expliquée à l'aide de la fonction AddressWidget.

QVariant TableModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= contacts.size() || index.row() < 0)
        return QVariant();

    if (role == Qt::DisplayRole) {
        const auto &contact = contacts.at(index.row());

        switch (index.column()) {
            case 0:
                return contact.name;
            case 1:
                return contact.address;
            default:
                break;
        }
    }
    return QVariant();
}

La fonction headerData() affiche l'en-tête du tableau, le nom et l'adresse. Si vous avez besoin d'entrées numérotées pour votre carnet d'adresses, vous pouvez utiliser un en-tête vertical que nous avons caché dans cet exemple (voir l'implémentation AddressWidget ).

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal) {
        switch (section) {
            case 0:
                return tr("Name");
            case 1:
                return tr("Address");
            default:
                break;
        }
    }
    return QVariant();
}

La fonction insertRows() est appelée avant l'ajout de nouvelles données, sinon les données ne seront pas affichées. Les fonctions beginInsertRows() et endInsertRows() sont appelées pour s'assurer que toutes les vues connectées sont informées des changements.

bool TableModel::insertRows(int position, int rows, const QModelIndex &index)
{
    Q_UNUSED(index);
    beginInsertRows(QModelIndex(), position, position + rows - 1);

    for (int row = 0; row < rows; ++row)
        contacts.insert(position, { QString(), QString() });

    endInsertRows();
    return true;
}

La fonction removeRows() est appelée pour supprimer des données. Là encore, les fonctions beginRemoveRows() et endRemoveRows() sont appelées pour s'assurer que toutes les vues connectées sont informées des modifications.

bool TableModel::removeRows(int position, int rows, const QModelIndex &index)
{
    Q_UNUSED(index);
    beginRemoveRows(QModelIndex(), position, position + rows - 1);

    for (int row = 0; row < rows; ++row)
        contacts.removeAt(position);

    endRemoveRows();
    return true;
}

La fonction setData() est la fonction qui insère les données dans la table, élément par élément et non ligne par ligne. Cela signifie que pour remplir une ligne du carnet d'adresses, la fonction setData() doit être appelée deux fois, car chaque ligne comporte deux colonnes. Il est important d'émettre le signal dataChanged() car il indique à toutes les vues connectées de mettre à jour leur affichage.

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {
        const int row = index.row();
        auto contact = contacts.value(row);

        switch (index.column()) {
            case 0:
                contact.name = value.toString();
                break;
            case 1:
                contact.address = value.toString();
                break;
            default:
                return false;
        }
        contacts.replace(row, contact);
        emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});

        return true;
    }

    return false;
}

La fonction flags() renvoie les drapeaux des éléments pour l'index donné.

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
}

Nous avons activé le drapeau Qt::ItemIsEditable parce que nous voulons permettre à TableModel d'être édité. Bien que nous n'utilisions pas les fonctions d'édition de l'objet QTableView dans cet exemple, nous les activons ici afin de pouvoir réutiliser le modèle dans d'autres programmes.

La dernière fonction de TableModel, getContacts(), renvoie l'objet QList<Contact> qui contient tous les contacts du carnet d'adresses. Nous utiliserons cette fonction ultérieurement pour obtenir la liste des contacts afin de vérifier s'il existe des entrées, d'écrire les contacts dans un fichier et de les relire. De plus amples explications sont fournies à l'adresse AddressWidget.

const QList<Contact> &TableModel::getContacts() const
{
    return contacts;
}

Définition de la classe AddressWidget

La classe AddressWidget est techniquement la classe principale de cet exemple, car elle fournit des fonctions permettant d'ajouter, de modifier et de supprimer des contacts, d'enregistrer les contacts dans un fichier et de les charger à partir d'un fichier.

class AddressWidget : public QTabWidget
{
    Q_OBJECT

public:
    AddressWidget(QWidget *parent = nullptr);
    void readFromFile();
    void writeToFile();

public slots:
    void showAddEntryDialog();
    void addEntry(const QString &name, const QString &address);
    void editEntry();
    void removeEntry();

signals:
    void selectionChanged (const QItemSelection &selected);

private:
    void setupTabs();

    inline static QString fileName =
        QStandardPaths::standardLocations(QStandardPaths::TempLocation).value(0)
        + QStringLiteral("/addressbook.dat");
    TableModel *table;
    NewAddressTab *newAddressTab;
};

AddressWidget Elle étend QTabWidget afin de contenir 10 onglets (NewAddressTab et les 9 onglets du groupe alphabétique) et manipule également table, l'objet TableModel, proxyModel, l'objet QSortFilterProxyModel que nous utilisons pour filtrer les entrées, et tableView, l'objet QTableView.

Mise en œuvre de la classe AddressWidget

Le constructeur de AddressWidget accepte un widget parent et instancie NewAddressTab, TableModel et QSortFilterProxyModel. L'objet NewAddressTab, utilisé pour indiquer que le carnet d'adresses est vide, est ajouté et les 9 autres onglets sont configurés avec setupTabs().

AddressWidget::AddressWidget(QWidget *parent)
    : QTabWidget(parent),
      table(new TableModel(this)),
      newAddressTab(new NewAddressTab(this))
{
    connect(newAddressTab, &NewAddressTab::sendDetails,
        this, &AddressWidget::addEntry);

    addTab(newAddressTab, tr("Address Book"));

    setupTabs();
}

La fonction setupTabs() est utilisée pour configurer les 9 onglets du groupe alphabétique, les vues de table et les modèles proxy dans AddressWidget. Chaque modèle proxy est configuré pour filtrer les noms de contact en fonction du groupe alphabétique correspondant à l'aide d'un objet QRegularExpression insensible à la casse. Les vues de tableau sont également triées par ordre croissant à l'aide de la fonction sort() du modèle proxy correspondant.

Les objets selectionMode et selectionBehavior de chaque vue de table sont respectivement définis sur QAbstractItemView::SingleSelection et QAbstractItemView::SelectRows, ce qui permet à l'utilisateur de sélectionner simultanément tous les éléments d'une ligne. Chaque objet QTableView reçoit automatiquement un QItemSelectionModel qui garde la trace des index sélectionnés.

void AddressWidget::setupTabs()
{
    using namespace Qt::StringLiterals;
    const auto groups = { "ABC"_L1, "DEF"_L1, "GHI"_L1, "JKL"_L1, "MNO"_L1, "PQR"_L1,
                          "STU"_L1, "VW"_L1, "XYZ"_L1 };

    for (QLatin1StringView str : groups) {
        const auto regExp = QRegularExpression(QLatin1StringView("^[%1].*").arg(str),
                                               QRegularExpression::CaseInsensitiveOption);

        auto proxyModel = new QSortFilterProxyModel(this);
        proxyModel->setSourceModel(table);
        proxyModel->setFilterRegularExpression(regExp);
        proxyModel->setFilterKeyColumn(0);

        QTableView *tableView = new QTableView;
        tableView->setModel(proxyModel);
        tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
        tableView->horizontalHeader()->setStretchLastSection(true);
        tableView->verticalHeader()->hide();
        tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
        tableView->setSelectionMode(QAbstractItemView::SingleSelection);
        tableView->setSortingEnabled(true);

        connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
                this, &AddressWidget::selectionChanged);

        connect(this, &QTabWidget::currentChanged, this, [this, tableView](int tabIndex) {
            if (widget(tabIndex) == tableView)
                emit selectionChanged(tableView->selectionModel()->selection());
        });

        addTab(tableView, str);
    }
}

La classe QItemSelectionModel fournit un signal selectionChanged qui est connecté au signal selectionChanged() de AddressWidget. Nous connectons également le signal QTabWidget::currentChanged() à l'expression lambda qui émet également le signal selectionChanged() de AddressWidget. Ces connexions sont nécessaires pour activer les actions Edit Entry... et Remove Entry dans le menu Outils de MainWindow. Ce point est expliqué plus en détail dans l'implémentation de MainWindow.

Chaque vue de table du carnet d'adresses est ajoutée en tant qu'onglet à QTabWidget avec l'étiquette correspondante, obtenue à partir de QStringList des groupes.

Signaux de carnet d'adresses et connexions de fentes

Nous fournissons deux fonctions addEntry(): L'une est destinée à accepter les données de l'utilisateur et l'autre est chargée d'ajouter de nouvelles entrées au carnet d'adresses. Nous divisons la responsabilité de l'ajout d'entrées en deux parties pour permettre à newAddressTab d'insérer des données sans avoir à faire apparaître une boîte de dialogue.

La première fonction addEntry() est un slot connecté à l'action Add Entry... de MainWindow. Cette fonction crée un objet AddDialog et appelle ensuite la deuxième fonction addEntry() pour ajouter le contact à table.

void AddressWidget::showAddEntryDialog()
{
    AddDialog aDialog;

    if (aDialog.exec())
        addEntry(aDialog.name(), aDialog.address());
}

Une validation de base est effectuée dans la deuxième fonction addEntry() afin d'éviter les entrées en double dans le carnet d'adresses. Comme indiqué pour TableModel, c'est en partie la raison pour laquelle nous avons besoin de la méthode getter getContacts().

void AddressWidget::addEntry(const QString &name, const QString &address)
{
    if (!name.front().isLetter()) {
        QMessageBox::information(this, tr("Invalid name"),
            tr("The name must start with a letter."));
    } else if (!table->getContacts().contains({ name, address })) {
        table->insertRows(0, 1, QModelIndex());

        QModelIndex index = table->index(0, 0, QModelIndex());
        table->setData(index, name, Qt::EditRole);
        index = table->index(0, 1, QModelIndex());
        table->setData(index, address, Qt::EditRole);
        removeTab(indexOf(newAddressTab));
    } else {
        QMessageBox::information(this, tr("Duplicate Name"),
            tr("The name \"%1\" already exists.").arg(name));
    }
}

Si le modèle ne contient pas déjà une entrée portant le même nom, nous appelons setData() pour insérer le nom et l'adresse dans les première et deuxième colonnes. Dans le cas contraire, nous affichons un message QMessageBox pour informer l'utilisateur.

Remarque : le newAddressTab est supprimé dès qu'un contact est ajouté, car le carnet d'adresses n'est plus vide.

La modification d'une entrée permet de mettre à jour l'adresse du contact uniquement, car l'exemple ne permet pas à l'utilisateur de modifier le nom d'un contact existant.

Tout d'abord, nous obtenons l'objet QTableView de l'onglet actif en utilisant QTabWidget::currentWidget(). Ensuite, nous extrayons l'objet selectionModel de l'objet tableView pour obtenir les index sélectionnés.

void AddressWidget::editEntry()
{
    QTableView *temp = static_cast<QTableView*>(currentWidget());
    QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
    QItemSelectionModel *selectionModel = temp->selectionModel();

    const QModelIndexList indexes = selectionModel->selectedRows();
    QString name;
    QString address;
    int row = -1;

    for (const QModelIndex &index : indexes) {
        row = proxy->mapToSource(index).row();
        QModelIndex nameIndex = table->index(row, 0, QModelIndex());
        QVariant varName = table->data(nameIndex, Qt::DisplayRole);
        name = varName.toString();

        QModelIndex addressIndex = table->index(row, 1, QModelIndex());
        QVariant varAddr = table->data(addressIndex, Qt::DisplayRole);
        address = varAddr.toString();
    }

Ensuite, nous extrayons les données de la ligne que l'utilisateur a l'intention de modifier. Ces données sont affichées dans une instance de AddDialog avec un titre de fenêtre différent. Le site table n'est mis à jour que si des modifications ont été apportées aux données du site aDialog.

    AddDialog aDialog;
    aDialog.setWindowTitle(tr("Edit a Contact"));
    aDialog.editAddress(name, address);

    if (aDialog.exec()) {
        const QString newAddress = aDialog.address();
        if (newAddress != address) {
            const QModelIndex index = table->index(row, 1, QModelIndex());
            table->setData(index, newAddress, Qt::EditRole);
        }
    }
}

Boîte de dialogue pour l'édition du nom et de l'adresse

Les entrées sont supprimées à l'aide de la fonction removeEntry(). La ligne sélectionnée est supprimée en y accédant par l'intermédiaire de l'objet QItemSelectionModel, selectionModel. L'objet newAddressTab n'est réajouté à l'objet AddressWidget que si l'utilisateur supprime tous les contacts du carnet d'adresses.

void AddressWidget::removeEntry()
{
    QTableView *temp = static_cast<QTableView*>(currentWidget());
    QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
    QItemSelectionModel *selectionModel = temp->selectionModel();

    const QModelIndexList indexes = selectionModel->selectedRows();

    for (QModelIndex index : indexes) {
        int row = proxy->mapToSource(index).row();
        table->removeRows(row, 1, QModelIndex());
    }

    if (table->rowCount(QModelIndex()) == 0)
        insertTab(0, newAddressTab, tr("Address Book"));
}

La fonction writeToFile() est utilisée pour enregistrer un fichier contenant tous les contacts du carnet d'adresses. Le fichier est enregistré dans un format personnalisé .dat. Le contenu de la liste de contacts est écrit sur file à l'aide de QDataStream. Si le fichier ne peut pas être ouvert, un message QMessageBox s'affiche avec le message d'erreur correspondant.

void AddressWidget::writeToFile()
{
    QFile file(fileName);

    if (!file.open(QIODevice::WriteOnly)) {
        QMessageBox::information(this, tr("Unable to open file"), file.errorString());
        return;
    }

    QDataStream out(&file);
    out << table->getContacts();
}

La fonction readFromFile() charge un fichier contenant tous les contacts du carnet d'adresses, précédemment enregistré à l'aide de writeToFile(). QDataStream est utilisé pour lire le contenu d'un fichier .dat dans une liste de contacts et chacun d'entre eux est ajouté à l'aide de addEntry().

void AddressWidget::readFromFile()
{
    QFile file(fileName);

    if (!file.open(QIODevice::ReadOnly)) {
        QMessageBox::information(this, tr("Unable to open file"),
            file.errorString());
        return;
    }

    QList<Contact> contacts;
    QDataStream in(&file);
    in >> contacts;

    if (contacts.isEmpty()) {
        QMessageBox::information(this, tr("No contacts in file"),
                                 tr("The file you are attempting to open contains no contacts."));
    } else {
        for (const auto &contact: std::as_const(contacts))
            addEntry(contact.name, contact.address);
    }
}

Définition de la classe NewAddressTab

La classe NewAddressTab fournit un onglet informatif indiquant à l'utilisateur que le carnet d'adresses est vide. Il apparaît et disparaît en fonction du contenu du carnet d'adresses, comme indiqué dans l'implémentation de AddressWidget.

Onglet pour l'ajout de nouveaux contacts

La classe NewAddressTab étend la classe QWidget et contient les classes QLabel et QPushButton.

class NewAddressTab : public QWidget
{
    Q_OBJECT

public:
    NewAddressTab(QWidget *parent = nullptr);

public slots:
    void addEntry();

signals:
    void sendDetails(const QString &name, const QString &address);
};

Mise en œuvre de la classe NewAddressTab

Le constructeur instancie addButton, descriptionLabel et connecte le signal de addButton à l'emplacement addEntry().

NewAddressTab::NewAddressTab(QWidget *parent)
    : QWidget(parent)
{
    auto descriptionLabel = new QLabel(tr("There are currently no contacts in your address book. "
                                          "\nClick Add to add new contacts."));

    auto addButton = new QPushButton(tr("Add"));

    connect(addButton, &QAbstractButton::clicked, this, &NewAddressTab::addEntry);

    auto mainLayout = new QVBoxLayout;
    mainLayout->addWidget(descriptionLabel);
    mainLayout->addWidget(addButton, 0, Qt::AlignCenter);

    setLayout(mainLayout);
}

La fonction addEntry() est similaire à la fonction addEntry() de AddressWidget dans le sens où les deux fonctions instancient un objet AddDialog. Les données du dialogue sont extraites et envoyées à l'emplacement addEntry() de AddressWidget en émettant le signal sendDetails().

void NewAddressTab::addEntry()
{
    AddDialog aDialog;

    if (aDialog.exec())
        emit sendDetails(aDialog.name(), aDialog.address());
}

Appels de fonction pour l'ajout d'un nouveau contact

Définition de la classe AddDialog

La classe AddDialog étend QDialog et fournit à l'utilisateur un QLineEdit et un QTextEdit pour saisir des données dans le carnet d'adresses.

class AddDialog : public QDialog
{
    Q_OBJECT

public:
    AddDialog(QWidget *parent = nullptr);

    QString name() const;
    QString address() const;
    void editAddress(const QString &name, const QString &address);

private:
    QLineEdit *nameText;
    QTextEdit *addressText;
};

Boîte de dialogue pour l'ajout d'un nouveau nom et d'une nouvelle adresse

Mise en œuvre de la classe AddDialog

Le constructeur de AddDialog met en place l'interface utilisateur, en créant les widgets nécessaires et en les plaçant dans les layouts.

AddDialog::AddDialog(QWidget *parent)
    : QDialog(parent),
      nameText(new QLineEdit),
      addressText(new QTextEdit)
{
    auto nameLabel = new QLabel(tr("Name"));
    auto addressLabel = new QLabel(tr("Address"));
    auto okButton = new QPushButton(tr("OK"));
    auto cancelButton = new QPushButton(tr("Cancel"));

    auto gLayout = new QGridLayout;
    gLayout->setColumnStretch(1, 2);
    gLayout->addWidget(nameLabel, 0, 0);
    gLayout->addWidget(nameText, 0, 1);

    gLayout->addWidget(addressLabel, 1, 0, Qt::AlignLeft|Qt::AlignTop);
    gLayout->addWidget(addressText, 1, 1, Qt::AlignLeft);

    auto buttonLayout = new QHBoxLayout;
    buttonLayout->addWidget(okButton);
    buttonLayout->addWidget(cancelButton);

    gLayout->addLayout(buttonLayout, 2, 1, Qt::AlignRight);

    auto mainLayout = new QVBoxLayout;
    mainLayout->addLayout(gLayout);
    setLayout(mainLayout);

    connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept);
    connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject);

    setWindowTitle(tr("Add a Contact"));
}

QString AddDialog::name() const
{
    return nameText->text();
}

QString AddDialog::address() const
{
    return addressText->toPlainText();
}

void AddDialog::editAddress(const QString &name, const QString &address)
{
    nameText->setReadOnly(true);
    nameText->setText(name);
    addressText->setPlainText(address);
}

Pour donner à la boîte de dialogue le comportement souhaité, nous connectons les boutons OK et Cancel aux emplacements accept() et reject() de la boîte de dialogue. Étant donné que la boîte de dialogue sert uniquement de conteneur pour les informations relatives au nom et à l'adresse, nous n'avons pas besoin d'implémenter d'autres fonctions pour elle.

Définition de la classe MainWindow

La classe MainWindow étend QMainWindow et implémente les menus et les actions nécessaires à la manipulation du carnet d'adresses.

Ouvrir, Enregistrer et Quitter dans le menu FichierAjouter une entrée, Modifier une entrée et Supprimer une entrée dans le menu Outils
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

private slots:
    void updateActions(const QItemSelection &selection);
    void openFile();
    void saveFile();

private:
    void createMenus();

    AddressWidget *addressWidget;
    QAction *editAct;
    QAction *removeAct;
};

La classe MainWindow utilise un AddressWidget comme widget central et fournit le menu Fichier avec les actions Open, Close et Exit, ainsi que le menu Tools avec les actions Add Entry..., Edit Entry... et Remove Entry.

Mise en œuvre de la classe MainWindow

Le constructeur de MainWindow instancie AddressWidget, le définit comme widget central et appelle la fonction createMenus().

MainWindow::MainWindow()
    : QMainWindow(),
      addressWidget(new AddressWidget)
{
    setCentralWidget(addressWidget);
    createMenus();
    setWindowTitle(tr("Address Book"));
}

La fonction createMenus() met en place les menus File et Tools, en connectant les actions à leurs emplacements respectifs. Les actions Edit Entry... et Remove Entry sont désactivées par défaut, car elles ne peuvent être exécutées sur un carnet d'adresses vide. Elles ne sont activées que lorsqu'un ou plusieurs contacts sont ajoutés.

void MainWindow::createMenus()
{
    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));

    QAction *openAct = new QAction(tr("&Open..."), this);
    fileMenu->addAction(openAct);
    connect(openAct, &QAction::triggered, this, &MainWindow::openFile);
    ...

    editAct = new QAction(tr("&Edit Entry..."), this);
    editAct->setEnabled(false);
    toolMenu->addAction(editAct);
    connect(editAct, &QAction::triggered, addressWidget, &AddressWidget::editEntry);

    toolMenu->addSeparator();

    removeAct = new QAction(tr("&Remove Entry"), this);
    removeAct->setEnabled(false);
    toolMenu->addAction(removeAct);
    connect(removeAct, &QAction::triggered, addressWidget, &AddressWidget::removeEntry);

    connect(addressWidget, &AddressWidget::selectionChanged,
        this, &MainWindow::updateActions);
}

Outre la connexion des signaux de toutes les actions à leurs emplacements respectifs, nous connectons également le signal selectionChanged() de AddressWidget à son emplacement updateActions().

La fonction openFile() ouvre un fichier personnalisé addressbook.dat qui contient les contacts du carnet d'adresses. Cette fonction est un emplacement connecté à openAct dans le menu File.

void MainWindow::openFile()
{
    addressWidget->readFromFile();
}

La fonction saveFile() enregistre un fichier personnalisé addressbook.dat qui contiendra les contacts du carnet d'adresses. Cette fonction est un emplacement connecté à saveAct dans le menu File.

void MainWindow::saveFile()
{
    addressWidget->writeToFile();
}

La fonction updateActions() active et désactive Edit Entry... et Remove Entry en fonction du contenu du carnet d'adresses. Si le carnet d'adresses est vide, ces actions sont désactivées ; sinon, elles sont activées. Cette fonction est un slot connecté au signal AddressWidget's selectionChanged().

void MainWindow::updateActions(const QItemSelection &selection)
{
    QModelIndexList indexes = selection.indexes();

    if (!indexes.isEmpty()) {
        removeAct->setEnabled(true);
        editAct->setEnabled(true);
    } else {
        removeAct->setEnabled(false);
        editAct->setEnabled(false);
    }
}

Fonction main()

La fonction principale du carnet d'adresses instancie QApplication et ouvre MainWindow avant d'exécuter la boucle d'événements.

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow mw;
    mw.show();
    return app.exec();
}

Exemple de projet @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.