Auf dieser Seite

Beispiel für den Shortcut-Editor

Das Beispiel „Shortcut-Editor“ zeigt, wie man ein einfaches, hierarchisches Lese-/Schreibmodell erstellt, das mit den Standardklassen „View“ und „ QKeySequenceEdit “ von Qt verwendet werden kann. Eine Beschreibung der Modell-View-Programmierung finden Sie in der Übersicht zur Modell-View-Programmierung.

Liste der Aktionen und der ihnen zugewiesenen Tastenkombinationen

Die Model/View-Architektur von Qt bietet eine Standardmethode, mit der Ansichten Informationen in einer Datenquelle bearbeiten können, wobei ein abstraktes Modell der Daten verwendet wird, um den Zugriff darauf zu vereinfachen und zu standardisieren. Das Modell des Shortcut-Editors stellt die Aktionen als Baumstruktur dar und ermöglicht es Ansichten, über ein indexbasiertes System auf diese Daten zuzugreifen. Allgemeiner gesagt können Modelle dazu verwendet werden, Daten in Form einer Baumstruktur darzustellen, indem jedes Element als übergeordnetes Element einer Tabelle mit untergeordneten Elementen fungiert.

Entwurf und Konzepte

Die Datenstruktur, die wir zur Darstellung der Datenstruktur verwenden, hat die Form eines Baums, der aus „ShortcutEditorModelItem“-Objekten aufgebaut ist. Jedes „ShortcutEditorModelItem“ repräsentiert ein Element in einer Baumansicht und enthält zwei Datenspalten.

Aufbau des Shortcut-Modells in ZeilenStruktur des Shortcut-Editors

Die Daten werden intern im Modell mithilfe von „ShortcutEditorModelItem“-Objekten gespeichert, die in einer zeigerbasierten Baumstruktur miteinander verknüpft sind. Im Allgemeinen hat jedes „ShortcutEditorModelItem“ ein übergeordnetes Element und kann eine beliebige Anzahl von untergeordneten Elementen haben. Das Stammelement der Baumstruktur hat jedoch kein übergeordnetes Element und wird außerhalb des Modells niemals referenziert.

Jedes „ShortcutEditorModelItem“ enthält Informationen über seine Position in der Baumstruktur; es kann sein übergeordnetes Element und seine Zeilennummer zurückgeben. Die sofortige Verfügbarkeit dieser Informationen erleichtert die Implementierung des Modells.

Da jedes Element in einer Baumansicht in der Regel mehrere Spalten mit Daten enthält (in diesem Beispiel einen Namen und eine Tastenkombination), ist es naheliegend, diese Informationen in jedem Element zu speichern. Der Einfachheit halber verwenden wir eine Liste von „ QVariant “-Objekten, um die Daten für jede Spalte im Element zu speichern.

Die Verwendung einer zeigerbasierten Baumstruktur bedeutet, dass wir bei der Übergabe eines Modellindexes an eine Ansicht die Adresse des entsprechenden Elements im Index speichern können (siehe ` QAbstractItemModel::createIndex()`) und diese später mit ` QModelIndex::internalPointer()` wieder abrufen können. Dies vereinfacht das Schreiben des Modells und stellt sicher, dass alle Modellindizes, die auf dasselbe Element verweisen, denselben internen Datenzeiger haben.

Mit der entsprechenden Datenstruktur können wir ein Baummodell mit einem minimalen Aufwand an zusätzlichem Code erstellen, um anderen Komponenten Modellindizes und Daten zur Verfügung zu stellen.

Klassendefinition von `ShortcutEditorModelItem`

Die Klasse „ShortcutEditorModelItem“ ist wie folgt definiert:

