QGIS 二次开发笔记(2)——显示图层

基于 QGIS 二次开发,最首要的功能就是显示图层。这是个看似非常简单的功能,但是在 QGIS 中写了非常复杂的代码,以支持各种数据源。 但是我们在二次开发中,一般不会支持那么多的数据源。这篇博客首先以 ESRI Shapefile 数据源为例,展示加载图层的过程。

博客以创建好的工程开始,创建工程的过程网上资料很多,这里就不再赘述了。

添加地图框

要想显示图层,首先要有一个显示图层的地方。在 QGIS SDK 中,使用类 QgsMapCanvas 显示地图。这个类需要 QT 中的 svg 组件,即

1
2
# QgsSdkApp.pro
QT += core gui xml svg

如果我们想在 QMainWindow 派生类(我的工程中创建的类名是 QgsSdkApp ,以后直接用这个名称)添加一个 QgsMapCanvas 类型的组件, 有以下三种方法:

  1. 添加插件的方式:在 QT Designer 中添加插件,在 QT Designer 中绘制(我没有实现成功,理论上可以)
  2. 提升类型的方式:在 QT Designer 中使用“提升”功能,将 QWidget 组件提升为 QgsMapCanvas 类型
  3. 手动创建的方式:在 QgsSdkApp.cpp 中手动创建 QgsMapCanvas 类型的对象,添加到窗口中

下面一一展示。

提升类型的方式

选择一个组件,右键单击之后,单击“提升为”按钮,可以弹出提升窗口部件的对话框。

上面这个对话框,现在 1 所示的位置输入要提升的类型的名称,然后点击 2 位置上的按钮,在上面的列表上选中刚刚添加的提升类型, 点击 3 位置上的按钮,即可将该组件提升为指定的类型(即 QgsMapCanvas 类型)。

然后,在 cpp 文件中已经可以访问到这个类型的组件了。

手动创建的方式

手动创建的方式就是在窗口类的构造函数中,构造一个 QgsMapCanvas 类型的对象,然后添加到窗口上。 这种情况下和其他在 QT 中手动创建对象没有差别,和其他一样处理即可。

添加图层

添加了地图框(在类内使用一个指针 mMapCanvas ,指向这个地图框)之后,下面就可以来添加图层了。 首先以 ESRI Shapefile 为例,介绍一下添加 QgsVectorLayer 的基本方法。

添加矢量图层的方法

在 QgsSdkApp.ui 中可以添加一个 action ,并拖到工具栏上创建工具按钮。点击这个按钮后开始添加图层。 这个过程网上有很多教程,这里就不再赘述了。

如果要添加 ESRI Shapefile 图层,我们需要先选择这个文件。我们可以直接弹出一个文件选择对话框。 我们以选择的文件路径作为数据源的路径,文件名(不含扩展名)为图层名称。

1
2
3
4
5
6
7
8
9
10
void QgsSdkApp::on_actionShp_Layer_triggered()
{
QString filePath = QFileDialog::getOpenFileName(this, tr("Open ESRI Shapefile"), tr(""), tr("ESRI Shapefile (*.shp)"));
QFileInfo fileInfo(filePath);
if (fileInfo.exists())
{
QString fileName = fileInfo.baseName();
addVectorLayer(filePath, fileName, "ogr");
}
}

创建 addVectorLayer() 函数,用于添加图层。我们可以以一种简单的方式添加图层,即直接创建 QgsVectorLayer 对象,并保存到一个列表(mMapLayerList)里。 然后让地图框加载这个列表,即可显示地图。

1
2
3
4
5
6
7
8
9
10
11
12
13
QgsVectorLayer *QgsSdkApp::addVectorLayer(const QString &vectorLayerPath, const QString &name, const QString &providerKey, bool guiWarning)
{
QgsVectorLayer* vectorLayer = new QgsVectorLayer(uri, layerName, providerKey);
mMapLayerList.append(vectorLayer);
mMapCanvas->setLayers(mMapLayerList);
if (mMapLayerList.size() == 1)
{
QgsMapLayer* firstLayer = mMapLayerList.first();
QgsRectangle extent = mMapCanvas->mapSettings().layerExtentToOutputExtent(firstLayer, firstLayer->extent());
mMapCanvas->setExtent(extent);
}
mMapCanvas->refresh();
}

这个函数中除了添加了地图,同时也限制了地图框地显示范围,即设置为第一个图层地显示范围。最后对地图进行了刷新。但是这种方式会遇到很多问题:

  1. 如果将来要设计 Layout ,那么这个地图框的内容无法同步到 Layout 中的地图框中。
  2. 如果图层的投影到地图框的投影有多种转换方式,那么无法选择指定投影方式(尚未实现成功)
  3. 如果图层有子图层,无法选择子图层(ESRI Shapefile 中不会遇到)
  4. 如果图层需要访问验证,无法获取图层(ESRI Shapefile 中不会遇到)

