QTableView制作属性编辑器

文章详细介绍了如何通过使用QTableView和自定义QItemDelegate来优化QT程序界面性能,解决了控件数量增加导致的构建速度缓慢问题。通过继承QItemDelegate并重写相关函数,实现了对大量参数进行高效设置的功能,显著减少了界面加载时间。
原文地址:http://blog.youkuaiyun.com/rolland1989/article/details/5714163
最近在用QT写的一个程序中,需要一个对大量参数进行设置的功能。过去用Delphi的经验是,尽量不要使用QListView和TGrid来实现,因为涉及到控件的对齐,界面的重绘等,反而使简单问题变得复杂。因此这次用QT写程序时也想当然,用控件数组的方式来解决。很快程序写好了,界面如下:

 

显示出的效果还不错,但是遇到了一个严重的问题,那就是速度。由于QT的机制是自动调整控件比例来适应各种系统和窗体大小,而这个调整的功能其实涉及到复杂的运算。另外,QT的控件创建也不如真正控件的创建那么快。因此当控件数量一大,构建整个界面就变得其慢无比了。在我的机器上,像上图这样的界面打开需要1-2秒时间,而且即使到了性能较好的计算机上也没有什么大的改善。这样的效果就连自己都无法忍受,更别说使用者了。

于是考虑改进,参考QT的example中的spinboxdelegate例子,使用QTableView来解决。具体方法是:

1)在控件设计器中放置一个QTableView,并调整好其与其它控件的位置关系。生成ui文件,在程序中制作相应的界面窗体类。

2)从 QItemDelegate中继承出一个新的类,它将用来配合上面的QTableView,用来告诉其是否可以针对某个Cell进入编辑模式,以及在编辑该Cell时,使用什么控件,相互的值如何传递等。
具体来说,只要继承QItemDelegate中以下的几个虚函数就可以了:

class enstCatheterNodeInfoDelegate : public QItemDelegate 
{
    Q_OBJECT
public:
    enstCatheterNodeInfoDelegate(QObject *parent, enstCatheterInfo *pCatheterInfo);
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                const QModelIndex &index) const;
    void setEditorData(QWidget *editor, const QModelIndex &index) const;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                const QModelIndex &index) const;
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
                const QModelIndex &index) const;
};
createEditor()函数,在QTableView中某个 Cell被DoubleClick时触发,这时可以用其index参数,获得该Cell的行与列,如果确定该Cell是可以被编辑的,那么就生成一个对应的编辑控件,如:             QComboBox *cbo = new QComboBox(parent);
            cbo->addItem(" 1 ");
            cbo->addItem(" 2 ");
            cbo->addItem(" 3 ");
            cbo->installEventFilter(const_cast<enstCatheterNodeInfoDelegate*>(this));
            return cbo;
这时,一个ComboBox就会被自动替换到该Cell的位置上了。以上 cbo->installEventFilter(),是为了让QItemDelegate能够获得用户在该控件上的Enter,Escape等事件,以便做出相应处理。 如果该Cell不能被编辑,此时返回NULL就可以了。 setEditorData()函数,用来对生成的控件中的值进行初始化,一般来说,这个函数在以上的createEditor()函数结束后会被自动调用,如下:             QComboBox *cbo = static_cast<QComboBox*>(editor);
            cbo->setCurrentIndex(0);
editor 是QWidget类型的参数,它代表createEditor()函数中生成的那个控件,这里必须将其进行类型转换,才可以变成自己实际生成的那个控件类型。如果在createEditor()中根据index.column()不同,生成了不同的编辑控件,那么这里也需要判断 index.column(),来将editor参数转换成相应的控件。 接着setModelData(),它会在用户的离开当前编辑中的Cell时被调用,可以在此将编辑控件中被修改了的数据写回到QTableView中,如下:             QString tmpstr;
            QComboBox *cbo = static_cast<QComboBox*>(editor);
            tmpstr.setNum(cbo->currentIndex());
            model->setData(index, tmpstr);