Die Klasse ist eine einfache C++-Klasse. Sie erbt nicht von „ QObject “ und stellt keine Signale und Slots bereit. Sie dient dazu, eine Liste von „QVariants“ zu speichern, die Spaltendaten sowie Informationen über deren Position in der Baumstruktur enthalten. Die Funktionen bieten folgende Funktionen:

  • Die Funktion „ appendChildItem() “ dient zum Hinzufügen von Daten beim erstmaligen Aufbau des Modells und wird im normalen Betrieb nicht verwendet.
  • Die Funktionen „ child() “ und „ childCount() “ ermöglichen es dem Modell, Informationen über beliebige untergeordnete Elemente abzurufen.
  • Informationen über die Anzahl der mit dem Element verbundenen Spalten liefert die Funktion ` columnCount()`, und die Daten in jeder Spalte können mit der Funktion `data()` abgerufen werden.
  • Die Funktionen „ row() “ und „ parent() “ dienen dazu, die Zeilennummer und das übergeordnete Element des Elements abzurufen.

Das übergeordnete Element und die Spaltendaten werden in den privaten Membervariablen „ parentItem “ und „ itemData “ gespeichert. Die Variable „ childItems “ enthält eine Liste von Zeigern auf die eigenen untergeordneten Elemente des Elements.

Klassendefinition von „ShortcutEditorModel“

Die Klasse „ ShortcutEditorModel “ ist wie folgt definiert:

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

Diese Klasse ähnelt den meisten anderen Unterklassen von ` QAbstractItemModel `, die Lese-Schreib-Modelle bereitstellen. Lediglich die Form des Konstruktors und die Funktion ` setupModelData() ` sind spezifisch für dieses Modell. Zusätzlich stellen wir einen Destruktor bereit, der bei der Zerstörung des Modells für die Bereinigung sorgt.

Implementierung der Klasse „ShortcutEditorModel“

Der Konstruktor nimmt ein Argument entgegen, das die Daten enthält, die das Modell mit Ansichten und Delegaten teilt:

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

Es ist Aufgabe des Konstruktors, ein Stammelement für das Modell zu erstellen. Dieses Element enthält der Einfachheit halber nur vertikale Kopfzeilendaten. Wir verwenden es außerdem, um auf die interne Datenstruktur zu verweisen, die die Modelldaten enthält, und es dient dazu, einen imaginären übergeordneten Eintrag der Elemente der obersten Ebene im Modell darzustellen.

Die interne Datenstruktur des Modells wird durch die Funktion „ setupModelData() “ mit Elementen gefüllt. Wir werden diese Funktion am Ende dieses Dokuments separat untersuchen.

Der Destruktor stellt sicher, dass das Stammelement und alle seine Nachkommen gelöscht werden, wenn das Modell zerstört wird:

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

Da wir dem Modell nach seiner Erstellung und Einrichtung keine Daten mehr hinzufügen können, vereinfacht dies die Verwaltung des internen Elementbaums.

Modelle müssen eine ` index() `-Funktion implementieren, um Indizes bereitzustellen, die von Ansichten und Delegaten beim Zugriff auf Daten verwendet werden können. Indizes werden für andere Komponenten erstellt, wenn diese über ihre Zeilen- und Spaltennummern sowie den Index ihres übergeordneten Modells referenziert werden. Wird ein ungültiger Modellindex als übergeordnetes Element angegeben, obliegt es dem Modell, einen Index zurückzugeben, der einem Element der obersten Ebene im Modell entspricht.

Wird ein Modellindex übergeben, prüfen wir zunächst, ob dieser gültig ist. Ist dies nicht der Fall, gehen wir davon aus, dass auf ein Element der obersten Ebene verwiesen wird; andernfalls ermitteln wir den Datenzeiger aus dem Modellindex mithilfe seiner Funktion ` internalPointer()` und verwenden ihn, um auf ein ` TreeItem `-Objekt zu verweisen. Beachten Sie, dass alle von uns erstellten Modellindizes einen Zeiger auf ein vorhandenes ` TreeItem` enthalten, sodass wir garantieren können, dass alle gültigen Modellindizes, die wir erhalten, einen gültigen Datenzeiger enthalten.

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

