QGis二次开发基础 — 属性表格

属性表想必是每个GIS系统必备的功能,也正是因为GIS矢量数据支持各种各样的属性与针对属性的操作功能,才使得GIS矢量图形与普通的绘画图形具有根本的区别。今天来探讨一下用QGis实现矢量图形的属性表显示功能。
属性表)


 

写在前面

本来核心的代码就几句,直接贴上来似乎就能解决问题。但是本着严谨的态度,还是详细的分析一下机理。借此也阐述出我对开源代码的学习方法,并不是我的方法就有多么好,而是希望大家能够从中看到一些可供借鉴的地方,同时也让我与大家产生了交流,完善我自己的方法。因此后续的内容分为两块,一个是详细的介绍使用QGis属性表的几个类和它们的调用机理,另一个是展示如何通过“抄袭”的办法直接将QGis的属性表窗口加入到我们的工程中来。

认识几个类

首先,还是来介绍一下所需要用到的几个与属性表相关的类以及它们的作用,它们分别是:

View 和 Model

QgsAttributeTableView 和 QgsAttributeTableModel 这两个类是按照 Qt 的MVC(Model-View-Controller)架构来创建的。QgsAttributeTableView 是继承于 QTableView 用于展示表格控件。QgsAttributeTableModel 继承于 QAbstractTableModel,用于给显示的 View 控件提供数据支持。关于MVC架构的资料,可以看 Qt 的官方帮助文档或者百度百科。这里引用Qt Assistant中的一个图形,方便理解。

这里写图片描述

简单的说,也就是数据存储在 Model 中,用 View 来展示给用户。对于属性表来说,QgsAttributeTableModel 里面存储着矢量数据的属性数据, 通过绑定到 QgsAttributeTableView 上展示给用户。

QgsVectorLayerCache

这个类的作用是加载并缓存 QgsVectorLayer 矢量数据中的要素。看看它的构造函数形式

QgsVectorLayerCache( QgsVectorLayer* layer, int cacheSize, QObject* parent = NULL );

第一个参数是矢量图层,第二个参数控制缓存要素的最大数量,当图层中的要素数量超过这个最大数量,就会有一部分要素不做缓存,而是用到的时候再加载。缓存可以提供要素的设置、查询等操作。

从这个构造函数可以知道,只需要一个 QgsVectorLayer 类就能构造出对应的缓存类。

QgsAttributeTableFilterModel

这里写图片描述

QgsAttributeTableFilterModel 继承自 QgsFeatureModel, 用来控制 QgsAttributeTableModel 当中的数据显示方式,类似一个“筛子”一样来选择要素的显示与隐藏。这么说也许不太直观,看一看类中的一个enum应该就理解了。

enum FilterMode
{
   ShowAll, // 全部显示
   ShowSelected, // 只显示选择的要素
   ShowVisible, // 只显示可见要素
   ShowFilteredList, // 只显示过滤掉的要素
   ShowEdited  // 只显示编辑的要素
};

再来看看它的构造函数原型

QgsAttributeTableFilterModel( QgsMapCanvas* canvas, QgsAttributeTableModel* sourceModel, QObject* parent = 0 );

需要一个 QgsMapCanvas 和一个 QgsAttributeTableModel 作为参数构造。用到 QgsAttributeTableModel 是自然的,因为需要原始数据来“筛选”。而需要 QgsMapCanvas 则是便于提供用户从地图显示控件上进行要素“筛选”的功能。

QgsEditorWidgetRegistry

这个类看起来好像跟属性表格没什么关系,但少了它属性表格是不能够被正确初始化的,虽然代码不会报错,但属性表格会变成下面这样

这里写图片描述

表格中的要素个数是正确显示的,但是属性字段却没有显示。出现这种错误且代码不报错真是很令人头疼,如果不明白机理,都不知道是错在哪里(老实说,我就被这个错误折磨过,好在功夫不负有心人)。

QgsEditorWidgetRegistry 这个类管理着所有可编辑控件的创建工厂。什么意思呢?QGis中对于提供给用于编辑数据的控件都是由不同的工厂类创建的,这个属于软件工程设计模式中的工厂模式。而对所有的工厂创建一个注册管理的类,通过不同的传入参数调用不同的工厂进行构建,在这里就是 QgsEditorWidgetRegistry 类。具体都有哪些用于编辑的控件,看一看源代码就一目了然了。下面是 QgsEditorWidgetRegistry 类中的 initEditors 方法的实现代码。

