바로가기 에디터 예제

바로가기 에디터 예제는 Qt의 표준 뷰 및 QKeySequenceEdit 클래스와 함께 사용할 기본 읽기-쓰기 계층적 모델을 만드는 방법을 보여줍니다. 모델/뷰 프로그래밍에 대한 설명은 모델/뷰 프로그래밍 개요를 참조하십시오.

Qt의 모델/뷰 아키텍처는 뷰가 데이터 소스의 정보를 조작하는 표준 방법을 제공하며, 데이터의 추상 모델을 사용하여 액세스 방식을 단순화 및 표준화합니다. 바로 가기 편집기 모델은 작업을 항목의 트리로 나타내며, 뷰가 인덱스 기반 시스템을 통해 이 데이터에 액세스할 수 있도록 합니다. 보다 일반적으로 모델은 각 항목이 하위 항목 테이블의 부모 역할을 하도록 하여 트리 구조의 형태로 데이터를 표현하는 데 사용할 수 있습니다.

디자인 및 개념

데이터 구조를 표현하는 데 사용하는 데이터 구조는 ShortcutEditorModelItem 객체로 구축된 트리의 형태를 취합니다. 각 ShortcutEditorModelItem은 트리 보기의 항목을 나타내며, 두 개의 데이터 열을 포함합니다.

바로가기 편집기 구조

데이터는 포인터 기반 트리 구조로 서로 연결된 ShortcutEditorModelItem 객체를 사용하여 모델 내부에 저장됩니다. 일반적으로 각 ShortcutEditorModelItem에는 부모 항목이 있으며, 여러 개의 하위 항목을 가질 수 있습니다. 그러나 트리 구조의 루트 항목에는 부모 항목이 없으며 모델 외부에서 참조되지 않습니다.

각 쇼트커트 에디터 모델 항목은 트리 구조에서 자신의 위치에 대한 정보를 포함하며, 부모 항목과 행 번호를 반환할 수 있습니다. 이 정보를 쉽게 사용할 수 있으면 모델을 더 쉽게 구현할 수 있습니다.

트리 보기의 각 항목에는 일반적으로 여러 열의 데이터(이 예에서는 이름과 바로 가기)가 포함되므로 각 항목에 이 정보를 저장하는 것이 자연스럽습니다. 간단하게 하기 위해 QVariant 개체 목록을 사용하여 항목의 각 열에 대한 데이터를 저장하겠습니다.

포인터 기반 트리 구조를 사용하면 모델 인덱스를 뷰에 전달할 때 해당 항목의 주소를 인덱스에 기록하고( QAbstractItemModel::createIndex() 참조) 나중에 QModelIndex::internalPointer()로 검색할 수 있습니다. 이렇게 하면 모델을 더 쉽게 작성할 수 있고 동일한 항목을 참조하는 모든 모델 인덱스가 동일한 내부 데이터 포인터를 갖도록 보장할 수 있습니다.

적절한 데이터 구조를 갖추면 최소한의 추가 코드로 트리 모델을 생성하여 다른 컴포넌트에 모델 인덱스와 데이터를 제공할 수 있습니다.

ShortcutEditorModelItem 클래스 정의

ShortcutEditorModelItem 클래스는 다음과 같이 정의됩니다:

이 클래스는 기본 C++ 클래스입니다. QObject 에서 상속하거나 신호 및 슬롯을 제공하지 않습니다. 이 클래스는 열 데이터와 트리 구조에서 해당 위치에 대한 정보를 포함하는 QVariant 목록을 보유하는 데 사용됩니다. 이 함수는 다음과 같은 기능을 제공합니다:

  • appendChildItem() 함수는 모델을 처음 구성할 때 데이터를 추가하는 데 사용되며 일반적인 사용 중에는 사용되지 않습니다.
  • child()childCount() 함수를 사용하면 모델에서 하위 항목에 대한 정보를 얻을 수 있습니다.
  • 항목과 관련된 열 수에 대한 정보는 columnCount() 에서 제공하며, 각 열의 데이터는 data() 함수를 사용하여 얻을 수 있습니다.
  • row()parent() 함수는 항목의 행 번호와 상위 항목을 가져오는 데 사용됩니다.

상위 항목과 열 데이터는 parentItemitemData 비공개 멤버 변수에 저장됩니다. childItems 변수에는 항목의 하위 항목에 대한 포인터 목록이 포함되어 있습니다.

단축 편집기 모델 클래스 정의

ShortcutEditorModel 클래스는 다음과 같이 정의됩니다:

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

이 클래스는 읽기-쓰기 모델을 제공하는 QAbstractItemModel 의 다른 대부분의 서브클래스와 유사합니다. 생성자의 형식과 setupModelData() 함수만 이 모델에 고유합니다. 또한 모델이 소멸될 때 정리를 위한 소멸자를 제공합니다.

ShortcutEditorModel 클래스 구현

생성자는 모델이 뷰 및 델리게이트와 공유할 데이터가 포함된 인수를 받습니다:

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

모델의 루트 항목을 생성하는 것은 생성자의 몫입니다. 이 항목에는 편의를 위해 세로 헤더 데이터만 포함됩니다. 또한 모델 데이터를 포함하는 내부 데이터 구조를 참조하는 데 사용되며, 모델에서 최상위 항목의 가상의 부모를 나타내는 데 사용됩니다.

모델의 내부 데이터 구조는 setupModelData() 함수에 의해 항목으로 채워집니다. 이 함수는 이 문서의 마지막 부분에서 별도로 살펴보겠습니다.

소멸자는 모델이 소멸될 때 루트 항목과 그 하위 항목이 모두 삭제되도록 합니다:

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

모델이 생성 및 설정된 후에는 모델에 데이터를 추가할 수 없으므로 항목의 내부 트리가 관리되는 방식이 간소화됩니다.

모델은 index() 함수를 구현하여 뷰 및 위임자가 데이터에 액세스할 때 사용할 인덱스를 제공해야 합니다. 인덱스는 행 및 열 번호와 상위 모델 인덱스로 참조될 때 다른 컴포넌트에 대해 생성됩니다. 잘못된 모델 인덱스가 부모로 지정된 경우 모델의 최상위 항목에 해당하는 인덱스를 반환하는 것은 모델의 몫입니다.

모델 인덱스가 제공되면 먼저 유효한지 여부를 확인합니다. 그렇지 않은 경우 최상위 항목이 참조되고 있다고 가정하고, 그렇지 않은 경우 internalPointer() 함수를 사용하여 모델 인덱스에서 데이터 포인터를 가져와 TreeItem 객체를 참조하는 데 사용합니다. 우리가 구성하는 모든 모델 인덱스에는 기존 TreeItem 에 대한 포인터가 포함되므로 수신하는 모든 유효한 모델 인덱스에는 유효한 데이터 포인터가 포함된다는 것을 보장할 수 있습니다.

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

이 함수의 행과 열 인수는 해당 상위 항목의 하위 항목을 참조하므로 TreeItem::child() 함수를 사용하여 항목을 가져옵니다. createIndex () 함수는 반환할 모델 인덱스를 생성하는 데 사용됩니다. 행과 열 번호와 항목 자체에 대한 포인터를 지정합니다. 모델 인덱스는 나중에 항목의 데이터를 가져오는 데 사용할 수 있습니다.

TreeItem 객체가 정의되는 방식 덕분에 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();
}

루트 항목에 해당하는 모델 인덱스를 반환하지 않도록 하기만 하면 됩니다. index() 함수가 구현되는 방식과 일관성을 유지하기 위해 모델에서 최상위 항목의 부모에 대해 잘못된 모델 인덱스를 반환합니다.

반환할 모델 인덱스를 만들 때는 해당 상위 항목의 행 번호와 열 번호를 지정해야 합니다. 행 번호는 TreeItem::row() 함수를 사용하여 쉽게 찾을 수 있지만, 상위 항목의 열 번호는 0을 지정하는 규칙을 따릅니다. 모델 인덱스는 index() 함수와 동일한 방식으로 createIndex()로 생성됩니다.

rowCount() 함수는 지정된 모델 인덱스에 해당하는 TreeItem 의 하위 항목 수 또는 잘못된 인덱스가 지정된 경우 최상위 항목 수를 반환합니다:

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

각 항목은 자체 열 데이터를 관리하므로 columnCount() 함수는 해당 항목의 자체 columnCount() 함수를 호출하여 주어진 모델 인덱스에 존재하는 열의 수를 확인해야 합니다. rowCount() 함수와 마찬가지로 유효하지 않은 모델 인덱스가 지정되면 반환되는 열 수는 루트 항목에서 결정됩니다:

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

data() 을 통해 모델에서 데이터를 가져옵니다. 항목이 자체 열을 관리하므로 TreeItem::data() 함수로 데이터를 검색하려면 열 번호를 사용해야 합니다:

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

    return m_rootItem->columnCount();
}

이 구현에서는 DisplayRole 만 지원하며, 잘못된 모델 인덱스에 대해서는 잘못된 QVariant 객체도 반환한다는 점에 유의하세요.

flags() 함수를 사용하여 뷰가 모델이 읽기 전용임을 알 수 있도록 합니다:

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

headerData() 함수는 루트 항목에 편리하게 저장한 데이터를 반환합니다:

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

이 정보는 생성자에 지정하거나 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

모델에서 데이터 설정하기

setupModelData() 함수를 사용하여 모델의 초기 데이터를 설정합니다. 이 함수는 등록된 작업 텍스트를 검색하고 데이터와 전체 모델 구조를 모두 기록하는 항목 개체를 만듭니다. 당연히 이 함수는 이 모델에 매우 특정한 방식으로 작동합니다. 동작에 대한 설명은 다음과 같으며 자세한 내용은 예제 코드 자체를 참조하시기 바랍니다.

모델이 올바르게 작동하도록 하려면 올바른 데이터와 상위 항목으로 ShortcutEditorModelItem의 인스턴스를 생성하기만 하면 됩니다.

예제 프로젝트 @ code.qt.io

© 2025 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.