Sur cette page

Exemple de l'éditeur de raccourcis

L'exemple de l'éditeur de raccourcis montre comment créer un modèle hiérarchique de base, en lecture-écriture, à utiliser avec les classes de vue et QKeySequenceEdit standard de Qt. Pour une description de la programmation modèle/vue, voir la vue d'ensemble de la programmation modèle/vue.

Liste des actions et des raccourcis clavier qui leur sont attribués

L'architecture modèle/vue de Qt fournit un moyen standard pour les vues de manipuler les informations dans une source de données, en utilisant un modèle abstrait des données pour simplifier et standardiser la façon dont on y accède. Le modèle de l'éditeur de raccourcis représente les actions sous la forme d'un arbre d'éléments et permet aux vues d'accéder à ces données par le biais d'un système basé sur un index. Plus généralement, les modèles peuvent être utilisés pour représenter les données sous la forme d'une structure arborescente en permettant à chaque élément d'agir en tant que parent d'une table d'éléments enfants.

Conception et concepts

La structure de données que nous utilisons pour représenter la structure des données prend la forme d'un arbre construit à partir d'objets ShortcutEditorModelItem. Chaque objet ShortcutEditorModelItem représente un élément de l'arborescence et contient deux colonnes de données.

Structure du modèle de raccourci en lignesStructure de l'éditeur de raccourcis

Les données sont stockées en interne dans le modèle à l'aide d'objets ShortcutEditorModelItem qui sont reliés entre eux dans une structure arborescente basée sur des pointeurs. En règle générale, chaque objet ShortcutEditorModelItem possède un élément parent et peut avoir un certain nombre d'éléments enfants. Toutefois, l'élément racine de l'arborescence n'a pas d'élément parent et n'est jamais référencé en dehors du modèle.

Chaque élément ShortcutEditorModelItem contient des informations sur sa place dans l'arborescence ; il peut renvoyer son élément parent et son numéro de ligne. Le fait de disposer de ces informations facilite la mise en œuvre du modèle.

Étant donné que chaque élément d'une arborescence contient généralement plusieurs colonnes de données (un nom et un raccourci dans cet exemple), il est naturel de stocker ces informations dans chaque élément. Par souci de simplicité, nous utiliserons une liste d'objets QVariant pour stocker les données de chaque colonne de l'élément.

L'utilisation d'une structure arborescente basée sur des pointeurs signifie que, lors du passage d'un index de modèle à une vue, nous pouvons enregistrer l'adresse de l'élément correspondant dans l'index (voir QAbstractItemModel::createIndex()) et la récupérer plus tard avec QModelIndex::internalPointer(). Cela facilite l'écriture du modèle et garantit que tous les index de modèle qui font référence au même élément ont le même pointeur de données interne.

Avec la structure de données appropriée en place, nous pouvons créer un modèle d'arbre avec un minimum de code supplémentaire pour fournir des index de modèle et des données à d'autres composants.

Définition de la classe ShortcutEditorModelItem

La classe ShortcutEditorModelItem est définie comme suit :

La classe est une classe C++ de base. Elle n'hérite pas de QObject et ne fournit pas de signaux ni d'emplacements. Elle est utilisée pour contenir une liste de QVariants, contenant des données de colonne et des informations sur sa position dans l'arborescence. Les fonctions offrent les caractéristiques suivantes :

  • La fonction appendChildItem() est utilisée pour ajouter des données lors de la construction initiale du modèle et n'est pas utilisée dans le cadre d'une utilisation normale.
  • Les fonctions child() et childCount() permettent au modèle d'obtenir des informations sur tous les éléments enfants.
  • L'information sur le nombre de colonnes associées à l'élément est fournie par columnCount(), et les données de chaque colonne peuvent être obtenues à l'aide de la fonction data().
  • Les fonctions row() et parent() sont utilisées pour obtenir le numéro de ligne de l'élément et l'élément parent.

Les données relatives à l'élément parent et à la colonne sont stockées dans les variables membres privées parentItem et itemData. La variable childItems contient une liste de pointeurs vers les éléments enfants de l'élément.

Définition de la classe ShortcutEditorModel

La classe ShortcutEditorModel est définie comme suit :

class ShortcutEditorModel : public QAbstractItemModel
{
    Q_OBJECT

    class ShortcutEditorModelItem
    {
    public:
        explicit ShortcutEditorModelItem(const QList<QVariant> &data,
                                         ShortcutEditorModelItem *parentItem = nullptr);
        ~ShortcutEditorModelItem();

        void appendChild(ShortcutEditorModelItem *child);

        ShortcutEditorModelItem *child(int row) const;
        int childCount() const;
        int columnCount() const;
        QVariant data(int column) const;
        int row() const;
        ShortcutEditorModelItem *parentItem() const;
        QAction *action() const;