void QgsEditorWidgetRegistry::initEditors( QgsMapCanvas *mapCanvas, QgsMessageBar *messageBar )
{
  QgsEditorWidgetRegistry *reg = instance();
  reg->registerWidget( "Classification", new QgsClassificationWidgetWrapperFactory( tr( "Classification" ) ) );
  reg->registerWidget( "Range", new QgsRangeWidgetFactory( tr( "Range" ) ) );
  reg->registerWidget( "UniqueValues", new QgsUniqueValueWidgetFactory( tr( "Unique Values" ) ) );
  reg->registerWidget( "FileName", new QgsFileNameWidgetFactory( tr( "File Name" ) ) );
  reg->registerWidget( "ValueMap", new QgsValueMapWidgetFactory( tr( "Value Map" ) ) );
  reg->registerWidget( "Enumeration", new QgsEnumerationWidgetFactory( tr( "Enumeration" ) ) );
  reg->registerWidget( "Hidden", new QgsHiddenWidgetFactory( tr( "Hidden" ) ) );
  reg->registerWidget( "CheckBox", new QgsCheckboxWidgetFactory( tr( "Check Box" ) ) );
  reg->registerWidget( "TextEdit", new QgsTextEditWidgetFactory( tr( "Text Edit" ) ) );
  reg->registerWidget( "ValueRelation", new QgsValueRelationWidgetFactory( tr( "Value Relation" ) ) );
  reg->registerWidget( "UuidGenerator", new QgsUuidWidgetFactory( tr( "Uuid Generator" ) ) );
  reg->registerWidget( "Photo", new QgsPhotoWidgetFactory( tr( "Photo" ) ) );
  reg->registerWidget( "WebView", new QgsWebViewWidgetFactory( tr( "Web View" ) ) );
  reg->registerWidget( "Color", new QgsColorWidgetFactory( tr( "Color" ) ) );
  reg->registerWidget( "RelationReference", new QgsRelationReferenceFactory( tr( "Relation Reference" ), mapCanvas, messageBar ) );
  reg->registerWidget( "DateTime", new QgsDateTimeEditFactory( tr( "Date/Time" ) ) );
}

可以看到有“Classification”、“Value Map”、“Check Box”、“Text Edit”等等控件。看到这里,就不难发现 QgsEditorWidgetRegistry 和属性表格有什么关系了,我们用于编辑属性数据的“Text Edit”控件正是由 QgsEditorWidgetRegistry 管理的。

调用机理

明白了上面所列出的类,要显示属性表格的功能就容易了。

最开始要做的是用 QgsMapCanvas 初始化编辑控件

QgsEditorWidgetRegistry::initEditors( mypMapCanvas );

然后再开始构造并显示属性表格。

属性表格自然需要一个矢量图层(修改下面的文件路径字符串以及图层名称)

QString myLayerPath         = "D:/Data/qgis_sample_data/shapefiles/airports.shp"; // 修改这里
QString myLayerBaseName     = "airports"; //图层名称;

QgsVectorLayer* mypLayer = new QgsVectorLayer( myLayerPath, myLayerBaseName, "ogr", false );

然后将它 Cache 进 QgsVectorLayerCache 类中以便用于构造
QgsAttributeTableModel 类。

QgsVectorLayerCache* lc = new QgsVectorLayerCache( mypLayer, mypLayer->featureCount() );

接着分别创建 QgsAttributeTableView 和 QgsAttributeTableModel 用于显示属性表格

QgsAttributeTableView* tv = new QgsAttributeTableView();
QgsAttributeTableModel* tm = new QgsAttributeTableModel( lc );

构造好 QgsAttributeTableModel 一定不要忘记加载一下图层,构造函数没有默认完成这个工作。

tm->loadLayer();

现在构建好的 QgsAttributeTableModel 不能直接与 QgsAttributeTableView 进行绑定,而是需要一个 Filter 在中间做“筛选”,因此需要一个 QgsAttributeTableFilterModel 来建立 QgsAttributeTableModel 和 QgsAttributeTableView 的之间联系。当然,不要忘记构造 QgsAttributeTableFilterModel 类还需要一个 QgsMapCanvas。

QgsAttributeTableFilterModel* tfm = new QgsAttributeTableFilterModel( mypMapCanvas, tm, tm );
tv->setModel( tfm );

最后,将属性表格显示出来就好了

tv->show();

显示效果如下图:
这里写图片描述

测试代码

照例,给出可运行的测试代码

// main.cpp

#include<QtGui/QApplication>
#include<qgsapplication.h>
#include<qgsproviderregistry.h>
#include<qgssinglesymbolrendererv2.h>
#include<qgsmaplayerregistry.h>
#include<qgsvectorlayer.h>
#include<qgsmapcanvas.h>
#include<QString>
#include<QApplication>
#include<QWidget>
#include <QStringList>

#include<QMessageBox>
#include<QObject>
#include <QList>
#include <QFileInfoList>
#include <QDir>
#include <QLibrary>
#include <QDebug>

#include <qgsattributetableview.h>
#include <qgsattributetablemodel.h>
#include <qgsvectorlayercache.h>
#include <qgsattributetablefiltermodel.h>
#include "qgseditorwidgetregistry.h"

int main( int argc, char *argv[] )
{
    QgsApplication myApp( argc, argv, true );
    QgsApplication::setPrefixPath( "C:/Program Files/qgis2.8.1", true );
    QgsApplication::initQgis();

    QgsProviderRegistry* provider = QgsProviderRegistry::instance();

    QString myLayerPath         = "D:/Data/qgis_sample_data/shapefiles/airports.shp"; // 修改文件路径字符串
    QString myLayerBaseName     = "airports"; //图层名称;

    QList<QgsMapLayer*> myList;
    QgsVectorLayer* mypLayer = new QgsVectorLayer( myLayerPath, myLayerBaseName, "ogr", false );
    if ( !mypLayer )
    {
        return 0;
    }
    if ( !mypLayer->isValid() )
    {
        QMessageBox::information( 0, "", "layer is invalid" );
        mypLayer->setProviderEncoding( "System" );
        myList << mypLayer;
    }
    QgsMapLayerRegistry::instance()->addMapLayer( mypLayer );
    QList<QgsMapCanvasLayer> myLayerSet;
    myLayerSet.append( QgsMapCanvasLayer( mypLayer ) );

    QgsMapCanvas* mypMapCanvas = new QgsMapCanvas( 0, 0 );
    mypMapCanvas->setExtent( mypLayer->extent() );
    mypMapCanvas->enableAntiAliasing( true );
    mypMapCanvas->setCanvasColor( QColor( 255, 255, 255 ) );
    mypMapCanvas->freeze( false );
    mypMapCanvas->setLayerSet( myLayerSet );
    mypMapCanvas->setVisible( true );
    mypMapCanvas->refresh();

    // 属性表格
    QgsEditorWidgetRegistry::initEditors( mypMapCanvas ); // 一定要做这步,其实最好是main函数一开始就执行这句

    QgsVectorLayerCache* lc = new QgsVectorLayerCache( mypLayer, mypLayer->featureCount() );

    QgsAttributeTableView* tv = new QgsAttributeTableView();
    QgsAttributeTableModel* tm = new QgsAttributeTableModel( lc );
    tm->loadLayer(); // 一定不要忘记,否则model里面没有图层的属性数据

    QgsAttributeTableFilterModel* tfm = new QgsAttributeTableFilterModel( mypMapCanvas, tm, tm );
    tfm->setFilterMode( QgsAttributeTableFilterModel::ShowAll );
    tv->setModel( tfm );

    tv->show();

    return myApp.exec();
}

“抄袭”QGis属性表

上面讲的属性表格未免有点简陋,只能做属性展示的功能而已,而要自定义一个功能齐全的属性表也不容易。QGis的原生属性表就很漂亮,而且功能也非常完备,直接引入那个属性表到我们的工程中来就省事多了。下面简单介绍一下如何进行“抄袭”,只做一个大概的提示以及操作,并不详细阐述原理细节。

首先,要从QGis源码文件夹中找到“qgsattributetabledialog.ui” 和 “qgsdualviewbase.ui” 这两个文件

这里写图片描述

拷贝到我们的工程中来

这里写图片描述

选中它们,并鼠标右键,选择 “编译”,或者快捷键 “Ctrl + F7”,会生成两个以 “ui”开头命名的文件

这里写图片描述

这两个文件是Qt自动生成的,不用管它。然后拷贝QGis源码文件夹下 “gui”文件夹中的 “attributetable”文件夹,到我们的开发包 “include”文件夹下。

这里写图片描述

创建一个新类继承那个属性表格的 ui

#include <QDialog>
#include "ui_qgsattributetabledialog.h"

#include <qgsvectorlayer.h>


// 属性表对话框
class qgis_devattrtableDialog : public QDialog, public Ui::QgsAttributeTableDialog
{
    Q_OBJECT

public:
    qgis_devattrtableDialog( QgsVectorLayer* theVecLayer, QWidget *parent = 0, Qt::WindowFlags flags = Qt::Window );
    ~qgis_devattrtableDialog();
}

并在实现文件的构造函数中写入

// 初始化DualView
mMainView->init( mLayer, qgis_dev::instance()->mapCanvas());

最后,在任意的地方调用这个 attributeTableDialog, 并给它一个矢量图层就好了。

void qgis_dev::openAttributeTableDialog()
{
    QgsVectorLayer* mylayer = qobject_cast<QgsVectorLayer*>( activeLayer() ); // 这句改成任意矢量图层
    if ( !mylayer ) { return; }
    qgis_devattrtableDialog* d = new qgis_devattrtableDialog( mylayer, this );
    d->show();
}

运行之后可以看到QGis的属性表格对话框

这里写图片描述

这里写图片描述

但是现在这些按钮都还没有功能,要添加功能只需要打开QGis源代码中的 “qgsattributetabledialog.h” 以及 “qgsattributetabledialog.cpp”文件,依葫芦画瓢,为 “qgis_devattrtableDialog”这个类添加一些功能代码,不想要的功能就删掉,想要的就Copy过来,基本就能完成这个属性表格的制作了。或者你再懒一点,你直接把”qgsattributetabledialog.h” 和 “qgsattributetabledialog.cpp”这两个文件拷贝过来修改,而不用刚刚创建的 “qgis_devattrtableDialog “类也是可以的。但我建议还是自己构造一个类模仿着源代码做一次,不要全部Copy,毕竟这样可以深入代码的底层,让自己对功能的实现机理有了一个把握。

终于写完了,还是放上最后一句:如有错误请不吝指正,谢谢阅读!

© 版权声明
THE END
喜欢就支持一下吧
点赞120 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容