在 QGIS 中,使用以下代码以较为完善地设置添加地图层,同时支持了很多其他功能。函数中创建的图层直接添加到 QgsProject 中,以支持 Layout 等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
QgsVectorLayer *QgsSdkApp::addVectorLayer(const QString &vectorLayerPath, const QString &name, const QString &providerKey, bool guiWarning)
{
QString baseName = QgsMapLayer::formatLayerName( name );

/* Eliminate the need to instantiate the layer based on provider type.
The caller is responsible for cobbling together the needed information to
open the layer
*/
QgsDebugMsg( "Creating new vector layer using " + vectorLayerPath
+ " with baseName of " + baseName
+ " and providerKey of " + providerKey );

// if the layer needs authentication, ensure the master password is set
bool authok = true;
QRegExp rx( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
if ( rx.indexIn( vectorLayerPath ) != -1 )
{
authok = false;
if ( !QgsAuthGuiUtils::isDisabled( messageBar(), messageTimeout() ) )
{
authok = QgsApplication::authManager()->setMasterPassword( true );
}
}

// create the layer
QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
// Default style is loaded later in this method
options.loadDefaultStyle = false;
QgsVectorLayer *layer = new QgsVectorLayer( vectorLayerPath, baseName, providerKey, options );

if ( authok && layer && layer->isValid() )
{
QStringList sublayers = layer->dataProvider()->subLayers();
QgsDebugMsg( QStringLiteral( "got valid layer with %1 sublayers" ).arg( sublayers.count() ) );

// If the newly created layer has more than 1 layer of data available, we show the
// sublayers selection dialog so the user can select the sublayers to actually load.
if ( sublayers.count() > 1 &&
! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) &&
! vectorLayerPath.contains( QStringLiteral( "layername=" ) ) )
{
QList< QgsMapLayer * > addedLayers = askUserForOGRSublayers( layer );

// The first layer loaded is not useful in that case. The user can select it in
// the list if he wants to load it.
delete layer;
layer = addedLayers.isEmpty() ? nullptr : qobject_cast< QgsVectorLayer * >( addedLayers.at( 0 ) );
for ( QgsMapLayer *l : addedLayers )
askUserForDatumTransform( l->crs(), QgsProject::instance()->crs(), l );
}
else
{
// Register this layer with the layers registry
QList<QgsMapLayer *> myList;

//set friendly name for datasources with only one layer
QStringList sublayers = layer->dataProvider()->subLayers();
if ( !sublayers.isEmpty() )
{
setupVectorLayer( vectorLayerPath, sublayers, layer,
providerKey, options );
}

myList << layer;
QgsProject::instance()->addMapLayers( myList );

askUserForDatumTransform( layer->crs(), QgsProject::instance()->crs(), layer );

bool ok;
layer->loadDefaultStyle( ok );
layer->loadDefaultMetadata( ok );
}
}
else
{
if ( guiWarning )
{
QString message = layer->dataProvider() ? layer->dataProvider()->error().message( QgsErrorMessage::Text ) : tr( "Invalid provider" );
QString msg = tr( "The layer %1 is not a valid layer and can not be added to the map. Reason: %2" ).arg( vectorLayerPath, message );
visibleMessageBar()->pushMessage( tr( "Layer is not valid" ), msg, Qgis::Critical, messageTimeout() );
}

delete layer;
return nullptr;
}

// Let render() do its own cursor management
// QApplication::restoreOverrideCursor();

return layer;
}

注意其中的 QgsProject::instance()->addMapLayers( myList ); 一行,其实在 QGIS 中所有图层都是通过 QgsProject 进行管理的,一般来说用户无需自己管理。 QGIS SDK 也提供了 QgsLayerTreeView 等一套类,用于显示图层的层级结构,使用也比较方便。 但是如果有一些特别需要的功能,再进行自己管理。