    private:
        QList<ShortcutEditorModelItem *> m_childItems;
        QList<QVariant> m_itemData;
        ShortcutEditorModelItem *m_parentItem;
    };

public:
    explicit ShortcutEditorModel(QObject *parent = nullptr);
    ~ShortcutEditorModel() override;

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;
    int rowCount(const QModelIndex &index = QModelIndex()) const override;
    int columnCount(const QModelIndex &index = QModelIndex()) const override;

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;

    void setActions();

private:
    void setupModelData(ShortcutEditorModelItem *parent);

    ShortcutEditorModelItem *m_rootItem;
};

Cette classe est similaire à la plupart des autres sous-classes de QAbstractItemModel qui fournissent des modèles en lecture-écriture. Seules la forme du constructeur et la fonction setupModelData() sont spécifiques à ce modèle. En outre, nous fournissons un destructeur pour nettoyer le modèle lorsqu'il est détruit.

Mise en œuvre de la classe ShortcutEditorModel

Le constructeur prend un argument contenant les données que le modèle partagera avec les vues et les délégués :

ShortcutEditorModel::ShortcutEditorModel(QObject *parent)
    : QAbstractItemModel(parent)
{
    m_rootItem = new ShortcutEditorModelItem({tr("Name"), tr("Shortcut")});
}

Il appartient au constructeur de créer un élément racine pour le modèle. Cet élément ne contient que des données d'en-tête verticales pour des raisons de commodité. Nous l'utilisons également pour référencer la structure de données interne qui contient les données du modèle, et il est utilisé pour représenter un parent imaginaire des éléments de niveau supérieur dans le modèle.

La structure de données interne du modèle est alimentée en éléments par la fonction setupModelData(). Nous examinerons cette fonction séparément à la fin de ce document.

Le destructeur garantit que l'élément racine et tous ses descendants sont supprimés lorsque le modèle est détruit :

ShortcutEditorModel::~ShortcutEditorModel()
{
    delete m_rootItem;
}

Comme nous ne pouvons pas ajouter de données au modèle une fois qu'il est construit et configuré, cela simplifie la gestion de l'arbre interne des éléments.

Les modèles doivent implémenter une fonction index() afin de fournir des index que les vues et les délégués utiliseront pour accéder aux données. Les index sont créés pour d'autres composants lorsqu'ils sont référencés par leurs numéros de ligne et de colonne et par l'index de leur modèle parent. Si un index de modèle invalide est spécifié comme parent, c'est au modèle de renvoyer un index correspondant à un élément de premier niveau dans le modèle.

Lorsqu'un index de modèle est fourni, nous vérifions d'abord s'il est valide. Dans le cas contraire, nous obtenons le pointeur de données de l'index du modèle à l'aide de la fonction internalPointer() et l'utilisons pour référencer un objet TreeItem. Notez que tous les index de modèle que nous construisons contiendront un pointeur vers un objet TreeItem existant, de sorte que nous pouvons garantir que tous les index de modèle valides que nous recevons contiendront un pointeur de données valide.

void ShortcutEditorModel::setActions()
{
    beginResetModel();
    setupModelData(m_rootItem);
    endResetModel();
}

Étant donné que les arguments ligne et colonne de cette fonction font référence à un élément enfant de l'élément parent correspondant, nous obtenons l'élément à l'aide de la fonction TreeItem::child(). La fonction createIndex() est utilisée pour créer un index de modèle à renvoyer. Nous spécifions les numéros de ligne et de colonne, ainsi qu'un pointeur sur l'élément lui-même. L'index du modèle peut être utilisé ultérieurement pour obtenir les données de l'élément.

La façon dont les objets TreeItem sont définis facilite l'écriture de la fonction parent():

QModelIndex ShortcutEditorModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    ShortcutEditorModelItem *parentItem;
    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = static_cast<ShortcutEditorModelItem*>(parent.internalPointer());

    ShortcutEditorModelItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);

    return QModelIndex();
}

Il suffit de s'assurer que l'on ne renvoie jamais un index de modèle correspondant à l'élément racine. Pour être cohérent avec la manière dont la fonction index() est implémentée, nous renvoyons un index de modèle invalide pour le parent de tout élément de niveau supérieur dans le modèle.

Lors de la création d'un index de modèle à renvoyer, nous devons spécifier les numéros de ligne et de colonne de l'élément parent au sein de son propre parent. Nous pouvons facilement découvrir le numéro de ligne avec la fonction TreeItem::row(), mais nous suivons une convention qui consiste à spécifier 0 comme numéro de colonne du parent. L'index du modèle est créé avec createIndex() de la même manière que dans la fonction index().

La fonction rowCount() renvoie simplement le nombre d'éléments enfants pour TreeItem qui correspond à un index de modèle donné, ou le nombre d'éléments de niveau supérieur si un index invalide est spécifié :