Da sich die Argumente „row“ und „column“ dieser Funktion auf ein untergeordnetes Element des entsprechenden übergeordneten Elements beziehen, rufen wir das Element mithilfe der Funktion „ TreeItem::child() “ ab. Die Funktion „ createIndex()“ wird verwendet, um einen Modellindex zu erstellen, der zurückgegeben werden soll. Wir geben die Zeilen- und Spaltennummern sowie einen Zeiger auf das Element selbst an. Der Modellindex kann später verwendet werden, um die Daten des Elements abzurufen.

Die Art und Weise, wie die „ TreeItem “-Objekte definiert sind, erleichtert das Schreiben der Funktion „ 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();
}

Wir müssen lediglich sicherstellen, dass wir niemals einen Modellindex zurückgeben, der dem Stammelement entspricht. Um mit der Implementierung der Funktion „ index() “ konsistent zu bleiben, geben wir für das übergeordnete Element aller Elemente der obersten Ebene im Modell einen ungültigen Modellindex zurück.

Beim Erstellen eines zurückzugebenden Modellindexes müssen wir die Zeilen- und Spaltennummern des übergeordneten Elements innerhalb seines eigenen übergeordneten Elements angeben. Die Zeilennummer lässt sich leicht mit der Funktion ` TreeItem::row() ` ermitteln, doch wir folgen der Konvention, als Spaltennummer des übergeordneten Elements den Wert 0 anzugeben. Der Modellindex wird mit ` createIndex()` auf dieselbe Weise erstellt wie in der Funktion ` index() `.

Die Funktion ` rowCount() ` gibt einfach die Anzahl der untergeordneten Elemente für den ` TreeItem ` zurück, der einem bestimmten Modellindex entspricht, oder die Anzahl der Elemente der obersten Ebene, wenn ein ungültiger Index angegeben wird:

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

Da jedes Element seine eigenen Spaltendaten verwaltet, muss die Funktion ` columnCount() ` die elementeigene Funktion ` columnCount() ` aufrufen, um zu ermitteln, wie viele Spalten für einen bestimmten Modellindex vorhanden sind. Wie bei der Funktion ` rowCount() ` wird bei Angabe eines ungültigen Modellindexes die Anzahl der zurückgegebenen Spalten anhand des Stammelements ermittelt:

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

Die Daten werden über ` data()` aus dem Modell abgerufen. Da das Element seine Spalten selbst verwaltet, müssen wir die Spaltennummer verwenden, um die Daten mit der Funktion ` TreeItem::data() ` abzurufen:

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

    return m_rootItem->columnCount();
}

Beachten Sie, dass wir in dieser Implementierung nur die Funktion „ DisplayRole “ unterstützen und bei ungültigen Modellindizes ebenfalls ungültige „ QVariant “-Objekte zurückgeben.

Wir verwenden die Funktion „ flags() “, um sicherzustellen, dass die Ansichten erkennen, dass das Modell schreibgeschützt ist:

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

Die Funktion ` headerData() ` gibt Daten zurück, die wir praktischerweise im Stammelement gespeichert haben:

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

Diese Informationen hätten auch auf andere Weise bereitgestellt werden können: entweder im Konstruktor angegeben oder fest in die Funktion ` headerData() ` eingeprogrammiert.

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

Einrichten der Daten im Modell

Wir verwenden die Funktion „ setupModelData() “, um die Anfangsdaten im Modell einzurichten. Diese Funktion ruft den Text der registrierten Aktionen ab und erstellt Elementobjekte, die sowohl die Daten als auch die gesamte Modellstruktur erfassen. Natürlich funktioniert diese Funktion auf eine Weise, die sehr spezifisch für dieses Modell ist. Wir geben im Folgenden eine Beschreibung ihres Verhaltens und verweisen den Leser für weitere Informationen auf den Beispielcode selbst.

Um sicherzustellen, dass das Modell korrekt funktioniert, müssen lediglich Instanzen von `ShortcutEditorModelItem` mit den richtigen Daten und dem richtigen übergeordneten Element erstellt werden.

Beispielprojekt @ 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.