En esta página

Ejemplo de editor de accesos directos

El ejemplo del Editor de accesos directos muestra cómo crear un modelo jerárquico básico de lectura-escritura para utilizarlo con las clases de vista estándar de Qt y QKeySequenceEdit. Para una descripción de la Programación Modelo/Vista, vea la descripción general de la Programación Modelo/Vista.

Lista de acciones y sus atajos de teclado asignados

La arquitectura modelo/vista de Qt proporciona una forma estándar para que las vistas manipulen la información en una fuente de datos, utilizando un modelo abstracto de los datos para simplificar y estandarizar la forma en que se accede a ellos. El modelo del editor de accesos directos representa las acciones como un árbol de elementos, y permite a las vistas acceder a estos datos mediante un sistema basado en índices. En términos más generales, los modelos pueden utilizarse para representar datos en forma de estructura de árbol, permitiendo que cada elemento actúe como padre de una tabla de elementos hijos.

Diseño y conceptos

La estructura de datos que utilizamos para representar la estructura de los datos adopta la forma de un árbol construido a partir de objetos ShortcutEditorModelItem. Cada ShortcutEditorModelItem representa un elemento en una vista de árbol, y contiene dos columnas de datos.

Estructura del modelo de acceso directo en filasEstructura del editor de accesos directos

Los datos se almacenan internamente en el modelo mediante objetos ShortcutEditorModelItem que están vinculados entre sí en una estructura de árbol basada en punteros. Generalmente, cada ShortcutEditorModelItem tiene un elemento padre, y puede tener un número de elementos hijo. Sin embargo, el elemento raíz de la estructura de árbol no tiene ningún elemento padre y nunca se hace referencia a él fuera del modelo.

Cada ShortcutEditorModelItem contiene información sobre su lugar en la estructura de árbol; puede devolver su elemento padre y su número de fila. Disponer de esta información facilita la implementación del modelo.

Dado que cada elemento de una vista en árbol suele contener varias columnas de datos (un nombre y un acceso directo en este ejemplo), es natural almacenar esta información en cada elemento. Para simplificar, utilizaremos una lista de objetos QVariant para almacenar los datos de cada columna del elemento.

El uso de una estructura de árbol basada en punteros significa que, al pasar un índice del modelo a una vista, podemos registrar la dirección del elemento correspondiente en el índice (véase QAbstractItemModel::createIndex()) y recuperarla más tarde con QModelIndex::internalPointer(). Esto facilita la escritura del modelo y garantiza que todos los índices del modelo que hagan referencia al mismo elemento tengan el mismo puntero de datos interno.

Con la estructura de datos adecuada en su lugar, podemos crear un modelo de árbol con una cantidad mínima de código adicional para suministrar índices de modelo y datos a otros componentes.

Definición de la Clase ShortcutEditorModelItem

La clase ShortcutEditorModelItem se define como sigue:

La clase es una clase C++ básica. No hereda de QObject ni proporciona señales y ranuras. Se utiliza para mantener una lista de QVariants, que contiene datos de columna, e información sobre su posición en la estructura de árbol. Las funciones proporcionan las siguientes características:

  • La función appendChildItem() se utiliza para añadir datos cuando se construye el modelo por primera vez y no se utiliza durante el uso normal.
  • Las funciones child() y childCount() permiten al modelo obtener información sobre cualquier elemento hijo.
  • La información sobre el número de columnas asociadas al elemento se proporciona mediante columnCount(), y los datos de cada columna pueden obtenerse con la función data().
  • Las funciones row() y parent() se utilizan para obtener el número de fila del elemento y el elemento padre.

Los datos del elemento padre y de la columna se almacenan en las variables privadas parentItem y itemData. La variable childItems contiene una lista de punteros a los elementos hijos del propio elemento.

Definición de la clase ShortcutEditorModel

La clase ShortcutEditorModel se define como sigue:

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;
};

Esta clase es similar a la mayoría de las otras subclases de QAbstractItemModel que proporcionan modelos de lectura-escritura. Sólo la forma del constructor y la función setupModelData() son específicas de este modelo. Además, proporcionamos un destructor para limpiar cuando se destruye el modelo.

Implementación de la clase ShortcutEditorModel

El constructor toma un argumento que contiene los datos que el modelo compartirá con las vistas y los delegados:

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

