Qt 中 signals-slots 的层级太深

Too deep hierarchy of signals-slots in Qt

我正在用 Qt 编写 GUI 应用程序。目前我在例行公事上浪费了太多时间。我的架构似乎有问题。请告诉我如何改变我的方法来改进代码。

我在做什么:

我的程序可以分解为 classes 的层次结构(不是继承而是组合)。例如:

class D { /* something */ };
class C { /* something */ };
class B { C c1; C c2; D d1; };
class A { D d1; C c1; };

所以,实际上它是一个树状层次结构,其中叶节点(class C,class D)在 Qt 术语中是 "models",用于保存数据。在层次结构的顶部放置 MainWindow (class A),它包含 "views" (class D,即 subwidget) 的第一层和带有数据的叶节点 (class C ,即文本字段)。

为了将信息从 main window 传递到数据,我使用从 mainwindow(按钮)到叶节点的函数调用。之后数据发生变化并通过信号槽机制告知 parents。 Parents继续通过信令向上传递消息。

我真的厌倦了建立所有这些交流。现在我最多有 5 个级别,但是在使用组合时通常的代码中并没有那么多。请告诉我如何改变我的方法。由于这些连接的复杂性,代码的开发极其缓慢,几乎停滞不前。


很难给出具体的例子,因为代码很多,但是解决起来难度很大的问题思路如下:

有两个QTreeView,分别从QAbstractItemModel继承自自己的模型显示数据(模型内部的树不是前面讨论的那棵树,这棵树只是一层层次)。 我想在一个 QTreeView 中 select objects 并在第二个 QTreeView 中改变 selection。 总共有 2 个 QTreeView,2 个不同的 QAbstractItemModel 实例, 2 棵自己的 objects 树(对于每个 QAbstractItemModel),以及 单个 数据。

听起来你可能已经成为经历太多例子的受害者。示例往往会在不属于它的地方塞满功能,从而有可能养成不良的编程习惯。

在实际生产中,事情需要更加划分。主要 window 不应该是 "application logic" 的容器,它需要关心的只是将主要小部件放在一起。

但这似乎不是你的情况,根据将事情“从主要window(按钮)委托给叶节点”的必要性来判断放吧。

在更大的范围内,根本不建议将应用程序逻辑与 UI 混合,更不要说将其全部塞入主要 window。应用程序逻辑应该是它自己的层,设计成它可以在没有任何 GUI 的情况下工作,然后 GUI 是另一个简单地连接到逻辑核心的层。

逻辑核心也不应该是一个整体,它应该由专注于其特定任务的各个组件组成。

您的用例实际上并不需要大量的连接,只需要 UI 元素的一些基本处理程序,它应该针对逻辑核心 API 而不是 GUI 元素,就像你现在正在做的那样。

不幸的是,你的澄清对我来说完全没有意义,我仍然完全不清楚你到底想做什么。

假设您的情况是这样的:

树 1 显示文件夹结构。

树 2 显示文件夹的文件内容,在树 1 中选择。

数据是文件的编辑器,假设是一个文本文件,在树 2 中选择。

因此,在伪代码中,假设 app 是您的应用程序核心逻辑对象:

点击树 1 中的一个项目会显示 app.setFolder(tree1.selectedItem())

点击树 2 中的一个项目说 app.setFile(tree2.selectedItem())

单击编辑器 "save" 按钮显示 app.save(editorUI.dataField.text())

logic layer                          gui layer
app                                  mainWindow
 folder <-----------select----------- tree1
 file   <-----------select----------- tree2
 save(newData) {                      editor
    if (file) file.rewrite(newData)    textField
 }                                     saveBtn: app.save(textField.text())

由于只有一个数据源,您可以执行以下操作:

  1. 为该数据源创建一个通用模型。模型应该概括地表示数据源,而不考虑视图需要什么。

  2. 创建两个代理视图模型,使通用模型适应视图的需要。

  3. 耦合显示视图模型的视图的选择模型。

鉴于映射到同一源的两个代理模型之上的选择模型,我们可以在它们之间传播选择变化。我们利用代理提供的选择映射。 QAbstractProxyModel 具有 mapSelectionxxxx 的功能实现。

void applySel(const QItemSelectionModel *src, const QItemSelection &sel,
              const QItemSelection &desel, const QItemSelectionModel *dst) {
  // Disallow reentrancy on the selection models
  static QHash<QObject*> busySelectionModels;
  if (busySelectionModels.contains(src) || busySelectionModels.contains(dst))
    return;
  busySelectionModels.insert(src);
  busySelectionModels.insert(dst);
  // The models must be proxies
  auto *srcModel = qobject_cast<QAbstractProxyItemModel*>(src->model());
  auto *dstModel = qobject_cast<QAbstractProxyItemModel*>(dst->model());
  Q_ASSERT(srcModel && dstModel);
  // The proxies must refer to the same source model
  auto *srcSourceModel = srcModel->sourceModel();
  auto *dstSourceModel = dstModel->sourceModel();
  Q_ASSERT(srcSourceModel && (srcSourceModel == dstSourceModel));
  // Convey the selection
  auto const srcSel = srcModel->mapSelectionToSource(sel);
  auto const srcDesel = srcModel->mapSelectionToSource(desel);
  auto const dstSel = dstModel->mapSelectionFromSource(srcSel);
  auto const dstDesel = dstModel->mapSelectionFromSource(srcDesel);
     // we would re-enter in the select calls
  dst->select(dstSel, QItemSelectionModel::Select);
  dst->select(dstDesel, QItemSelectionModel::Deselect);
  // Allow re-entrancy
  busySelectionModels.remove(src);
  busySelectionModels.remove(dst);
}

如果您有两个以上的视图,以上内容可以很容易地适用于目标项目选择模型列表。

我们可以使用这个翻译来耦合视图的选择模型:

void coupleSelections(QAbstractItemView *view1, QAbstractItemView *view2) {
  auto *sel1 = view1->selectionModel();
  auto *sel2 = view2->selectionModel();
  Q_ASSERT(sel1 && sel2);
  connect(sel1, &QItemSelectionModel::selectionChanged, 
          [=](const QItemSelection &sel, const QItemSelection &desel){
            applySel(sel1, sel, desel, sel2);
          });
  connect(sel2, &QItemSelectionModel::selectionChanged, 
          [=](const QItemSelection &sel, const QItemSelection &desel){
            applySel(sel2, sel, desel, sel1);
          });
}

以上内容未经测试,是凭记忆写的,但希望能顺利完成。