数据编辑
环境搭建
软件下载
-
QGIS下载较简单 这是个开源免费的软件
-
VS2015 有安装经验了
-
QT下载:https://blog.csdn.net/jjxcsdn/article/details/125432165
原先的官网下载太慢了,初步的理解就是图形用户界面应用程序开发框架
-
下载OSGeo4W Setup:https://blog.csdn.net/qq_42244020/article/details/122678532
-
下载成功的标志是打开VS2015,可以直接搜索QT安装
注意
这里有个大坑!
第一次下载玩QGIS后,你会发现在apps/qgis下没include lib文件 apps下也没有qgis-ltr文件。这是因为上面只安装了运行程序(不清楚)所以需要进一步安装。但这里的问题是什么叫进一步安装。答案是 需要把所有要用的到的文件都改成数字版本(比如关键的qgis-ltr-dev),这些可以在最后一列中看文件名确认,或者直接搜索框搜索。这样文件夹才完整。
![img](https://i-blog.csdnimg.cn/blog_migrate/f218d52ed7a55e7c4319fadf7007e621.png#pic_center)
-
关于不能打开QDocument:https://blog.csdn.net/weixin_42258743/article/details/108991578
-
QT的ui打开后闪退:https://blog.csdn.net/weixin_32155265/article/details/114905744
-
关于vs2015+qt5编译出现error LNK2001: 无法解析的外部符号:https://yunxingluoyun.blog.csdn.net/article/details/122453814?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-122453814-blog-121333556.pc_relevant_landingrelevant&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-122453814-blog-121333556.pc_relevant_landingrelevant&utm_relevant_index=1
-
报错,可以加载dll,但无法打开pdb:http://www.caiyi.tech/post.html?postKey=qgis_dev_1_env_setup(配置调试环境)
https://www.cnblogs.com/fcfc940503/p/11254063.html(加载Windows符号库)
运行
把环境调试好后,试运行软件,功能是打开文件夹选择一个shp文件进行打开和展示。(直接ctrl+f5运行不要调试运行,调试的话太卡了,或者把之前勾选的“符号服务器”再取消勾选,因为一次加载符号就够了)
迁移到QT.UI中去开发
原本的窗口设计完全借助与QMainWindow插件,这样不方便以后的窗口美化设计。所以项目最好可以迁移到QT.UI中设计(如C#的窗体设计一样 可以拖拽式设计)
实现步骤其实很简单,奈何网上资料很少。
- 双击“xxxx.ui”即可进入QT的窗体设计界面(QTDesigner)
- 然后自己在QTDesigner中进行按钮的拖拽,命名什么的。
- 关键怎么让代码用它 或者说,你的程序跑起来就是这个我设计的QT窗口。
第一步:在项目的头文件中(QGISEdit.h)引用,注意引用的格式。
#include "ui_QGISEdit.h"
第二步:记住在QT窗口中,给每个按钮的name,在cpp程序中,需要使用这个name来激发按钮事件
connect( ui.actionOpenShp, SIGNAL( triggered() ), this, SLOT( addVectorLayers() ) );
connect( ui.actionOpenTif, SIGNAL( triggered() ), this, SLOT( addRasterLayers() ) );
其他就都和正常的代码编写过程一样了。运行程序后,就会发现窗口是在QTDesigner中设计的窗口了。
文件处理
打开shp文件
前一步已经实现,不赘述
打开tif文件
- 与打开shp文件相对应,打开tif文件需要用到qgsrasterlayer.h接口:https://api.qgis.org/api/1.8/classQgsRasterLayer.html
#include <qgsrasterlayer.h>
- 逻辑处理和打开shp文件很像:https://www.osgeo.cn/post/3815g
//选择文件
QString fileName = QFileDialog::getOpenFileName(this, tr("Open image file"), "", "*.tif");
if (fileName.isEmpty()) {
QMessageBox::critical(this, "警告", "文件名称为空");
return;
}
//存储图层
QgsRasterLayer *my_layer = nullptr;
//图层 name 设置为tifFile 供删除时使用
QString basename = "tifFile";
QString provideKey = "";
if (provideKey.isEmpty()) {
my_layer = new QgsRasterLayer(fileName, basename);
}
else {
my_layer = new QgsRasterLayer(fileName, basename, provideKey);
}
if (!my_layer->isValid()) {
QMessageBox::critical(this, "警告", "栅格图层无效");
return;
}
//添加到图层list
layers.push_front(my_layer);
//画布重绘
mapCanvas->setExtent(my_layer->extent());
mapCanvas->setLayers(layers);
mapCanvas->refresh();
打开tif效果
图层列表
大致效果为:
- 界面左侧显示图层列表,列出当前已加载的所有图层,同时每个图层前面有复选框可以控制图层的显示/隐藏;
- 界面右侧为画布,按图层列表的适当顺序显示所有未隐藏的图层。
QGIS 提供了 QgsLayerTreeView
类,专门实现图层列表功能。QgsLayerTreeView
是 QTreeView
的子类,遵循Qt 的 Model/View 架构。与 QgsLayerTreeView
配合的是 QgsLayerTreeModel
类(QgsAbstractItemModel
的子类)。(典型的MVC架构 可惜还是理解的不是很深刻)。
最后的代码是四处缝补了,采取了很多人代码的所长,避免所短。
- 主要参考(可惜它的版本是QGIS2的):https://blog.csdn.net/deirjie/article/details/50428179
- 次要参考(弥补上个参考的QGIS2的版本缺陷):https://blog.csdn.net/guoqiong07/article/details/126285681
- 次要参考(相对简洁,是对前两个文件的提炼):http://www.caiyi.tech/post.html?postKey=qgis_dev_3_layer_tree
QgsLayerTreeModel* model = new QgsLayerTreeModel( QgsProject::instance()->layerTreeRoot(), this );
model->setFlag( QgsLayerTreeModel::AllowNodeRename );
model->setFlag( QgsLayerTreeModel::AllowNodeReorder );
model->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
model->setFlag( QgsLayerTreeModel::ShowLegendAsTree );
model->setAutoCollapseLegendNodes( 10 );
m_layerTreeView->setModel( model );
最后的效果如图:
属性数据的编辑
打开属性表
- 在头文件(QGISTest.h)里引入一个qgsvectorlayercache.h不然后面构造矢量图层的数据缓冲QgsVectorLayerCache时会报错“未定义QgsVectorLayerCache”
同理引入一个,qgsattributetablemodel.h和gsattributetablefiltermodel.h,为后面创建矢量图层数据模型做准备
- 关于QgsVectorLayerCache的官方文档:https://api.qgis.org/api/2.8/classQgsVectorLayerCache.html
#include <qgsvectorlayercache.h>
#include <qgsattributetablemodel.h>
#include <qgsattributetablefiltermodel.h>
- 在头文件中声明构造矢量图层的数据缓冲,通过实际的矢量图层
QgsVectorLayer
建立的数据缓存类。构造QgsAttributeTableModel
时,只能使用图层的数据缓存,而不能直接使用原图层,应该是为了避免数据冲突之类的问题。
// 矢量图层的数据缓冲,用于建立数据模型
QgsVectorLayerCache* mpVectorLayerCache = nullptr;
- 然后我们在程序文件(QGISTest.cpp)通过将矢量图层的指针传入构造函数来构造数据缓存对象。第二个参数表示缓存大小,这里我们直接将缓存大小设定为要素数量,即全部缓存。把这行代码写在打开矢量数据的功能函数**void QGISTest::addVectorLayer()**中
mpVectorLayerCache = new QgsVectorLayerCache(vecLayer, vecLayer->featureCount());
-
接下来我们通过刚刚建立的数据缓存来创建图层的数据模型。在打开矢量数据的功能函数添加如下代码
mpAttrTableModel = new QgsAttributeTableModel(mpVectorLayerCache); mpAttrTableModel->loadLayer();
-
下一步,我们构造数据的筛选模型。构造函数除了要求传入源模型对象之外,还需要传入画布对象。这是因为筛选执行的过程中,画布上同样要更新渲染(只显示筛选后的要素)。
// 创建属性表筛选器 Model
mpAttrTableFilterModel = new QgsAttributeTableFilterModel(mapCanvas, mpAttrTableModel);
-
给”打开属性表“按钮添加事件
QMessageBox::critical(this, "error", QString("执行\n")); QgsAttributeTableView* pView = new QgsAttributeTableView(this); pView->setWindowModality(Qt::WindowModality::WindowModal); pView->setWindowFlag(Qt::Window); pView->setWindowTitle(u8"图层属性表"); pView->setModel(mpAttrTableFilterModel); pView->resize(700, 450); pView->show();
注意
-
如果就这么写,会发现,“打开属性表”这个按钮点击后没有反应,调试后发现这个按钮的响应事件根本没有进行响应
-
原因:还记得一开始生成的moc_QGISTest.cpp文件吗,它在这个文件里注册了按钮的响应,每次添加新的QGIS功能都需要更新这个文件。更新步骤参考“关于vs2015+qt5编译出现error LNK2001: 无法解析的外部符号:”这个网站。
结果
删除属性数据
这可能是最简单的一个属性编辑了,它不涉及添加的(因为可能要创建几何元素)。它不涉及查询的构造过滤器。但是,依旧做了5天,其中很多时候都在原地踏步。
还是那句话,当报错的时候,不要怀疑是环境问题,不要怀疑编译器问题,更不要怀疑电脑坏了。根本原因都在于,你根本不懂QGIS,不懂Qt,或者根本就不懂C++。
QGIS有关于属性表全套的架构(MVC)
分别是QgsAttributeTableModel,QgsAttributeTableView。还有一个很重要的类QgsAttributeTableFilterModel(它会帮助筛选你选定的图层,要素,表记录),
但是QGIS的关于属性表,并没有完全封装好的删除属性表记录的方法(至少我没发现)
所以只能找到原生的头文件和CPP文件进行重写,这里详细记录步骤,方便以后的学习借鉴。
首先明确目标:可以在属性表窗口触发事件(不管是右击记录,或选择记录后点击删除按钮)删除表记录,并且图层中的要素也会消失。比如我们我打开贵州省的属性表,选中贵州市的数据后,再选择删除。贵州市的记录就在表中删除,同时图层(画布)上的数据也随之删除。
明确目标后就开始吧
重写
-
找到qgsattributetableview.h的原文件。这个可以在qgis-ltr-dev下的include文件夹下找到(或者是qgis-lt,qgis的include文件夹)这里也进一步总结了,include文件夹是组织头文件的(.h)。是不是有点废话?但是刚开始可能连这个也不知道。
-
找qgsattributetableview.cpp。头文件定义函数,变量。这些函数具体的实现就在cpp文件里了。我们得去GitHub上搜索“qgis”找到其开源数据直接下载下来。找到src文件夹,里面存放了各种功能的具体实现。
- 两个文件都找到了,在我们的项目“QGISEdit”上,分别在头文件夹(Header Files)和源文件夹(Source Files)下建立一个新的头文件(.h)和源文件(.cpp)。可以直接把前一步找到的文件对应复制进去。当然,会报很多错。这里总结一下:
- 首先要把类名改为你的文件名。比如原文件里都是qgsattributetableview::xxxx;这里当然要改成“你的文件名::xxx"。这样就大概消去一大半错误了
- 其次,头文件里刚开始对类的声明时,去掉原生的“ CORE_EXPORT”。为什么呢,因为不去掉运行就不通过,其深层次原因暂时还没研究出来。
-
版本问题:找到的头文件和cpp文件可能版本与现有的项目不一样。举个例子:
老版本(2.x)的cpp文件里,可能需要用到context这个变量。但是QGIS新版本(3.x)里,对于maplayerActions这个函数,可能就不要context这个入参了。这种问题需要自己“转到定义”去看其源代码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T21MbfHZ-1675499363947)(E:\TyporaImage\image-20221211214053363.png)]
- 一番操作后,总算可以运行通过。这个时候就需要来观察一下这两个文件了。
- 头文件里,再函数后面写override的都可以在cpp里对它进行重写。比如鼠标按钮按下,键盘按下按钮等等。特别注意,这里的contextMenuEvent是鼠标右击事件
- cpp文件里的功能就多了。有些可以“顾名思义”。这里特别注意一个函数:selectedFeaturesIds(),这个太重要了。它的功能返回选中数据的id!当然这个函数本来目的应该是为了给记录排序做准备。
QList<QgsFeatureId> QGISEditQgsAttributeTableView::selectedFeaturesIds() const
{
// In order to get the ids in the right sorted order based on the view we have to get the feature ids first
// from the selection manager which is in the order the user selected them when clicking
// then get the model index, sort that, and finally return the new sorted features ids.
const QgsFeatureIds featureIds = mFeatureSelectionManager->selectedFeatureIds();
QModelIndexList indexList;
for (const QgsFeatureId &id : featureIds)
{
const QModelIndex index = mFilterModel->fidToIndex(id);
indexList << index;
}
std::sort(indexList.begin(), indexList.end());
QList<QgsFeatureId> ids;
for (const QModelIndex &index : indexList)
{
const QgsFeatureId id = mFilterModel->data(index, QgsAttributeTableModel::FeatureIdRole).toLongLong();
ids.append(id);
}
return ids;
}
- 删除功能的实现:https://www.cnblogs.com/hik-wxy/p/15901555.html。在QGIS资源贫瘠的网络,好心大哥提供了关于删除元素的只言片语。另外,比较幸运的是,“mFilterModel”函数可以帮我们获取我们当前正在操作的图层!这省了太多的事了。
现在把ID改为具体的0,1,2就可以在触发事件的时候,删除对应的记录和图层数据。
QgsVectorLayer *vlayer = mFilterModel->layer();
//启用编辑
vlayer->startEditing();
vlayer->deleteFeature(QgsFeatureId(对应的ID));
vlayer->updateExtents();
//提交修改
vlayer->commitChanges();
- 关键就变为,怎么获取我们选中要素的ID?这个折腾了好久。戏剧性的是到最后通过qDebug() <<调试发现,在cpp文件里已经写好一个函数来确定选中的元素ID了。这就是之前提到的**selectedFeaturesIds()**函数。它返回了一个选中元素的id。注意一定要给这个函数带括号,这样返回的才是返回值本身。
但是,这不能拿过来直接用。因为再qDebug() <<一下这个返回值,你会返现,它返回的是(id)而我只要id。别小看这个括号,C++就是这么霸道,有没有括号本质上是他们的数据类型不一样,而C++有时候不想互相转换的数据类型,你是想破脑袋都无法进行数据类型的转换的。
这里需要看一下他们各自的数据类型。利用一个很好用函数:
qDebug() << typeid(QgsFeatureId("1")).name();//调试时输出QgsFeatureId所需的数据类型
qDebug() << typeid(selectedFeaturesIds()).name();//调试时输出selectedFeaturesIds()的数据类型
- 发现QgsFeatureId()需要的是一个int就行,而**selectedFeaturesIds()**返回的是一个QList类型的数据。具体情况可以利用qDebug() <<反复输出比较。
现在只需要吧QList里的数据读出来就好了:https://blog.csdn.net/qq_41708281/article/details/124660896。这个时候再输出调试,发现输出的就是一个完整的数字了。
QMutableListIterator<qint64>i(selectedFeaturesIds()); //创建读写器
qDebug() << i.next();
数据访问冲突的问题
- 参考:https://blog.csdn.net/kenfan1647/article/details/119812828
如果这么简单就结束,那就太天真了。C++是一门非常严谨的语言。
你会发现,这个时候,选择id为2的数据,点击删除,会报错“this is nullptr”。为什么呢?
原因是,当你鼠标选中id为2的数据时,这条记录就被锁定了,不允许你的删除事件访问这条数据了。可以细看一下mousepress的事件以及对应的selectrow事件(这里先填坑)。那怎么办呢,最直接的想法就是,当我删除的时候,在删除事件的开头,取消对记录的选择。
坑来了。这里取消对数据的选择指的是取消对索引(index)的选择,而不是selection本身,别把这两个方法给用混了。
selectionModel()->clearCurrentIndex();
其中,selecttionModel来源于QItemSelectionModel。
QItemSelectionModel *selectionModel() const;
完整代码奉上:
selectionModel()->clearCurrentIndex();
QMutableListIterator<qint64>i(selectedFeaturesIds()); //创建读写器;selectedFeaturesIds()是一个集合方便多选
//从qint64 位转为 int类型
int id = static_cast<int>(i.next());
qDebug() << id; //输出id 看看它的格式
QgsVectorLayer *vlayer = mFilterModel->layer();
//启用编辑
vlayer->startEditing();
vlayer->deleteFeature(QgsFeatureId(id));
vlayer->updateExtents();
//提交修改
vlayer->commitChanges();
return;
实时更新
最好的效果是,删除记录后,画布对应的图层上的几何要素,也要删除。这就涉及到更新画布的问题。
-
首先把一开始的错误思路摆出来(可能走的通,但是我陷进死胡同了):因为画布(m_mapCanavas)变量一开始在QGISEdit.h上就定义好了,在QGISEdit.cpp,FileProcess.cpp,AttributeEdit.cpp上一直继承的都是QGISEdit.h的变量。但是现在,删除记录的操作写在QGISEditqgsattributetableview.h的头文件下,所以一开始的想法是怎么在一个框架下引用或修改另一个框架的变量(m_mapCanavas)。听上去不难,但是C++严谨的算法,限制了这种随意的发挥。各种尝试包括声明头文件的跨用,提炼出更新画布的方法等,都失败了。
-
正确的做法:不要想着去跨用变量了。在QGISEditqgsattributetableview的文件下,直接有办法获取当前属性表正在操作者的图层和画布!还是那个QgsAttributeTableFilterModel包,转到它的头文件QgsAttributeTableFilterModel.h。发现里面已经有写好获取当前图层和画布的方法。
QgsAttributeTableFilterModel *mFilterModel = nullptr;
这就避免去跨用变量。实现当前文件也可以获取同一图层,画布变量的关键操作。
代码如下:
void QGISEditQgsAttributeTableView::deleteItem()
{
selectionModel()->clearCurrentIndex(); //清除索引,消除对选中数据的读取
QMutableListIterator<qint64>i(selectedFeaturesIds()); //创建读写器;selectedFeaturesIds()是一个集合,方便多选
//从qint64 位转为 int类型
int id = static_cast<int>(i.next());
qDebug() << id;
QgsVectorLayer *vlayer = mFilterModel->layer();
//启用编辑
vlayer->startEditing();
vlayer->deleteFeature(QgsFeatureId(id));
vlayer->updateExtents();
//提交修改
vlayer->commitChanges();
QgsMapCanvas* m_mapCanvas=mFilterModel->mapCanvas();
QList<QgsMapLayer *> mapCanvasLayerSet; // 地图画布所用的图层集合
m_mapCanvas->setExtent(vlayer->extent());
mapCanvasLayerSet.append(vlayer);
m_mapCanvas->setLayers(mapCanvasLayerSet);
m_mapCanvas->refresh();
return;
}
删除多条数据
前面的工作都是删除一条数据。因为使用的是函数deleteFeature(QgsFeatureId(id))。转到头文件qgsvectorlayer.h。可以看待有与之对应的一个方法叫deleteFeatures。根据头文件中的解释,它是可以删除多条数据(集合set的形式)。
按照这个思路,我们输入的元素应该也是一个集合(QgsFeatureIds)。索性,源代码中有写好的源代码来获取当前表格中选中的元素的集合。
const QgsFeatureIds featureIds = mFeatureSelectionManager->selectedFeatureIds();
属性数据修改
时间周期最长的一个功能,还是那句话,根本原因就在于不懂QT 不懂C++。
想要实现的功能是双击表格中的单元格 然后就可以进入修改数据,按下回车 或者点击空白处就可以保存编辑。图层和表格数据都保存修改后的内容。
实现思路
有很多思路,比如据说可以直接继承Qwidget的方法,但是试了走不通。
直到看到这边文章,它的思想就是当你双击一个按钮的时候,会出现一个QLineEdit(类似于C#中的输入文本框)。设置这个文本框的位置和大小,让它恰好覆盖住当前的单元格。用户输入值(其实是输入在了文本框中)后再双击空白处,删除了文本框。我们再获取输入的数据进行修改。这样,一个看起来是双击修改属性数据的功能就完成了。
先写一个双击单元格激发的事件
//双击除表头和列头除外的区域都会触发事件 以此为基础进行属性修改
void QGISEditQgsAttributeTableView::mouseDoubleClickEvent(QMouseEvent *event)
生成输入文本框
先获得当前点击的行列位置(是从0行0列开始的)
const QModelIndex index = indexAt(event->pos());
int row = index.row();
int column = index.column();
生成一个QlineEdit,注意要设置父窗口(setParent(this)),其中这个this就是指的表格(即Pview 它继承了自定义的QGISEditqgsattributetableview这个类)
medit = new QLineEdit;
medit ->setParent(this);
另外我们要获得本来表格的内容,并使得双击后,文本框的初始内容为原来表格中的内容。
QVariant text = index.data();
medit->setText(text.toString());
如何让生成的文本框正好覆盖住表格的位置呢,这其实是一个数学问题,我们既然获得了表格的行列信息,而且每行每列的行宽 行高都是固定的。根据简单的数学公式即可算出它的“坐标”,然后使用setGeometry函数设置好对应的文本框位置
int width = this->columnWidth(0); //单元格宽度 实时获取!
int height = this->rowHeight(0); //单元格宽度 实时获取!
int posX =20+ width*column;
int posY = 30 + height*row;
medit->setGeometry(posX, posY, width, height); //开始位置(200,00) 宽高300,30
修改数据
这个其实不难 网上有示例代码。使用medit->text()获取文本框中的内容(当然要进行一个类型转换),然后使用原生的changeAttributeValue方法进行修改,最后进行图层的更新等。
QString str = medit->text();
QVariant newContent(str);
qDebug() << newContent;
vlayer->startEditing();
vlayer->changeAttributeValue(Editrow, Editcolumn, newContent);
vlayer->updateExtents();
//提交修改
vlayer->commitChanges();
QgsMapCanvas* m_mapCanvas = mFilterModel->mapCanvas();
m_mapCanvas->setExtent(vlayer->extent());
m_mapCanvas->refresh();
算法逻辑
要把上面的功能放在不同的事件下。有三个问题。
- 如果之前有一个medit,我们再双击另一个单元格,那我们要删除之前的medit,再生成一个medit(就是用户连续修改2个及以上数据的情景)。这里我们使用了一个布尔类型的isMeditExit进行控制。
- 双击空白处时也一样。如果已经有一个medit出现,双击空白处就要读取文本框中的数据,然后同样的删除它。同样的关闭表格后,也要使isMeditExit变为FALSE。
- 这个获得行列row column因为在if语句外面定义,所以为了代码的逻辑和功能的合理,需要来一个中间变量Editrow、Editcolumn。不然会导致你双击一个单元格后,再双击另一个 文本框还是生成在之前的位置。
双击表格内容的完成代码如下:个人觉得算法有待完善,不应该套if语句。
int Editrow;
int Editcolumn;
//双击除表头和列头除外的区域都会触发事件 以此为基础进行属性修改
void QGISEditQgsAttributeTableView::mouseDoubleClickEvent(QMouseEvent *event)
{
qDebug() << Editrow;
qDebug() << Editcolumn;
const QModelIndex index = indexAt(event->pos());
int row = index.row();
int column = index.column();
QgsVectorLayer *vlayer = mFilterModel->layer();//点击在单元格上
if (row >= 0 && column>0)
{
Editrow = row;
Editcolumn = column;
if (!isMeditExit)
{
medit = new QLineEdit;
medit ->setParent(this);
QVariant text = index.data();
medit->setText(text.toString());
// 提交当前项的数据
int width = this->columnWidth(0); //单元格宽度 实时获取!
int height = this->rowHeight(0); //单元格宽度 实时获取!
int posX =20+ width*column;
int posY = 30 + height*row;
medit->setGeometry(posX, posY, width, height); //开始位置(200,00) 宽高300,30
medit->setVisible(true);
isMeditExit = true;
}
//之前存在一个medit 就要删除之前的medit 这边的算法要完善 暂且先用if语句
else if (isMeditExit)
{
delete medit;
medit = new QLineEdit;
medit->setParent(this);
QVariant text = index.data();
medit->setText(text.toString());
QgsVectorLayer *vlayer = mFilterModel->layer();
int width = this->columnWidth(0); //单元格宽度 实时获取!
int height = this->rowHeight(0); //单元格宽度 实时获取!
int posX = 20 + width*column;
int posY = 30 + height*row;
medit->setGeometry(posX, posY, width, height); //开始位置(200,00) 宽高300,30
medit->setVisible(true);
}
}
//没点击在单元格上
else
{
if (isMeditExit)
{
qDebug() << medit->text();
QString str = medit->text();
QVariant newContent(str);
qDebug() << newContent;
vlayer->startEditing();
vlayer->changeAttributeValue(Editrow, Editcolumn, newContent);
vlayer->updateExtents();
//提交修改
vlayer->commitChanges();
QgsMapCanvas* m_mapCanvas = mFilterModel->mapCanvas();
m_mapCanvas->setExtent(vlayer->extent());
m_mapCanvas->refresh();
delete medit;
isMeditExit = false;
}
setSelectionMode(QAbstractItemView::NoSelection);
QTableView::mouseDoubleClickEvent(event);
setSelectionMode(QAbstractItemView::ExtendedSelection); //让你可以再次选择
//清除高亮
QItemSelection selection;
selection.append(QItemSelectionRange(mFilterModel->index(0, 0), mFilterModel->index(mFilterModel->rowCount() - 1, 0)));
mFeatureSelectionModel->selectFeatures(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
//清除index
selectionModel()->clearCurrentIndex();
}
}
几何数据编辑
添加多边形
- 参照 QGIS 和 ArcGIS,用一个按钮控制编辑会话的开始和结束,即控制图层处于编辑状态与否。按下表示处于编辑状态,弹起处于非编辑状态;
- 编辑状态下,激活“绘制多边形”按钮,点击激活添加多边形地图工具,弹起取消激活;
- 添加多边形地图工具激活时,用户可以在画布上点击绘制多边形:左键添加节点,右键结束当前多边形绘制。
-
原生的QgsMapToolCapture 的继承链为:
QgsMapTool <-- QgsMapToolEdit <-- QgsMapToolAdvancedDigitizing <-- QgsMapToolCapture
为了方便的操作工具所属的图层和画布,县创建一个AddPolygonTool.h的文件,在该头文件中创建一个
QgsMapToolEdit
的派生类,而QgsMapToolEdit又继承自qgsmaptooledit.h文件。代码如下:class AddPolygonTool : public QgsMapToolEdit { public: AddPolygonTool(QgsMapCanvas* pMapCanvas); // 清除当前的 RubberBand void clearRubberBand(); protected: // 重写 QgsMapTool 的鼠标移动事件 void canvasMoveEvent(QgsMapMouseEvent * e) override; // 重写 QgsMapTool 的鼠标点击事件 void canvasPressEvent(QgsMapMouseEvent * e) override; private: // 当前正在工作的 RubberBand QgsRubberBand* mpRubberBand = nullptr; // 记录是否正在绘制中,构造函数中初始化为 false bool mIsDrawing; };
-
计划是重写鼠标点击事件:如当前无工作中的 RubberBand(绘制的线),则创建并存入 mpRubberBand 并点下第一个点。之后用户连续点击鼠标左键往 mpRubberBand 加入点,直到点击鼠标右键(点击鼠标右键表示停止绘制)。如果此时有效点数小于 3,不足以构成多边形,则丢弃编辑。否则将 mpRubberBand(折线) 输出为新的 QgsFeature,加入受编辑的 QgsVectorLayer 之中。
注意
- QGIS原本有原生的QgsMapToolCapture接口绘制多边形,但是使用这个工具需要很麻烦的操作过程,太过累赘。详细原因,参考网址:http://www.caiyi.tech/post.html?postKey=qgis_dev_8_add_feature 。所以教程中追溯到接口的上层代码,直接手动实现绘制多边形的功能。
运行报错总结
- 遇到报错“无法打开XX.exe”:https://blog.csdn.net/weixin_44604710/article/details/126030851。就是暴力执行程序次数太多,导致后台进程中的exe重复出现。
- 有一个关于moc文件的大坑,有时候运行软件,突然报错,你的moc文件同时有两个。一看确实是。在它提示的错误里面确实有两个同名的moc文件,而且这两个moc文件每运行一次都会同时在x64/release文件夹下自动生成一下。如果你同名的话势必会起冲突。冲突的效果就是程序运行但跳不出窗口,报警告。
坑在于,别看网上的主流答案改QT设置的moc目录。因为VS2015和现版的QT你是找不到网上解决方案里的那个窗口的
正解在于修改C++的输出文件路径(完美解决)。
- 再填一坑。在VS2015里面如果报错,“无法加载pdb文件”,前面已经贴了。但是如果不进入VS2015,直接打开release文件下的执行文件.exe。依旧报错无法加载pdb文件云云,按照网上说法,把jvm.dll,qgis_core.dll等依次加进去。但是还是会报”无法定位程序于动态链接库云云“。这个时候网上就没有参考文献,直到看到某个博客下的评论。原来还需要把QSGeo4/bin下的所有dll文件都复制到.exe的同目录下,至此问题解决。
- 留个坑 为什么override随便起一个doubleclick的事件 真的doubleclick了?
- 小问题,打开其他GitHub上已有的项目时,中文乱码。这个需要更改Windows的语言设置并重启电脑。
- 之前配置环境留下的隐患,运行项目,报错某某文件(xxx.h)不在系统生成的文件当中。转到该文件,发现打不开该文件(QT的文件),这很不合理。推测出是环境配置问题。打开项目设置,选择C/C++—>附加包含目录。把缺失的文件路径添加路径即可(比如QTSvg,QTXml等等)
22scm%2522%253A%252220140713.130102334…%2522%257D&request_id=167041593916800182169275&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-80080615-null-null.142v68control,201v4add_ask,213v2t3_control2&utm_term=QGIS%20%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91&spm=1018.2226.3001.4187)。原来还需要把QSGeo4/bin下的所有dll文件都复制到.exe的同目录下,至此问题解决。