Depende del constructor crear un elemento raíz para el modelo. Este elemento sólo contiene datos de cabecera vertical por conveniencia. También lo utilizamos para hacer referencia a la estructura de datos interna que contiene los datos del modelo, y se utiliza para representar un padre imaginario de los elementos de nivel superior en el modelo.

La estructura de datos interna del modelo se rellena con elementos mediante la función setupModelData(). Examinaremos esta función por separado al final de este documento.

El destructor asegura que el elemento raíz y todos sus descendientes se borran cuando se destruye el modelo:

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

Dado que no podemos añadir datos al modelo una vez construido y configurado, esto simplifica la forma en que se gestiona el árbol interno de elementos.

Los modelos deben implementar una función index() para proporcionar índices que las vistas y los delegados puedan utilizar al acceder a los datos. Los índices se crean para otros componentes cuando son referenciados por sus números de fila y columna, y su índice de modelo padre. Si se especifica un índice de modelo no válido como padre, es el modelo el que debe devolver un índice que corresponda a un elemento de nivel superior en el modelo.

Cuando se proporciona un índice de modelo, primero se comprueba si es válido. Si no lo es, asumimos que se está haciendo referencia a un elemento de nivel superior; en caso contrario, obtenemos el puntero de datos del índice del modelo con su función internalPointer() y lo utilizamos para hacer referencia a un objeto TreeItem. Nótese que todos los índices de modelo que construyamos contendrán un puntero a un TreeItem existente, por lo que podemos garantizar que cualquier índice de modelo válido que recibamos contendrá un puntero de datos válido.

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

Dado que los argumentos de fila y columna de esta función se refieren a un elemento hijo del elemento padre correspondiente, obtenemos el elemento utilizando la función TreeItem::child(). La función createIndex() se utiliza para crear un índice modelo que se devolverá. Especificamos los números de fila y columna, y un puntero al propio artículo. El índice del modelo puede utilizarse posteriormente para obtener los datos del elemento.

La forma en que se definen los objetos TreeItem facilita la escritura de la función 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();
}

Sólo tenemos que asegurarnos de que nunca devolvemos un índice de modelo correspondiente al elemento raíz. Para ser coherentes con la forma en que se implementa la función index(), devolvemos un índice de modelo no válido para el padre de cualquier elemento de nivel superior en el modelo.

Al crear un índice de modelo para devolver, debemos especificar los números de fila y columna del elemento padre dentro de su propio padre. Podemos descubrir fácilmente el número de fila con la función TreeItem::row(), pero seguimos la convención de especificar 0 como número de columna del elemento padre. El índice del modelo se crea con createIndex() del mismo modo que en la función index().

La función rowCount() simplemente devuelve el número de elementos hijo para el TreeItem que corresponde a un índice de modelo dado, o el número de elementos de nivel superior si se especifica un índice no válido:

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);
}

Dado que cada elemento gestiona sus propios datos de columna, la función columnCount() tiene que llamar a la función columnCount() del propio elemento para determinar cuántas columnas están presentes para un índice de modelo dado. Al igual que con la función rowCount(), si se especifica un índice de modelo no válido, el número de columnas devueltas se determina a partir del elemento raíz:

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();
}

Los datos se obtienen del modelo a través de data(). Dado que el elemento gestiona sus propias columnas, necesitamos utilizar el número de columna para recuperar los datos con la función TreeItem::data():

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

    return m_rootItem->columnCount();
}

Ten en cuenta que en esta implementación sólo admitimos DisplayRole, y que también devolvemos objetos QVariant no válidos para índices de modelo no válidos.

Utilizamos la función flags() para garantizar que las vistas sepan que el modelo es de sólo lectura:

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 función headerData() devuelve datos que hemos almacenado convenientemente en el elemento raíz:

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;
}

Esta información podría haber sido suministrada de otra manera: especificada en el constructor, o codificada en la función 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

Configuración de los datos del modelo

Usamos la función setupModelData() para configurar los datos iniciales en el modelo. Esta función recupera el texto de las acciones registradas y crea objetos item que registran tanto los datos como la estructura general del modelo. Naturalmente, esta función trabaja de una manera muy específica para este modelo. Proporcionamos la siguiente descripción de su comportamiento, y remitimos al lector al propio código de ejemplo para más información.

Para que el modelo funcione correctamente, sólo es necesario crear instancias de ShortcutEditorModelItem con los datos y el elemento padre correctos.

Proyecto de ejemplo @ 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.