最后,updateEditorGeometry(),这个函数是用来控制编辑控件的位置的,一般只需要写成:     editor->setGeometry(option.rect);
编辑控件就会自动被调整到Cell的大小了。 3)在实际的窗体类中,使用如下的代码将以上这个类与 QTableView关联起来:     enstCatheterNodeInfoDelegate *delegate = new enstCatheterNodeInfoDelegate(this);
    tbl1->setItemDelegate(delegate);
4)光这样还不行,由于QTableView实在太底层了,它还需要知道以怎样的方式显示,即指定一个ItemModel,可以选用的类包括: QDirModel:可以以树状方式显示某个目录下的所有子目录,以及其相关信息。 QProxyModel:用来将旧的 Model类型过渡到新类型上的。 QStandardItemModel:最简单的Grid方式的显示Model。 另外,还可以自己从 QAbstractListModel, QAbstractProxyModel, QAbstractTableModel来继承出符合自己要求的model。 偷个懒,我就直接使用QStandardItemModel了:     mItemModel = new QStandradItemMode(this, 3, 10);
    tbl1->setModel(mItemModel);
5)基本完成,最后再加上初始化QTableView中所有文字,以及最终将设置好了的内容保存出来的代码,就可以了,完成的界面显示如下: 但是,仔细看看,还有不少不满意的地方,TableView的ColumnHeader居然自动填上了数字,而左边则多出了一条垂直Header,另外,所有Cell的宽度变成一样的了,多少有点不爽。。经过努力,总算摸索出了解决方法,不过不清楚真正的解决方法是什么,我这个解决方法应该并不好: 首先从QStandardItemModel中继承出自己的类,暂且名叫 enstCatheterNodeItemModel了: class enstCatheterNodeItemModel : public QStandardItemModel
{
    Q_OBJECT
public:
    enstCatheterNodeItemModel(QObject *parent = 0);
};
在其构造函数中,使用: enstCatheterNodeItemModel::enstCatheterNodeItemModel(QObject *parent)
    : QStandardItemModel(MAX_CATHETERNODE_COUNT, 3, parent)
{
    setHeaderData(0, Qt::Horizontal, tr("Input"));
    setHeaderData(1, Qt::Horizontal, tr("Signal"));
    setHeaderData(2, Qt::Horizontal, tr("Locate"));
}
强制其只能有三列,并指定每个列的标题。 在窗体的构造函数中,进一步调整QTableView:     QHeaderView *header;
    header = tbl1->horizontalHeader();
    header->resizeSection(0, 260);
    header->resizeSection(1, 260);
    header->resizeSection(2, 50);
    header = tbl1->verticalHeader();
    header->hide();
设置其各个列的宽度,同时将左边的垂直Header隐藏。 总算大功告成,加载延时已经基本感觉不到了。看看最后的效果: 以及另一组编辑器,其中的颜色选择ComboBox并不是QT自带的控件,而是自己实现的,照样可以被做成编辑控件:
<think>我们正在讨论如何在QTableView中关闭所有已打开的编辑器。根据用户的问题,我们需要一个方法,当用户点击其他位置或执行某些操作时,关闭当前所有打开的编辑器(比如完成编辑、取消编辑等)。 在QtQTableView中,当用户双击一个单元格(或触发编辑)时,会创建一个编辑器(例如QLineEdit、QSpinBox等)。有时候,我们可能需要以编程方式关闭这些打开的编辑器,比如在保存数据之前强制完成编辑,或者在切换行时关闭编辑器。 解决方案: 1. 我们可以通过获取当前打开的编辑器并关闭它来实现。但是,QTableView可能同时有多个编辑器打开吗?实际上,通常一次只有一个编辑器是打开的(因为编辑一个单元格时,其他单元格的编辑器会自动关闭)。但为了安全,我们可以考虑遍历所有打开的编辑器。 2. 实际上,QTableView提供了一个方法:`closePersistentEditor()`用于关闭持久化编辑器,但通常我们使用的是普通编辑器,它们是通过`openPersistentEditor()`打开的才会持久存在。而普通编辑器是临时打开的,我们通常使用`closeEditor()`,但这个方法不是公共的。 3. 更通用的方法是:我们可以通过QAbstractItemView的`closePersistentEditor()`来关闭持久编辑器,但是临时编辑器通常不需要我们手动关闭,因为当用户点击其他地方时,它们会自动关闭。然而,有时我们需要强制关闭,比如在程序中进行某些操作之前。 4. 实际上,我们可以发送一个编辑完成的信号,或者让QTableView失去焦点,但这可能不是可靠的方法。 5. 另一种方法是:我们可以通过获取当前正在编辑的索引(current editor index)然后关闭它的编辑器QTableView有一个方法`commitData()`和`closeEditor()`,但是这些是protected的,所以我们不能直接调用。 6. 实际上,我们可以调用`edit()`函数并传入一个无效的索引,这样就会关闭当前打开的编辑器。因为当调用`edit(QModelIndex())`时,它会关闭当前编辑器。 具体步骤: - 我们可以调用`tableView->setCurrentIndex(QModelIndex());`来清除当前选中的索引,但这可能不会关闭编辑器。 - 更有效的方法是:调用`tableView->closePersistentEditor(index)`,但前提是我们知道哪些索引打开了持久编辑器。 但是,对于临时编辑器(非持久),我们可以通过让视图失去焦点来关闭,或者通过模拟键盘事件(如按下Enter或Esc)来关闭,但这并不优雅。 实际上,Qt提供了一个方法:`QAbstractItemView::closeActiveEditor()`,但它是protected函数,我们不能直接调用。 因此,我们可以使用以下技巧:通过向tableView发送一个事件,让它失去焦点,从而关闭编辑器。但这样可能会影响用户体验。 更好的方法:我们可以使用`QTableView`的`commitData()`和`closeEditor()`,但是需要子类化。如果不想子类化,我们可以尝试以下方法: 在QAbstractItemView中,有一个方法叫做`commitData(QWidget *editor)`,它也是protected。但是我们可以通过事件过滤器来模拟。 然而,有一个更简单的方法:我们可以通过获取当前打开的编辑器,然后发送一个QKeyEvent事件(如Qt::Key_Enter)来提交数据并关闭编辑器。但这种方法比较hack。 实际上,Qt的文档中并没有提供一个公共的直接关闭所有临时编辑器的方法。 但是,我们可以通过设置QTableView的编辑触发器(edit triggers)为NoEditTriggers,然后再设置回原来的值,这样会取消当前的编辑。但这样会改变编辑触发器的设置,可能不是我们想要的。 另一种思路:我们可以遍历所有的单元格,如果有编辑器就关闭。但是临时编辑器并不是一直存在的,我们无法直接获取。 经过查阅资料,一个常用的方法是:让QTableView强制失去焦点,这样当前打开的编辑器就会提交并关闭。我们可以通过将焦点设置到另一个控件来实现。 例如: if (tableView->hasFocus()) { // 将焦点转移到其他控件,比如父窗口 parentWidget()->setFocus(); } 但是这样会改变焦点,可能不是用户期望的。 推荐的方法:使用一个无效的索引来关闭编辑器。 在Qt的源代码中,当我们点击其他地方时,会触发一个事件,然后调用`closeEditor`。我们可以模拟这一过程: 我们可以调用: tableView->setCurrentIndex(QModelIndex()); // 先清除当前索引 tableView->clearFocus(); // 清除焦点 但是,这并不总是有效。 实际上,有一个更直接的方法:使用`QAbstractItemDelegate`。我们可以获取当前打开的编辑器的索引,然后关闭它。 如何获取当前打开的编辑器的索引?我们可以通过`tableView->currentIndex()`来获取当前编辑的索引(如果有的话)。但是,如果用户正在编辑,那么当前索引就是正在编辑的索引。 然后,我们可以调用: tableView->closePersistentEditor(index); // 如果这个索引是持久编辑器 // 但是临时编辑器呢? 对于临时编辑器,我们可以使用: tableView->setIndexWidget(index, nullptr); // 但是setIndexWidget通常用于设置持久的小部件,而不是临时编辑器。 实际上,临时编辑器是由视图内部管理的,我们无法直接通过公共接口关闭。 经过搜索,发现一个可行的公共方法:通过调用`tableView->edit(QModelIndex())`。因为`edit`函数在传入一个无效索引时,会关闭当前打开的编辑器(如果存在)。 因此,我们可以这样写: tableView->edit(QModelIndex()); 这个方法会关闭当前打开的临时编辑器(如果有的话),并且不会打开新的编辑器。 但是,这个方法在Qt的文档中并没有明确说明,但在实现中确实是这样做的。我们可以查看Qt源代码(或通过测试)来验证。 测试:在编辑一个单元格时,调用`tableView->edit(QModelIndex())`,应该会关闭编辑器并提交数据(如果可提交)。 因此,我们可以使用: tableView->edit(QModelIndex()); 来关闭当前打开的编辑器(临时编辑器)。对于持久编辑器,我们需要单独关闭,但通常我们不会同时使用持久编辑器和临时编辑器。 如果我们的表格中有持久编辑器,那么我们需要记录哪些索引使用了持久编辑器,然后分别关闭它们。 但是,用户的问题只是要求关闭所有已打开的编辑器,并没有区分临时和持久。所以如果表格中使用了持久编辑器,我们需要单独处理。 然而,在大多数情况下,我们使用的是临时编辑器(即默认的编辑方式)。所以,我们可以这样实现: // 关闭临时编辑器 tableView->edit(QModelIndex()); // 关闭持久编辑器:我们需要知道哪些索引打开了持久编辑器,但通常我们不会在同一个表格中混用,所以如果用户使用了持久编辑器,应该自己维护一个列表。 // 或者,我们可以遍历整个模型的所有索引,然后对每一个索引调用closePersistentEditor,但这样效率很低,而且通常持久编辑器不会太多。 所以,如果用户确实使用了持久编辑器,那么需要自己记录,然后关闭。如果没有,那么使用上面的方法即可。 因此,我们可以提供一个函数,先关闭临时编辑器,然后关闭所有持久编辑器(如果用户有记录的话)。或者,我们可以提供一个方法,只关闭临时编辑器,然后由用户负责关闭持久编辑器。 总结: 对于临时编辑器,调用`tableView->edit(QModelIndex())`可以关闭当前打开的编辑器(因为一次只能有一个临时编辑器打开)。 对于持久编辑器,我们需要在需要关闭的时候,对每个使用`openPersistentEditor()`打开的索引调用`closePersistentEditor()`。 所以,如果用户没有使用持久编辑器,那么只需要: tableView->edit(QModelIndex()); 如果使用了,那么需要: // 假设我们有一个持久编辑器索引的列表 foreach (const QModelIndex &index, persistentEditorIndexes) { tableView->closePersistentEditor(index); } // 然后再关闭临时编辑器(如果有的话,因为持久编辑器和临时编辑器可以同时存在?但实际上,当你打开持久编辑器时,临时编辑器就不会再打开了) // 所以,我们可能需要同时做两件事。 但是,用户的问题是如何关闭所有打开的编辑器,所以我们需要同时处理这两种情况。 然而,在没有持久编辑器索引记录的情况下,我们无法关闭所有持久编辑器。因此,如果用户需要关闭所有持久编辑器,那么必须自己维护一个列表。 因此,我们可以这样设计: 1. 关闭当前临时编辑器(如果有的话): tableView->edit(QModelIndex()); 2. 关闭所有持久编辑器:遍历所有我们曾经打开过持久编辑器的索引,逐个关闭。 但是,如果用户没有记录,那么我们就无法关闭持久编辑器。所以,我们建议用户在使用持久编辑器时自行记录。 另外,还有一种情况:我们可能不知道哪些索引打开了持久编辑器。有没有办法获取所有打开持久编辑器的索引?答案是没有直接的接口。所以,用户必须自己记录。 因此,实现方法如下: // 关闭当前活动的临时编辑器(如果有) tableView->edit(QModelIndex()); // 然后,如果用户自己维护了一个持久编辑器索引的集合,则遍历这个集合并关闭 QSet<QModelIndex> persistentIndexes = ...; // 用户自己维护 for (auto index : persistentIndexes) { tableView->closePersistentEditor(index); } 但是,如果用户没有使用持久编辑器,那么第二步可以省略。 所以,用户需要根据自己是否使用持久编辑器来决定。 另外,我们也可以考虑在QTableView的子类中维护一个持久编辑器索引的列表,在调用openPersistentEditor时添加,在调用closePersistentEditor时移除。 注意:持久编辑器在模型数据改变时不会自动关闭,所以需要手动管理。 结论: 对于临时编辑器,使用`tableView->edit(QModelIndex())`来关闭。 对于持久编辑器,需要用户自己维护索引列表,然后逐个关闭。 因此,回答用户的问题:如何关闭所有打开的编辑器? 我们可以这样实现: // 关闭临时编辑器 tableView->edit(QModelIndex()); // 关闭持久编辑器:需要用户自己维护一个列表,然后遍历关闭 如果用户没有使用持久编辑器,那么只需要第一行代码。 示例代码: // 假设tableView是QTableView对象 // 关闭临时编辑器 tableView->edit(QModelIndex()); // 如果用户有持久编辑器,假设有一个成员变量 m_persistentIndexes 存储了所有打开持久编辑器的索引 for (auto index : m_persistentIndexes) { tableView->closePersistentEditor(index); } 注意:在关闭持久编辑器后,如果不再需要,应该从列表中移除。 但是,用户可能没有维护这样的列表。如果用户没有维护,那么只能通过其他方式,比如遍历整个模型的所有索引,然后对每个索引调用`closePersistentEditor`,但这样效率很低,而且可能关闭了没有打开的持久编辑器(虽然不会有副作用,但也不推荐)。 因此,我们建议用户在使用`openPersistentEditor`时记录索引,在不需要时关闭并移除记录。 最后,我们还需要注意:关闭编辑器(无论是临时还是持久)会触发相应的信号(如dataChanged),所以要注意信号的处理。 参考引用: 关于QTableView的编辑操作,可以参考Qt文档中关于QAbstractItemView的部分,特别是编辑相关的函数。 另外,引用[2]中提到了QTableView的编辑操作,但并没有直接提到如何关闭编辑器。引用[3]提到了删除控件的方法,但这里不适用。 因此,我们给出以下答案:</think>在 Qt 的 `QTableView` 中,关闭所有打开的编辑器可以通过以下两种方法实现: ### 方法 1:强制提交并关闭活动编辑器(推荐) ```cpp // 通过触发编辑器的提交操作关闭编辑器 tableView->setCurrentIndex(QModelIndex()); // 清除当前选中索引 tableView->clearFocus(); // 移除焦点 ``` **原理**: 当 `QTableView` 失去焦点或选中索引变为无效时,会自动提交并关闭打开的编辑器。这种方法利用了 Qt 内置的编辑提交机制,安全可靠。 ### 方法 2:遍历关闭所有持久化编辑器(按需使用) ```cpp // 遍历所有单元格关闭持久化编辑器 for (int row = 0; row < model->rowCount(); ++row) { for (int col = 0; col < model->columnCount(); ++col) { QModelIndex index = model->index(row, col); tableView->closePersistentEditor(index); // 关闭指定索引的编辑器 } } ``` **适用场景**: 仅当使用 `openPersistentEditor()` 显式打开持久化编辑器时需调用此方法(默认编辑器无需此操作)。 --- ### 关键说明 1. **临时编辑器**(默认行为) 通过用户交互(如双击单元格)自动创建,通过方法 1 即可关闭。 2. **持久化编辑器** 需通过 `openPersistentEditor()` 显式创建,关闭需使用方法 2 遍历操作。 3. **数据提交** 关闭编辑器时会自动触发 `dataChanged` 信号提交数据到模型[^2]。 --- ### 完整示例 ```cpp void closeAllEditors(QTableView* tableView) { // 关闭临时编辑器 tableView->setCurrentIndex(QModelIndex()); tableView->clearFocus(); // 关闭持久化编辑器(按需添加) QAbstractItemModel* model = tableView->model(); for (int r = 0; r < model->rowCount(); ++r) { for (int c = 0; c < model->columnCount(); ++c) { tableView->closePersistentEditor(model->index(r, c)); } } } ``` ### 注意事项 - 方法 1 对临时编辑器完全有效 - 持久化编辑器较少使用,通常无需额外处理 - 操作后编辑器内容会自动提交到数据模型[^2] [^1]: 当 TableView 设置为可编辑时,双击单元格可修改内容,编辑器会动态创建。 [^2]: 数据模型被修改后,会直接在 TableView 上更新显示。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值