「Qt Widget中文示例指南」如何实现一个快捷编辑器(二)

Qt 是目前最先进、最完整的跨平台C++开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。

快捷编辑器示例展示了如何创建一个基本的读写层次模型,来与Qt的标准视图和QKeySequenceEdit类一起使用。

点击获取Qt Widget组件下载(Q技术交流:166830288)

「Qt Widget中文示例指南」如何实现一个快捷编辑器

Qt的模型/视图架构为视图提供了一种标准的方式来操作数据源中的信息,使用数据的抽象模型来简化和标准化访问数据的方式。快捷编辑器模型将操作表示为项目树,并允许视图通过基于索引的系统访问此数据。更一般地说,可以使用模型以树结构的形式表示数据,方法是允许每个项作为子项表的父项。

在上文中(点击这里回顾>>),我们为大家介绍了快捷编辑器的设计理念及结构等,本文将继续介绍一些具体的实现类。

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作为父列号的约定。模型索引是用createIndex()创建的,方法与index()函数相同。

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实例。

Qt Widget组件推荐
  • QtitanRibbon - Ribbon UI组件:是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件,QtitanRibbon致力于为Windows、Linux和Mac OS X提供功能完整的Ribbon组件。
  • QtitanChart - Qt类图表组件:是一个C ++库,代表一组控件,这些控件使您可以快速地为应用程序提供漂亮而丰富的图表。
  • QtitanDataGrid - Qt网格组件:提供了一套完整的标准 QTableView 函数和传统组件无法实现的独特功能。使您能够将不同来源的各类数据加载到一个快速、灵活且功能强大的可编辑网格中,支持排序、分组、报告、创建带状列、拖放按钮和许多其他方便的功能。
  • QtitanDocking:允许您像 Visual Studio 一样为您的伟大应用程序配备可停靠面板和可停靠工具栏。黑色、白色、蓝色调色板完全支持 Visual Studio 2019 主题!
  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现一个代码编辑器,需要了解以下几个方面: 1. 用户界面:使用图形界面库(如GTK、Qt、wxWidgets等)来创建用户界面,可以包括菜单、工具栏、编辑窗口、状态栏等。 2. 文本编辑器实现文本编辑器的功能,包括插入、删除、移动光标、选择文本等。 3. 语法高亮:实现代码的语法高亮,可以使用正则表达式或解析器来实现。 4. 自动完成:实现代码的自动完成功能,可以使用Trie树等数据结构来实现。 5. 文件操作:实现文件的打开、保存、另存为等文件操作功能。 下面是一个简单的C语言代码编辑器实现示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <gtk/gtk.h> GtkWidget *text_view; // 文本编辑框 GtkWidget *window; // 主窗口 // 打开文件 void open_file(GtkWidget *widget, gpointer data) { GtkWidget *dialog; GtkTextBuffer *buffer; dialog = gtk_file_chooser_dialog_new("Open File", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { char *filename; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); FILE *fp; fp = fopen(filename, "r"); if (fp != NULL) { char buf[1024]; buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); while (fgets(buf, sizeof(buf), fp) != NULL) { gtk_text_buffer_insert_at_cursor(buffer, buf, -1); } fclose(fp); } g_free(filename); } gtk_widget_destroy(dialog); } // 保存文件 void save_file(GtkWidget *widget, gpointer data) { GtkWidget *dialog; GtkTextBuffer *buffer; dialog = gtk_file_chooser_dialog_new("Save File", GTK_WINDOW(window), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { char *filename; filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); GtkTextIter start, end; gtk_text_buffer_get_start_iter(buffer, &start); gtk_text_buffer_get_end_iter(buffer, &end); char *text; text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); FILE *fp; fp = fopen(filename, "w"); if (fp != NULL) { fputs(text, fp); fclose(fp); } g_free(filename); g_free(text); } gtk_widget_destroy(dialog); } // 创建主窗口 void create_window() { GtkWidget *vbox; GtkWidget *menubar; GtkWidget *filemenu; GtkWidget *file; GtkWidget *open; GtkWidget *save; GtkWidget *separator; window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "Code Editor"); gtk_window_set_default_size(GTK_WINDOW(window), 640, 480); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox); menubar = gtk_menu_bar_new(); filemenu = gtk_menu_new(); file = gtk_menu_item_new_with_label("File"); gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), filemenu); gtk_menu_shell_append(GTK_MENU_SHELL(menubar), file); open = gtk_menu_item_new_with_label("Open"); gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), open); g_signal_connect(G_OBJECT(open), "activate", G_CALLBACK(open_file), NULL); save = gtk_menu_item_new_with_label("Save"); gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), save); g_signal_connect(G_OBJECT(save), "activate", G_CALLBACK(save_file), NULL); separator = gtk_separator_menu_item_new(); gtk_menu_shell_append(GTK_MENU_SHELL(filemenu), separator); gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); text_view = gtk_text_view_new(); gtk_box_pack_start(GTK_BOX(vbox), text_view, TRUE, TRUE, 0); gtk_widget_show_all(window); } int main(int argc, char *argv[]) { gtk_init(&argc, &argv); create_window(); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_main(); return 0; } ``` 上述示例代码使用GTK+库创建了一个简单的C语言代码编辑器,可以实现打开、保存等基本功能,但仅供参考,还需要根据实际需求进行修改和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值