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())
由于只有一个数据源,您可以执行以下操作:
为该数据源创建一个通用模型。模型应该概括地表示数据源,而不考虑视图需要什么。
创建两个代理视图模型,使通用模型适应视图的需要。
耦合显示视图模型的视图的选择模型。
鉴于映射到同一源的两个代理模型之上的选择模型,我们可以在它们之间传播选择变化。我们利用代理提供的选择映射。 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);
});
}
以上内容未经测试,是凭记忆写的,但希望能顺利完成。
我正在用 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())
由于只有一个数据源,您可以执行以下操作:
为该数据源创建一个通用模型。模型应该概括地表示数据源,而不考虑视图需要什么。
创建两个代理视图模型,使通用模型适应视图的需要。
耦合显示视图模型的视图的选择模型。
鉴于映射到同一源的两个代理模型之上的选择模型,我们可以在它们之间传播选择变化。我们利用代理提供的选择映射。 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);
});
}
以上内容未经测试,是凭记忆写的,但希望能顺利完成。