多个QTableWidget的共享选择

Shared selection for several QTableWidgets

提问人:Letynn C 提问时间:11/17/2023 更新时间:11/19/2023 访问量:44

问:

我正在使用Qt 5.6.1。有几个 QTableWidget 表,理论上它们代表一个表 - 它们位于紧密的位置,并且行上的数据是相互关联的,但它们应该呈现为单独的小部件。它们的选择标志是 SelectRows 和 ExtendedSelection mod。我怎样才能为他们做一个一般的选择?假设您选择了第一个表的几行,并在其他表中选择了相同的行。应相应地删除所选内容。表具有不同的列数。

我尝试以这种方式处理来自 selectionModel 表的信号:selectionChanged

void wSpisokActive::selectionChangedMain(const QItemSelection &selected, const QItemSelection &deselected)
{
    permission_to_changed_main_ = false; // flag to exit a similar slot in another table

    if (permission_to_changed_monitoring_) { // select row
        QModelIndexList selected_list = selected.indexes();
        for (QModelIndex model_index: selected_list) {
            ui->tbMonitoring->selectRow(model_index.row());
        }

        QModelIndexList deselected_list = deselected.indexes();
        int last_row = -1;
        for (QModelIndex model_index: deselected_list) { // deselect row
            if (last_row == model_index.row()) continue;
            last_row = model_index.row();
            for (int i = 0; i < ui->tbMonitoring->columnCount(); ++i) {
                ui->tbMonitoring->selectionModel()->select(ui->tbMonitoring->model()->index(last_row, i), QItemSelectionModel::Deselect);
            }
        }
    }

    permission_to_changed_main_ = true;
}

但是这样,通过 Ctrl 进行选择的处理方式会不正确。此外,在选择几行时,只有最后一行会突出显示 - 我尝试在选择行之前和之后进行设置。但这无济于事。ui->tbMonitoring->setSelectionMode(QAbstractItemView::MultiSelection);ui->tbMonitoring->setSelectionMode(QAbstractItemView::ExtendedSelection);

C++ qt qt5 qtablewidget

评论

0赞 musicamante 11/17/2023
如果选择行为是一致的,并且行数是一致的,那么还有另一种选择:与其使用多个QTableWidgets,不如使用一个QTableView,而对其他QTableView使用QTableView。第一个将具有完整的表内容,其他将使用其模型;然后,您可以根据要在其他表上显示的表来调用每个表。最后,将第一个表的选择模型设置为所有其他表。SelectRowssetColumnHidden()
0赞 Letynn C 11/18/2023
@musicamante感谢您的回复!这是一个有趣的解决方案,我将在周一尝试。然后我可以像QTableView一样制作所有表,并在代码中的某处有一个指向QTableWidget的指针。然后,当我在所有其他表格中单击一个表格中的一行时,我会得到一个选择吗?
0赞 musicamante 11/18/2023
你的意思是,使用一个“隐藏的”QTableWidget?在这一点上,拥有一个 QTableWidget 是没有意义的:只需为所有视图创建一个通用的 QStandardItemModel,并在它们之间共享相同的项目选择模型。

答:

1赞 Atmo 11/18/2023 #1

正如本帮助页面中所解释的(我的重点):

有关视图中所选项的信息存储在 QItemSelectionModel 类的实例中。这将维护单个模型中项的模型索引,并且独立于任何视图。由于一个模型上可以有多个视图,因此可以在视图之间共享选择,从而允许应用程序以一致的方式显示多个视图。

您感兴趣的方法成对出现:

生成的代码如下所示(假设这是在您的主线程上):

auto widgetModel    = myTableWidget->model();
auto widgetSelModel = myTableWidget->selectionModel();
for (auto view : { myTableView1, myTableView2, myTableView3 }) {
    view->setModel(widgetModel);
    delete view->selectionModel();
    view->setSelectionModel(widgetSelModel);
}

编辑:关于QTBUG-49966,@musicamante在评论中指出。

为了减少追逐:是的,每个视图 1 个选择模型(更确切地说,视图的标题视图的 1 个选择模型)将保留在内存中,直到视图及其标题被删除。
但是,如果您只执行我上面介绍的内容,则每个视图只会“泄漏”1个选择模型(这应该是一个非常小的数字),当您删除视图时,这些模型将被删除。

如果要实现一个窗口,其中调用了任意次数(例如,请参阅注释中 @musicamante 描述的场景),则可以执行以下几项操作作为缓解措施:setModel

  1. 要限制内存泄漏的生存期,请执行以下操作:

    • AFAICT,Qt创建的所有选择模型都有一个父集。
      如果确保删除小部件(确保在关闭窗口时清除所有内容的最简单方法是调用 ),则这些未使用的选择模型不会比视图更长久。
      myWindow->setAttribute(Qt::WA_DeleteOnClose);

    如果你从不删除视图/小部件,那么内存泄漏就在你身上(你永远不会删除创建的对象),而不是因为Qt创建的未使用的选择模型。

    • 切勿自行创建选择模型,至少在不设置其父模型的情况下。我希望这是显而易见的,但永远不要打电话。
      只要所有选择模型都具有视图作为其父视图,它们就会随视图一起删除。
      mySelectionModel->setParent(nullptr);
  2. 若要限制内存泄漏的大小:必须确保每个视图不要多次调用:
    setModel()

    • 如果您的模型支持重置,则使用这种可能性。
      QSqlTableModel示例:不应通过创建新模型来替换现有模型来更改加载的SQL表。相反,请执行以下操作:
    myExistingSqlTableModel->setTable(newlySelectedTable);
    myExistingSqlTableModel->select();
    
    • 如果您确实必须更改模型(例如,因为模型不支持重置,或者因为新模型与现有模型的类型不同),由于 QIdentityProxyModel,您仍然有一条出路。
      代理模型的目的不应该是这个,但它在这里派上用场。可以在视图上更改模型(至少,这是它的视觉外观),而无需调用 .
      它是通过执行以下操作来初始化的:
      setModel
    QIdentityProxyModel* proxyModel = new QIdentityProxyModel(myView);
    proxyModel->setSourceModel(myModel);
    myView->setModel(proxyModel);
    

    然后,可以将新模型附加到视图:

    if (auto proxyModel = dynamic_cast<QAbstractProxyModel*>(myView->model()); proxyModel) {
        if (proxyModel->sourceModel())
            delete proxyModel->sourceModel();
        proxyModel->setSourceModel(myNewModel);
    }
    

    瞧!不再需要在同一视图上多次调用。setModel


与此相关的是,如果我能提出一个快速(可选)的建议:

  • 有经验的Qt开发人员倾向于偏爱,而不是同行。
    虽然是初学者的首选课程,简化了很多所需的工作,但它对灵活性设置了如此有限的限制,以至于缺点远远大于优点。
    QListViewQTableViewQTreeViewQxxxxxWidgetQTableWidget
  • 有经验的Qt开发人员也倾向于避免(注意,所有类都使用自己的内部独立模型)。
    如果您的应用程序已经具有一些内部结构来存储屏幕上显示的数据,则将其映射到 的自定义子类(而不是将其复制到独立模型(如 )中通常更有效。
    QStandardItemModelQxxxxxWidgetQAbstractItemModelQStandardItemModel

你越早习惯于创建自己的模型类,你的生活就会变得越轻松。
如果您使用的是表格,则可以使用 QAbstractTableModel 开始您的学习之旅。它应该足够容易作为起点,并且通常与 相比更容易进行子类化。
QAbstractItemModel

以上内容适用于您当前的代码,因此请随意忽略此建议

评论

0赞 musicamante 11/19/2023
有趣的是,我刚刚意识到这会导致标头选择模型的内存泄漏(参见 QTBUG-49966,因为它直接调用它们(反过来,它们会创建自己的选择模型),但随后它通过显式调用表来覆盖它。因此,至少在您的示例中,生成的标题的“幻影”选择模型应与视图的“幻影”选择模型一起销毁。setModel()setModel()setSelectionModel()
0赞 Atmo 11/19/2023
@musicamante:TBH,我不明白Christian Ehrlicher(2018年2月1日)的评论。当我阅读代码时,所有内容最终都会调用,其中选择模型由以下行创建: 。因此,视图(包括标题视图)被设置为选择模型的父级,并且应该将其删除(包括调用 的情况)。QAbstractItemView::setModelQItemSelectionModel *selection_model = new QItemSelectionModel(d->model, this);setModel(nullptr);
0赞 Atmo 11/19/2023
因此,只要你从不称呼自己(上面的代码避免了这样做,它重用了之前在内部创建的选择模型)并且没有父级,并且你确保你的小部件被删除(如果你不这样做,那么你就会有泄漏,但在小部件本身上),内存最终应该被释放,不是吗?如果这是真的,那么只有在您在长期视图上重复更改模型时,“泄漏”才会成为问题,但我无法想象会发生这种情况的情况......我的分析有什么问题吗?new QItemSelectionModel()setModel()
0赞 musicamante 11/19/2023
只要我能理解代码(对不起,我是 Python 用户,所以我对 C++ 只有部分了解),当调用(以及类似的 QTreeView)时,它也会调用其标头。这会导致在标头上创建进一步的 QItemSelectionModel:它是不可控的,但它也是任何 QAbstractItemView 的预期行为。但是,QTableView也会按预期创建自己的选择模型,并在其标题上设置它以保持一致性,而无需检查他们刚刚创建的先前创建的选择模型。QTableView.setModel()setModel()
0赞 musicamante 11/19/2023
我知道这是一个特殊的场景,但并不罕见:例如,您可能有一个基于外部方面切换模型的持久视图;想象一下,一个 QListView 在其项目中列出了几个模型(例如,表示数据库中的不同表),以及一个 QTableView,它在选择列表的项(模型)时显示它们的内容。我了解默认行为和进行此类更改的含义,但至少应该在使用 QHeaderViews 的模型视图的文档中解释这方面。