我用 C ++ / QT 编写了一个应用程序,它与设备进行通信以读/写其变量,将它们放入/获取它们并在gui中显示它们用于查看/编辑目的。
1)该设备附带一个示例c代码,该代码还定义了通信协议(以非常糟糕的方式),如:
#define VALUE_1 0x12345678
#define VALUE_2 0xDEADBEEF
#define MY_DEVICE_VAR (VALUE_1 << 4) & (VALUE_2 >> 6)
#define MY_DEVICE_VAR_1 (MY_DEVICE_VAR & (VALUE_1 << 2)
#define MY_DEVICE_VAR_2 (MY_DEVICE_VAR & (VALUE_2 << 4)
#define MY_DEVICE_VAR_2 (MY_DEVICE_VAR & (VALUE_2 << 4)
// .. and 300 more lines like above
因此,变量VAR_1
表示为:MY_DEVICE_VAR_1
。
2)我有一个包含设备所有变量的结构:
struct MyDeviceData
{
int var1;
double var2;
char var3;
bool var4;
.
.
};
它基本上是用于从设备读取的数据的投影存储。有4种不同类型的POD变量。
3)最后我的gui有gui元素来显示和编辑MyDeviceData的实例
class MyGuI
{
QLineEdit var1;
QLineEdit var2;
QComboBox var3;
QCheckBox var4;
.
.
};
现在我的问题是:
1)我正在进行MY_DEVICE_VAR1
- &gt;的映射MyDeviceData::var1
- &gt; MyGUI::var1
if
和switch/case
声明,我并不感到自豪。什么是更好的“程序化”方式来进行映射?
2)当gui元素的值发生变化时,我想将仅更新的值发送到卡片。除了覆盖诸如“textChanged
,selectedIndexChanged
”之类的事件的处理函数之外。是否有任何“更智能”的方法? (QSignalMapper?)
3)在这种项目中,是否可以概括整个苦差事? (代码生成器工具?模板?)
答案 0 :(得分:1)
我最近遇到了完全相同的问题,尽管我是设计设备,固件和通信协议的人。
我认为必须使用模型/视图才能保持理智。
我将所有变量作为派生自QAbstractTableModel
的数据模型类中的元素。那是因为有一定数量的简单参数(行),每个设备(列)都是相同的。但是很快,我将不得不转移到树模型,因为内部的一些参数被构造为列表,向量或矩阵,并且将它们直接暴露给视图是有帮助的,而不仅仅是格式化的字符串
模型类还有一些方便的getter / setter,因此您不必按行/列引用参数。通过QModelIndex
进行的行/列访问仅供视图使用。
我选择将UserRole用于直接表示的值(大多数是SI单位的两倍),以及显示和编辑角色,以将格式化/缩放数据呈现给小部件。
对于非视图控件,需要一个binder对象。 QDataWidgetMapper
由Qt提供,理想情况下您应该使用它。
前段时间我没有注意到有小部件映射器,所以我编写了一个自定义绑定器对象(从QObject派生),它为每个GUI控件实例化,将模型的某个索引绑定到非-view Qt控件小部件。另一种方法是使用QListView
s,每个视图只有一个元素的代理模型,并正确分配代理。不过,这会带来很多开销。绑定对象方法非常轻量级。
模型 - 视图方法还使人们能够轻松地分解每个控件的最新指示。
首次启动应用程序时,模型可以(通过专用角色)指示值无效。这可以在控制器上放置一个x交叉或理发杆,以清楚地表明那里没有有效值。
当设备处于活动状态且用户修改控件时,不同的角色可以指示该值已在模型中更改,但尚未传播到设备。
当设备通信代码从模型中获取更改并将其提交给设备时,它可以告诉模型有关它,并且视图(biner,真的)将自动获取并更新控制。
向模型添加Model * clone() const
方法,或添加序列化/反序列化运算符,可以轻松获取模型的快照,并实现撤消/重做,提交/恢复等。
活页夹的相关摘要如下:
// constructor
Binder::Binder(QAbstractItemModel * model_, const QModelIndex & index_, QObject * object) :
QObject(object),
model(model_),
index(index_),
sourceRole(Qt::DisplayRole),
property(""),
target(object),
lockout(false)
{
Q_ASSERT(index.isValid());
// replicate for each type of control
if (qobject_cast<QDoubleSpinBox*>(object)) {
connect(object, SIGNAL(valueChanged(double)), SLOT(doubleSpinBoxGet(double)));
connect(index.model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(doubleSpinBoxSet(QModelIndex, QModelIndex)));
}
else if (....)
}
// getter/setter for QDoubleSpinBox
void Binder::doubleSpinBoxGet(double val)
{
if (lockout) return;
QScopedValueRollback<bool> r(lockout);
lockout = true;
model->setData(index, val, sourceRole);
}
void Binder::doubleSpinBoxSet(const QModelIndex & tl, const QModelIndex & br)
{
if (lockout) return;
if (! isTarget(tl, br)) return;
QScopedValueRollback<bool> r(lockout);
lockout = true;
if (! index.data().canConvert<double>()) return;
qobject_cast<QDoubleSpinBox*>(target)->setValue(index.data(sourceRole).toDouble());
}
// helper
bool Binder::isTarget(const QModelIndex & topLeft, const QModelIndex & bottomRight)
{
return topLeft.parent() == bottomRight.parent()
&& topLeft.parent() == index.parent()
&& topLeft.row() <= index.row()
&& topLeft.column() <= index.column()
&& bottomRight.row() >= index.row()
&& bottomRight.column() >= index.column();
}