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

Qt의 모델/뷰 아키텍처는 데이터의 추상 모델을 사용하여 데이터 소스 내 정보를 조작하는 표준화된 방식을 제공함으로써, 데이터에 접근하는 방식을 단순화하고 표준화합니다. 바로 가기 편집기 모델은 동작을 항목의 트리 구조로 표현하며, 뷰가 인덱스 기반 시스템을 통해 이 데이터에 접근할 수 있도록 합니다. 보다 일반적으로, 모델은 각 항목이 자식 항목 테이블의 부모 역할을 하도록 허용함으로써 데이터를 트리 구조의 형태로 표현하는 데 사용될 수 있습니다.
설계 및 개념
데이터의 구조를 표현하는 데 사용하는 데이터 구조는 ShortcutEditorModelItem 객체로 구성된 트리 형태를 띱니다. 각 ShortcutEditorModelItem은 트리 뷰의 한 항목을 나타내며, 두 개의 데이터 열을 포함합니다.
| 단축키 편집기 구조 데이터는 포인터 기반 트리 구조로 서로 연결된 ShortcutEditorModelItem 객체를 사용하여 모델 내부에 저장됩니다. 일반적으로 각 ShortcutEditorModelItem은 부모 항목을 가지며, 여러 개의 자식 항목을 가질 수 있습니다. 그러나 트리 구조의 루트 항목은 부모 항목이 없으며, 모델 외부에서는 절대 참조되지 않습니다. 각 ShortcutEditorModelItem은 트리 구조 내에서의 위치에 대한 정보를 포함하며, 부모 항목과 행 번호를 반환할 수 있습니다. 이러한 정보를 즉시 활용할 수 있기 때문에 모델 구현이 더 쉬워집니다. 트리 뷰의 각 항목은 대개 여러 열의 데이터(이 예제에서는 이름과 단축키)를 포함하므로, 이 정보를 각 항목에 저장하는 것이 자연스럽습니다. 단순화를 위해, 항목의 각 열에 대한 데이터를 저장하는 데 ` QVariant ` 객체 목록을 사용할 것입니다. |
포인터 기반 트리 구조를 사용하면, 뷰에 모델 인덱스를 전달할 때 해당 인덱스에 있는 항목의 주소를 기록해 둘 수 있으며( QAbstractItemModel::createIndex() 참조), 나중에 QModelIndex::internalPointer()를 통해 이를 다시 가져올 수 있습니다. 이렇게 하면 모델 작성이 더 쉬워지고, 동일한 항목을 참조하는 모든 모델 인덱스가 동일한 내부 데이터 포인터를 가지게 됩니다.
적절한 데이터 구조가 마련되면, 다른 컴포넌트에 모델 인덱스와 데이터를 제공하기 위해 최소한의 추가 코드만으로 트리 모델을 생성할 수 있습니다.
ShortcutEditorModelItem 클래스 정의
ShortcutEditorModelItem 클래스는 다음과 같이 정의됩니다:
이 클래스는 기본적인 C++ 클래스입니다. ` QObject `을 상속하지 않으며 시그널과 슬롯도 제공하지 않습니다. 이 클래스는 열 데이터를 포함하는 `QVariant` 목록과 트리 구조 내에서의 위치 정보를 보관하는 데 사용됩니다. 이 클래스의 함수는 다음과 같은 기능을 제공합니다:
appendChildItem()은 모델이 처음 생성될 때 데이터를 추가하는 데 사용되며, 일반적인 사용 중에는 사용되지 않습니다.child()및childCount()함수를 사용하면 모델이 모든 자식 항목에 대한 정보를 얻을 수 있습니다.columnCount()는 항목과 관련된 열 수에 대한 정보를 제공하며, data() 함수를 사용하면 각 열의 데이터를 얻을 수 있습니다.row()및parent()함수는 항목의 행 번호와 상위 항목을 가져오는 데 사용됩니다.
부모 항목과 열 데이터는 parentItem 및 itemData 비공개 멤버 변수에 저장됩니다. childItems 변수에는 해당 항목의 자식 항목들에 대한 포인터 목록이 포함됩니다.
ShortcutEditorModel 클래스 정의
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();
}이 함수의 행(row) 및 열(column) 인수는 해당 부모 항목의 자식 항목을 가리키므로, ` 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 인스턴스를 생성하기만 하면 됩니다.
© 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.