这个函数中还需要其他几个函数,分别定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
QList<QgsMapLayer *> QgsSdkApp::askUserForOGRSublayers(QgsVectorLayer *layer)
{
QList<QgsMapLayer *> result;
if ( !layer )
{
layer = qobject_cast<QgsVectorLayer *>( activeLayer() );
if ( !layer || layer->providerType() != QLatin1String( "ogr" ) )
return result;
}

QStringList sublayers = layer->dataProvider()->subLayers();

QgsSublayersDialog::LayerDefinitionList list;
QMap< QString, int > mapLayerNameToCount;
bool uniqueNames = true;
int lastLayerId = -1;
const auto constSublayers = sublayers;
for ( const QString &sublayer : constSublayers )
{
// OGR provider returns items in this format:
// <layer_index>:<name>:<feature_count>:<geom_type>

QStringList elements = splitSubLayerDef( sublayer );
if ( elements.count() >= 4 )
{
QgsSublayersDialog::LayerDefinition def;
def.layerId = elements[0].toInt();
def.layerName = elements[1];
def.count = elements[2].toInt();
def.type = elements[3];
// ignore geometry column name at elements[4]
if ( elements.count() >= 6 )
def.description = elements[5];
if ( lastLayerId != def.layerId )
{
int count = ++mapLayerNameToCount[def.layerName];
if ( count > 1 || def.layerName.isEmpty() )
uniqueNames = false;
lastLayerId = def.layerId;
}
list << def;
}
else
{
QgsDebugMsg( "Unexpected output from OGR provider's subLayers()! " + sublayer );
}
}

// Check if the current layer uri contains the

// We initialize a selection dialog and display it.
QgsSublayersDialog chooseSublayersDialog( QgsSublayersDialog::Ogr, QStringLiteral( "ogr" ), this );
chooseSublayersDialog.setShowAddToGroupCheckbox( true );
chooseSublayersDialog.populateLayerTable( list );

if ( !chooseSublayersDialog.exec() )
return result;

QString name = layer->name();

auto uriParts = QgsProviderRegistry::instance()->decodeUri(
layer->providerType(), layer->dataProvider()->dataSourceUri() );
QString uri( uriParts.value( QStringLiteral( "path" ) ).toString() );

// The uri must contain the actual uri of the vectorLayer from which we are
// going to load the sublayers.
QString fileName = QFileInfo( uri ).baseName();
const auto constSelection = chooseSublayersDialog.selection();
for ( const QgsSublayersDialog::LayerDefinition &def : constSelection )
{
QString layerGeometryType = def.type;
QString composedURI = uri;
if ( uniqueNames )
{
composedURI += "|layername=" + def.layerName;
}
else
{
// Only use layerId if there are ambiguities with names
composedURI += "|layerid=" + QString::number( def.layerId );
}

if ( !layerGeometryType.isEmpty() )
{
composedURI += "|geometrytype=" + layerGeometryType;
}

QgsDebugMsg( "Creating new vector layer using " + composedURI );
QString name = fileName + " " + def.layerName;
QgsVectorLayer::LayerOptions options { QgsProject::instance()->transformContext() };
options.loadDefaultStyle = false;
QgsVectorLayer *layer = new QgsVectorLayer( composedURI, name, QStringLiteral( "ogr" ), options );
if ( layer && layer->isValid() )
{
result << layer;
}
else
{
QString msg = tr( "%1 is not a valid or recognized data source" ).arg( composedURI );
visibleMessageBar()->pushMessage( tr( "Invalid Data Source" ), msg, Qgis::Critical, messageTimeout() );
delete layer;
}
}

if ( !result.isEmpty() )
{
QgsSettings settings;
bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), true ).toBool();
bool newLayersVisible = settings.value( QStringLiteral( "/qgis/new_layers_visible" ), true ).toBool();
QgsLayerTreeGroup *group = nullptr;
if ( addToGroup )
group = QgsProject::instance()->layerTreeRoot()->insertGroup( 0, name );

QgsProject::instance()->addMapLayers( result, ! addToGroup );
for ( QgsMapLayer *l : qgis::as_const( result ) )
{
bool ok;
l->loadDefaultStyle( ok );
l->loadDefaultMetadata( ok );
if ( addToGroup )
group->addLayer( l );
}

// Respect if user don't want the new group of layers visible.
if ( addToGroup && ! newLayersVisible )
group->setItemVisibilityCheckedRecursive( newLayersVisible );
}
return result;
}

bool QgsSdkApp::askUserForDatumTransform(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsMapLayer *layer)
{
Q_ASSERT( qApp->thread() == QThread::currentThread() );

QString title;
if ( layer )
{
// try to make a user-friendly (short!) identifier for the layer
QString layerIdentifier;
if ( !layer->name().isEmpty() )
{
layerIdentifier = layer->name();
}
else
{
const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
if ( parts.contains( QStringLiteral( "path" ) ) )
{
const QFileInfo fi( parts.value( QStringLiteral( "path" ) ).toString() );
layerIdentifier = fi.fileName();
}
else if ( layer->dataProvider() )
{
const QgsDataSourceUri uri( layer->source() );
layerIdentifier = uri.table();
}
}
if ( !layerIdentifier.isEmpty() )
title = tr( "Select Transformation for %1" ).arg( layerIdentifier );
}

return QgsDatumTransformDialog::run( sourceCrs, destinationCrs, this, mMapCanvas, title );
}

