关于QModelIndex、QStandardItem、QSelectionModel以及QTableview视图模型的实际运用与一些理解。
首先先介绍一下它们的概念:
QStandardItemModel:基于项数据的标准数据模型,可以处理二维数据。维护一个二维的项数据数组,每个项是一个 QStandardltem 类的变量,用于存储项的数据、字体格式、对齐方式等。
QModelIndex:是 Qt 中用于表示数据模型中项的索引的类。它包含了描述项位置的行号、列号和父索引等信息,用于在数据模型和视图之间进行数据交互。
QItemSelectionModel:一个用于跟踪视图组件的单元格选择状态的类,当在 QTableView 选择某个单元格,或多个单元格时,通过 QItemSelectionModel 可以获得选中的单元格的模型索引,为单元格的选择操作提供方便。
QTableView:二维数据表视图组件,有多个行和多个列,每个基本显示单元是一个单元格,通过 setModel() 函数设置一个 QStandardItemModel 类的数据模型之后,一个单元格显示 QStandardItemModel 数据模型中的一个项。
再介绍一下任务需求:此次需要实现一个简易的opcua客户端,大致的功能是实现对opcua服务器数据的连接、断开连接、数据加载显示以及对服务端opc数据读写功能。因此会用到上述所介绍的数据模型;
先看一下整体实现效果:
和实际ua客户端是同步的:
使用tableView为标注读写点的视图框,tableview是从上方右侧框,双击数据将其添加到tableview进行数据读取与显示,最后可在tableview里面进行写值,写入到opcua服务端。
部分代码如下:
创建数据模型:
m_pItemModel = new QStandardItemModel(ui.tableView);
m_pItemModel->setColumnCount(3);
m_pItemModel->setHeaderData(0, Qt::Horizontal, u8"OPC名称");
m_pItemModel->setHeaderData(1, Qt::Horizontal, u8"读取值");
m_pItemModel->setHeaderData(2, Qt::Horizontal, u8"写入值");
ui.tableView->setModel(m_pItemModel);
ui.tableView->setSelectionMode(QAbstractItemView::SingleSelection);
ui.tableView->setColumnWidth(0, 520);
ui.tableView->setColumnWidth(1, 80);
ui.tableView->setColumnWidth(2, 80);
读取数据:
void ReadWriteOPC::DoReadValue(const QPoint& point) {
QListWidgetItem* clickedItem = ui.listWidget2->itemAt(point);
if (clickedItem) {
std::string opcName = clickedItem->text().toStdString();
UaString str[1];
str[0] = UaString(opcName.c_str());
//假设返回信息的个数dwItemCnt为1;
// boost::shared_array<UaNodeValue>& rdValues;
boost::shared_array<UaNodeValue>rdValues;
UaNodeValue wrt[1];
wrt[0].wDataType = UA_DATATYPE_INT16;
// wrt[0].wDataType= UA_DATATYPE_INT16;
if (m_OpcuaClient.Read(str, rdValues, 1, AttributeValue) == false) {
qDebug() << "OPC点位值读取失败" << rdValues[0].val.iValue << ";";
}
else {
int ReadValue = rdValues[0].val.iValue;
qDebug() << "OPC点位值读取成功" << ReadValue << ";";
m_mapOpcList.insert(std::make_pair(opcName, ReadValue));
RefreshOpcTableView(m_mapOpcList);
}
}
}
写入数据:
void ReadWriteOPC::DoWriteValue()
{
int rows = ui.tableView->model()->rowCount();
for (int r = 0; r < rows; r++) {
QModelIndex OpcNameIndex = ui.tableView->model()->index(r, 0); // 获取指定行索引和第一列的模型索引
QModelIndex OpcValueIndex2 = ui.tableView->model()->index(r, 2);
QVariant OpcName = ui.tableView->model()->data(OpcNameIndex);
QVariant OpcValue = ui.tableView->model()->data(OpcValueIndex2);
///这种是传数据模型item;
//QVariant value1 = item->model()->data(item->model()->index(row, 0)); // 第一列的值
//QVariant value2 = item->model()->data(item->model()->index(row, 1)); // 第二列的值
if (OpcValue.isValid()) {
UaString str[1];
// QString firstValue = item->text();
//int WriteValue = item->data(Qt::UserRole).toInt();
QString OpcNameValue = OpcName.value<QString>();
int WriteValue = OpcValue.value<int>();
std::string strOpcNameValue = OpcNameValue.toStdString();
str[0] = UaString(strOpcNameValue.c_str());
boost::shared_array<uint32_t>wrtRlts;
UaNodeValue wrt[1];
wrt[0].val.iValue = WriteValue;
wrt[0].wDataType = UA_DATATYPE_INT16;
if (m_OpcuaClient.Write(str, wrt, 1, wrtRlts) == false) {
qDebug() << "OPC点位写入失败";
}
else {
qDebug() << "OPC点位写入成功";
for (auto it = m_mapOpcList.begin(); it != m_mapOpcList.end(); ++it) {
// 检查当前值是否为"Rccs"
if (it->first == strOpcNameValue) {
// 修改为新的值
it->second = WriteValue;
}
}
}
}
else {
qDebug() << "写入点无效";
}
}
}
每次更新tableview:
void ReadWriteOPC::RefreshOpcTableView(std::map<std::string, int>mapMonItem)
{
m_pItemModel->clear();
int nIndex = 0;
for (const auto& iter : mapMonItem) {
m_pItemModel->setItem(nIndex, 0, new QStandardItem(iter.first.c_str()));
m_pItemModel->setItem(nIndex, 1, new QStandardItem(QString::number(iter.second)));
m_pItemModel->setItem(nIndex, 2, new QStandardItem(QString::number(iter.second)));
++nIndex;
}
m_pItemModel->setHeaderData(0, Qt::Horizontal, u8"OPC名称");
m_pItemModel->setHeaderData(1, Qt::Horizontal, u8"读取值");
m_pItemModel->setHeaderData(2, Qt::Horizontal, u8"写入值");
ui.tableView->setModel(m_pItemModel);
ui.tableView->setColumnWidth(0, 520);
ui.tableView->setColumnWidth(1, 80);
ui.tableView->setColumnWidth(2, 80);
}
之前在界面写入数据触发更新写入数据:
connect(m_pItemModel, &QStandardItemModel::itemChanged, this, &ReadWriteOPC::DoWriteValue);
用到了itemChanged,即每次对tableview里面的数据模型进行改动时,即触发写值操作,但是有一个弊端,即每次往tableview里面添加读取的数据后即触发了itemchanged,但此时并未进行写值,不需要触发写值操作,虽然在表面上看不出,但是在debug模式时,相当于改一次值触发了两次写值操作,遂放弃这种方式。它传的参数即是QStandardItem模型。
后面又了解到数据选择模型,于是用到了currentChanged
connect(m_pItemSeleModel, &QItemSelectionModel::currentChanged, this, &ReadWriteOPC::DoWriteValue);
但是发现它触发信号的条件是选择项的改变,但是有时候造成多余操作了,比如:我只需要改变一项,因此导致信号无法触发。但是这个模型也给到了一些启发,通过它传的参数QModelIndex的模型,了解到可以直接通过index取值,而不需要通过QStandarItemModel模型来取。
最后采用了按钮触发的操作,每次都一次性写入,一次性更新。
connect(ui.btnSaveWriteVal, &QPushButton::clicked, this, &ReadWriteOPC::DoWriteValue);