QModelIndex ShortcutEditorModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    ShortcutEditorModelItem *childItem = static_cast<ShortcutEditorModelItem*>(index.internalPointer());
    ShortcutEditorModelItem *parentItem = childItem->parentItem();

    if (parentItem == m_rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

Étant donné que chaque élément gère ses propres données de colonne, la fonction columnCount() doit appeler la fonction columnCount() de l'élément pour déterminer le nombre de colonnes présentes pour un indice de modèle donné. Comme pour la fonction rowCount(), si un index de modèle non valide est spécifié, le nombre de colonnes renvoyées est déterminé à partir de l'élément racine :

int ShortcutEditorModel::rowCount(const QModelIndex &parent) const
{
    ShortcutEditorModelItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = m_rootItem;
    else
        parentItem = static_cast<ShortcutEditorModelItem*>(parent.internalPointer());

    return parentItem->childCount();
}

Les données sont obtenues à partir du modèle via data(). Étant donné que l'élément gère ses propres colonnes, nous devons utiliser le numéro de colonne pour récupérer les données avec la fonction TreeItem::data():

int ShortcutEditorModel::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid())
        return static_cast<ShortcutEditorModelItem*>(parent.internalPointer())->columnCount();

    return m_rootItem->columnCount();
}

Notez que nous ne prenons en charge que DisplayRole dans cette implémentation, et que nous renvoyons également des objets QVariant invalides pour les index de modèle invalides.

Nous utilisons la fonction flags() pour nous assurer que les vues savent que le modèle est en lecture seule :

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

    if (role != Qt::DisplayRole && role != Qt::EditRole)
        return QVariant();

    ShortcutEditorModelItem *item = static_cast<ShortcutEditorModelItem*>(index.internalPointer());
    return item->data(index.column());
}

La fonction headerData() renvoie les données que nous avons commodément stockées dans l'élément racine :

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

    Qt::ItemFlags modelFlags = QAbstractItemModel::flags(index);
    if (index.column() == static_cast<int>(Column::Shortcut))
        modelFlags |= Qt::ItemIsEditable;

    return modelFlags;
}

Cette information aurait pu être fournie d'une autre manière : soit spécifiée dans le constructeur, soit codée en dur dans la fonction headerData().

QVariant ShortcutEditorModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
        return m_rootItem->data(section);
    }

    return QVariant();
}

TODO

void ShortcutEditorModel::setupModelData(ShortcutEditorModelItem *parent)
{
    ActionsMap actionsMap;
    Application *application = static_cast<Application *>(QCoreApplication::instance());
    ActionManager *actionManager = application->actionManager();
    const QList<QAction *> registeredActions = actionManager->registeredActions();
    for (QAction *action : registeredActions) {
        QString context = actionManager->contextForAction(action);
        QString category = actionManager->categoryForAction(action);
        actionsMap[context][category].append(action);
    }

    QAction *nullAction = nullptr;
    const QString contextIdPrefix = "root";
    // Go through each context, one context - many categories each iteration
    for (const auto &contextLevel : actionsMap.keys()) {
        ShortcutEditorModelItem *contextLevelItem = new ShortcutEditorModelItem({contextLevel, QVariant::fromValue(nullAction)}, parent);
        parent->appendChild(contextLevelItem);

        // Go through each category, one category - many actions each iteration
        for (const auto &categoryLevel : actionsMap[contextLevel].keys()) {
            ShortcutEditorModelItem *categoryLevelItem = new ShortcutEditorModelItem({categoryLevel, QVariant::fromValue(nullAction)}, contextLevelItem);
            contextLevelItem->appendChild(categoryLevelItem);
            for (QAction *action : actionsMap[contextLevel][categoryLevel]) {
                QString name = action->text();
                if (name.isEmpty() || !action)
                    continue;

                ShortcutEditorModelItem *actionLevelItem = new ShortcutEditorModelItem({name, QVariant::fromValue(reinterpret_cast<void *>(action))}, categoryLevelItem);
                categoryLevelItem->appendChild(actionLevelItem);
            }
        }
    }
}

TODO

bool ShortcutEditorModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole && index.column() == static_cast<int>(Column::Shortcut)) {
        QString keySequenceString = value.toString();
        ShortcutEditorModelItem *item = static_cast<ShortcutEditorModelItem *>(index.internalPointer());
        QAction *itemAction = item->action();
        if (itemAction) {
            if (keySequenceString == itemAction->shortcut().toString(QKeySequence::NativeText))
                return true;
            itemAction->setShortcut(keySequenceString);
        }
        Q_EMIT dataChanged(index, index);

        if (keySequenceString.isEmpty())
            return true;
    }

    return QAbstractItemModel::setData(index, value, role);
}

TODO

Mise en place des données dans le modèle

Nous utilisons la fonction setupModelData() pour configurer les données initiales du modèle. Cette fonction récupère le texte des actions enregistrées et crée des objets item qui enregistrent à la fois les données et la structure globale du modèle. Naturellement, cette fonction fonctionne d'une manière très spécifique à ce modèle. Nous décrivons ci-après son comportement et renvoyons le lecteur au code de l'exemple pour plus d'informations.

Pour que le modèle fonctionne correctement, il suffit de créer des instances de ShortcutEditorModelItem avec les données et l'élément parent corrects.

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.