static QStringList splitSubLayerDef( const QString &subLayerDef )
{
return subLayerDef.split( QgsDataProvider::sublayerSeparator() );
}

static void setupVectorLayer( const QString &vectorLayerPath,
const QStringList &sublayers,
QgsVectorLayer *&layer,
const QString &providerKey,
QgsVectorLayer::LayerOptions options )
{
//set friendly name for datasources with only one layer
QgsSettings settings;
QStringList elements = splitSubLayerDef( sublayers.at( 0 ) );
QString rawLayerName = elements.size() >= 2 ? elements.at( 1 ) : QString();
QString subLayerNameFormatted = rawLayerName;
if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
{
subLayerNameFormatted = QgsMapLayer::formatLayerName( subLayerNameFormatted );
}

if ( elements.size() >= 4 && layer->name().compare( rawLayerName, Qt::CaseInsensitive ) != 0
&& layer->name().compare( subLayerNameFormatted, Qt::CaseInsensitive ) != 0 )
{
layer->setName( QStringLiteral( "%1 %2" ).arg( layer->name(), rawLayerName ) );
}

// Systematically add a layername= option to OGR datasets in case
// the current single layer dataset becomes layer a multi-layer one.
// Except for a few select extensions, known to be always single layer dataset.
QFileInfo fi( vectorLayerPath );
QString ext = fi.suffix().toLower();
if ( providerKey == QLatin1String( "ogr" ) &&
ext != QLatin1String( "shp" ) &&
ext != QLatin1String( "mif" ) &&
ext != QLatin1String( "tab" ) &&
ext != QLatin1String( "csv" ) &&
ext != QLatin1String( "geojson" ) &&
! vectorLayerPath.contains( QStringLiteral( "layerid=" ) ) &&
! vectorLayerPath.contains( QStringLiteral( "layername=" ) ) )
{
auto uriParts = QgsProviderRegistry::instance()->decodeUri(
layer->providerType(), layer->dataProvider()->dataSourceUri() );
QString composedURI( uriParts.value( QStringLiteral( "path" ) ).toString() );
composedURI += "|layername=" + rawLayerName;

auto newLayer = qgis::make_unique<QgsVectorLayer>( composedURI, layer->name(), QStringLiteral( "ogr" ), options );
if ( newLayer && newLayer->isValid() )
{
delete layer;
layer = newLayer.release();
}
}
}

这个函数中还需要以下几个 Getter 函数,我们在窗口中提供对应的组件即可:

  1. QgsMessageBar* messageBar() : 可以在状态栏上创建一个 QgsMessageBar 的对象,用这个函数获取
  2. int messageTimeout() : 可以直接返回 500 ,或根据配置文件、设置等返回
  3. QgsMessageBar* visibleMessageBar() : 可直接返回状态栏上的 QgsMessageBar 对象
  4. QgsMapLayer* activeLayer() : 需要使用 QgsLayerTreeView 类获取,下面详述

但是现在,还不能在地图上显示,需要将 QgsMapCanvasQgsProject 建立关联,才能将 QgsProject 中的图层同步到 QgsMapCanvas 中。 使用的方法是在构造函数中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
QgsSdkApp::QgsSdkApp(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::QgsSdkApp)
{
ui->setupUi(this);
mMapCanvas = ui->centralwidget;
mMapCanvas->setObjectName(QStringLiteral("theMapCanvas"));

/** [BEGIN] 添加的用于将 `QgsMapCanvas` 和 `QgsProject` 建立关联的代码 */
mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, this);
/** [END] */

connect(ui->actionAdd_Shp_Layer, &QAction::triggered, this, &QgsSdkApp::on_actionShp_Layer_triggered);
}

这样添加了图层之后,就可以在地图上显示了。

添加其他图层

