简体   繁体   中英

Too deep hierarchy of signals-slots in Qt

I am writing GUI applicaton in Qt. Currently I waste too much time on routine. It seems something wrong with my architecture. Please tell me how can I change my approach to improve code.

What I am doing:

My program can be decomposed as hierarchy of classes (not inheritance but composition). In example:

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

So, actually it is a tree hierarchy where leaf nodes (class C, class D) are "models" in Qt terminology which hold data. At the top of hierarchy MainWindow (class A) is placed, which holds first level of "views" (class D, ie subwidget) and leaf node with data (class C, ie text field).

To pass information down from main window to data I use function calls from mainwindow (pushbuttons) to leaf nodes. After that data changes and tells parents about it with signal slot mechanism. Parents continue to pass message up with signaling.

I am really bored by establishing all these communication. Now I have up to 5 levels, however it is not that much in usual code when using composition. Please tell me how can I change my approach. Due to complexity of these connections, development of the code extremely slow, almost stopped.


It is hard to give a concrete example, because there are a lot of code, but the idea of problem which is very difficult to solve is following:

There are two QTreeView, which differently shows data from own model inherited from QAbstractItemModel (tree inside model is not that tree in previous discussion, this tree is only single level of hierarchy). I want to select objects in one QTreeView and change by that selection in second QTreeView. There are total 2 QTreeView, 2 different QAbstractItemModel instances, 2 trees of own objects (for each QAbstractItemModel), and single data.

Sounds like you might have become a victim of going through too many examples. Examples tend to cram functionality where it doesn't belong, creating the possibility to develop bad programming habits.

In actual production things need to be more compartmentalized. The main window should not be the container of the "application logic", all it needs to concern itself with is holding together the main widgets.

But that doesn't seem to be your case, judging by the necessity to delegate things " from mainwindow (pushbuttons) to leaf nodes " as you put it.

On a grander scale, it is not advisable to mix application logic with UI at all, much less cram it all in the main window. The application logic should be its own layer, designed so that it can work without any GUI whatsoever, and then the GUI is another layer that simply hooks up to the logic core.

The logic core should not be monolith either, it should be made of individual components focusing on their particular task.

Your use case doesn't really require any crazy amount of connections, just some basic handlers for the UI elements, which should target the logic core API rather than GUI elements as you appear to be doing now.

Your clarification unfortunately makes absolutely no sense to me, it is still completely unclear what you exactly you want to do.

Let's assume your situation is something like this:

Tree 1 shows a folder structure.

Tree 2 shows the file content of the folder, selected in tree 1.

Data is an editor for the file, assuming a text file, selected in tree 2.

So, in pseudocode, presuming that app is your application core logic object:

Clicking an item in tree 1 says app.setFolder(tree1.selectedItem())

Clicking an item in tree 2 says app.setFile(tree2.selectedItem())

Clicking the editor "save" button says 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())

Since there is only a single data source, you could do the following:

  1. Create a general model for that data source. The model should represent the data source generally, without consideration of what the views need.

  2. Create two proxy viewmodels that adapt the general model to the needs of the views.

  3. Couple the selection models of the views that display the viewmodels.

Given the selection models on top of the two proxy models that map to the same source, we can propagate the selection change between them. We leverage the selection mapping provided by the proxy. The QAbstractProxyModel has a functional implementation of 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);
}

The above could be easily adapted for a list of destination item selection models, in case you had more than two views.

We can use this translation to couple the selection models of the views:

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);
          });
}

The above is untested and written from memory, but hopefully will work without much ado.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM