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,畢竟這樣可以深入代碼的底層,讓自己對功能的實現機理有了一個把握。

終於寫完了,還是放上最後一句:如有錯誤請不吝指正,謝謝閱讀!

發佈了33 篇原創文章 · 獲贊 41 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章