其他图层的添加方法,都可以从 QGIS 的代码中进行参考。在 qgisapp.cpp 文件中,有这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void QgisApp::dataSourceManager( const QString &pageName )
{
if ( ! mDataSourceManagerDialog )
{
mDataSourceManagerDialog = new QgsDataSourceManagerDialog( mBrowserModel, this, mapCanvas() );
connect( this, &QgisApp::connectionsChanged, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::refresh );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::connectionsChanged, this, &QgisApp::connectionsChanged );
connect( mDataSourceManagerDialog, SIGNAL( addRasterLayer( QString const &, QString const &, QString const & ) ),
this, SLOT( addRasterLayer( QString const &, QString const &, QString const & ) ) );
connect( mDataSourceManagerDialog, SIGNAL( addVectorLayer( QString const &, QString const &, QString const & ) ),
this, SLOT( addVectorLayer( QString const &, QString const &, QString const & ) ) );
connect( mDataSourceManagerDialog, SIGNAL( addVectorLayers( QStringList const &, QString const &, QString const & ) ),
this, SLOT( addVectorLayers( QStringList const &, QString const &, QString const & ) ) );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addMeshLayer, this, &QgisApp::addMeshLayer );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::showStatusMessage, this, &QgisApp::showStatusMessage );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::addDatabaseLayers, this, &QgisApp::addDatabaseLayers );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::replaceSelectedVectorLayer, this, &QgisApp::replaceSelectedVectorLayer );
connect( mDataSourceManagerDialog, static_cast<void ( QgsDataSourceManagerDialog::* )()>( &QgsDataSourceManagerDialog::addRasterLayer ), this, static_cast<void ( QgisApp::* )()>( &QgisApp::addRasterLayer ) );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::handleDropUriList, this, &QgisApp::handleDropUriList );
connect( this, &QgisApp::newProject, mDataSourceManagerDialog, &QgsDataSourceManagerDialog::updateProjectHome );
connect( mDataSourceManagerDialog, &QgsDataSourceManagerDialog::openFile, this, &QgisApp::openFile );

}
else
{
mDataSourceManagerDialog->reset();
}
// Try to open the dialog on a particular page
if ( ! pageName.isEmpty() )
{
mDataSourceManagerDialog->openPage( pageName );
}
if ( QgsSettings().value( QStringLiteral( "/qgis/dataSourceManagerNonModal" ), true ).toBool() )
{
mDataSourceManagerDialog->show();
}
else
{
mDataSourceManagerDialog->exec();
}
}

这个函数中有 addRasterLayer addVectorLayer addMeshLayer 等函数,是添加不同类型的图层的方法。可以直接查看这些方法,学习其中的方法,放到工程中来。

在下篇博客中,我计划介绍添加 CSV 类型数据的方法。

显示图层树

一般情况下我们都需要使用图层树来对图层进行管理。下面我们就在界面上添加 QgsLayerTreeView 对象。

我们首先在界面上创建一个 QWidget 组件,提升为 QgsLayerTreeView 类型。然后再构造函数中给其设置 Model 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QgsSdkApp::QgsSdkApp(QWidget *parent) : QMainWindow(parent), ui(new Ui::QgsSdkApp)
{
ui->setupUi(this);
mMapCanvas = ui->centralwidget;
mMapCanvas->setObjectName(QStringLiteral("theMapCanvas"));
mLayerTreeCanvasBridge = new QgsLayerTreeMapCanvasBridge(QgsProject::instance()->layerTreeRoot(), mMapCanvas, this);

/** [BEGIN] 设置 QgsLayerTreeView 的 Model */
QgsLayerTreeModel* model = new QgsLayerTreeModel(QgsProject::instance()->layerTreeRoot(), this);
ui->layerTreeView->setModel(model);
ui->layerTreeView->setObjectName(QStringLiteral( "theLayerTreeView" ));
/** [END] */

mInfoBar = new QgsMessageBar(this);
ui->statusbar->addWidget(mInfoBar);

connect(ui->actionAdd_Shp_Layer, &QAction::triggered, this, &QgsSdkApp::on_actionShp_Layer_triggered);
}

QT 中采用的 MVC 模型。 QT 中提供了 QTreeView 、 QListView 、 QTableView 等视图(View), 也提供了 QAbstractItemModel 、 QAbstractListModel 、 QAbstractTableModel 三种模型(Model), 也会提供了一些 Delegate 委托(相当于控制器 Controller)。

但是我们这个图层树仅仅有一个最基本的功能,而在 QGIS 中排序、右键菜单丰富的功能。 对于右键菜单,QGIS 中使用了 Provider 的方式提供右键菜单的菜单项,我们需要将这些 Provider 的代码复制过来,添加到工程中。

1
2
// qgisapp.cpp [4493行]
mLayerTreeView->setMenuProvider( new QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );

对于排序等其他功能,我们可以按需添加。

感谢您的阅读,本文由 HPDell 的个人博客 版权所有。如若转载,请注明出处:HPDell 的个人博客(http://hpdell.github.io/编程/qgisdev2-mapcanvas/
QGIS 二次开发笔记(1)——环境配置
QGIS 二次开发笔记(3)——空间距离和空间权重