In this section
Object Model Diagram Click here
Example Code Click here
Description This project provides a custom layer that reads geographic data from a file-based data source. Supporting classes include a property page that allows the user to change the source file for the layer and a custom IdentifyObject to identify the feature attributes for the layer. To further complete the custom layer implementation, a custom GxObject is provided so that the data file can be browsed and previewed in ArcCatalog. A layer factory, name, and layer enumeration are also provided to allow you to preview the layer in ArcCatalog.
The VB example project is restricted in scope. As the ILayerFactory and IName interfaces cannot be implemented in VB6, the VB sample provides the custom layer, identify object, and property page onlythis means that the VB custom layer must be added programmatically to a map.
Design SimplePointLayer is a subtype of the Layer abstract class. The coclass
SimplePointLayerPropPage is a property page that applies to the layer object.
A custom IdentifyObj object, SimplePointLayerIdentifyObj, also
accompanies this layer to enable feature identification.
SimplePointLayerGxObject is a subtype of the GxObject class, which represents the data
in ArcCatalog. SimplePointLayerGxObjectFactory is a subtype of the GxObjectFactory class
and is responsible for creating this custom GxObject.
SimplePointLayerFactory is a subtype of the abstract class LayerFactory. The LayerFactory
creates the layer by first evaluating the layer's associated Name object,
SimplePointLayerName. When the LayerFactory creates the layer, it adds it to an
enumeration, SimplePointEnumLayer.
License ArcView or above.
Libraries Carto, Catalog, Display, Framework, GeoDatabase, Geometry, System, and SystemUI.
Languages Visual Basic (some restrictions), Visual C++.
Categories ESRI Layer Property Pages, Layer Factory, and ESRI GxObject Factories.
Interfaces (VC++) IEnumLayer, IGeoDataset, IGxLayerSource, IGxObject, IGxObjectFactory,
IGxObjectFactoryFileExtensions, IGxObjectUI, IIdentifyObj, ILayer, ILayerInfo,
ILayerDrawingProperties, ILayerFactory, ILegendInfo, IIdentify, IName,
IPersistStream, IPropertyPage, IPropertyPageContext.
(VB) ILayer, IGeoDataset, IIdentify, ILayerInfo, ILegendInfo,
ILayerDrawingProperties, IPersistVariant, IIdentifyObj, IComPropertyPage
How to use
VC++
VB6
SubAddSimplePointLayer()DimpLPTAsSimplePointLayerVB.ISimplePointLayerSetpLPT =NewSimplePointLayerVB.SimplePointLayer pLPT.File = <path to data>DimpLyrAsesriCarto.ILayerSetpLyr = pLPTIfpLyr.Valid =False ThenMsgBox "please check path to data"Exit Sub End IfesriArcMapUI.IMxDocumentDimpMxDoc AsSetpMxDoc = Application.Document pMxDoc.AddLayer pLyrEnd Sub
Many different formats of data are supported for viewing in ArcMap as layers: coverages, CAD files, personal geodatabase feature classes, and so on. In addition to these layers, you can use the Add XY Data command in ArcMap to display a Table as a layer. This means that you can take a table that contains x and y coordinates and display this as if it were a point layer.
If you have data stored in an ASCII file, it may not always be possible to use this as a layer however. For example, if your ASCII file uses a fixed-width format instead of a delimited format, you will not be able to use this as a table and, therefore, will not be able to display this as a layer.
Therefore, for your fixed-width ASCII x,y data, you may want to create a custom layer to display the data as a layer, rather than performing some kind of data conversion on the data files.
To solve the requirements of this example, you will create a subtype of the Layer abstract class called SimplePointLayer, by implementing ILayer and IGeoDataset. Implementations of the SimplePointLayer are available as Visual C++ and Visual Basic sample projectspersistence is added by implementing IPersistVariant in VB, or IPersistStream in VC++. Throughout the discussion of the sample we will follow the VC++ implementationthe VB implementation will be discussed where it differs from the main concepts of the VC++ implementation.
In addition to the minimum interfaces, you will implement ILayerDrawingProperties, which is typically used internally by a layer's property page to indicate if some properties of the layer have been changed so that the layer needs to be redrawn. To associate an icon with the layer file, you will also implement the ILayerInfo interface. To include the layer in the ArcMap table of contents window, you will also implement the ILegendInfo interface.
To be able to use the Identify tool on features in the custom layer, you will also implement IIdentify. This implementation also requires a custom IdentifyObj object, which will be covered in great detail in a separate section below.
You will also create a property page, which will be discussed in more detail in the Layer Property Page section below.
The custom SimplePointLayer will allow an unsupported data format to be displayed by ArcGIS as a layer.
By reviewing the behavior and implementation details of existing layer coclasses, such as FeatureLayer, RasterLayer, and CadFeatureLayer, you will see that the inclusion of these interfaces - ILayerInfo, ILayerDrawingProperties and IIdentify - provides a higher level of integration with the ArcObjects framework. For more information on Layers, look at the Carto Library Reference Overview in the ArcGIS Developer Help.
The first interface you will implement is ILayer. The implementation of ILayer provides the system the information it needs to draw the layer.
A layer should be considered invalid if there is a problem connecting to the datasource. If a layer is not valid, then it should not be drawn nor should its extent be returned or its features identified; for example, in the Draw method you will check to see that the layer is valid before drawing. Add a member variable to indicate the validity of the layer and return its value from Valid. You will set this value later in the 'Creating and Implementing ISimplePointLayer' section.
STDMETHODIMP CSimplePointLayer::get_Valid(VARIANT_BOOL * Valid)
{
if (Valid == NULL)
return E_POINTER;
*Valid = m_bValid;
return S_OK;
When a new spatial reference is set in the Map, a reference to the new coordinate system is passed to the SpatialReference member of ILayer. To implement the SpatialReference property, simply store this reference.
ISpatialReferencePtr m_ipLayerSpatialRef;// data frame... STDMETHODIMP CSimplePointLayer::putref_SpatialReference(ISpatialReference * pSR ) { m_ipLayerSpatialRef = pSR;returnS_OK;
Your layer will need to apply the current spatial reference when drawing features, identifying features, and returning its own extent. Remember to project from the data source's native spatial reference system to the spatial reference system applied to the Map. The datasource's spatial reference system is indicated in the IGeoDataset implementation of the layer. This interface is discussed in more detail in the Implementing IGeoDataset section below.
When a layer is drawn, the map will ask the layer for its AreaOfInterest property so that it can determine where on the map to draw. The AreaOfInterest can be set by asking the layer for its full extent. The Extent of the Layer can be retrieved from its IGeoDataset implementation. You should expand this extent so the symbols used to draw the layer's features are fully included in the display. This will be discussed in more detail in the Implementing IGeoDataset section below.
STDMETHODIMP CSimplePointLayer::get_AreaOfInterest(IEnvelope * * aoi)
{
if (aoi == NULL)
return E_POINTER;
if (!m_bValid)
{
aoi = 0;
return S_OK;
}
return get_Extent(aoi);
The actual drawing of the layer occurs in the Draw member method.
STDMETHODIMP CSimplePointLayer::Draw(esriDrawPhase DrawPhase, IDisplay * Display, ITrackCancel * trackCancel)
{
if (DrawPhase != esriDPGeography) return S_OK;
..
if(m_bValid == VARIANT_FALSE)returnS_OK;
if(m_bValid == VARIANT_FALSE)returnS_OK;if(m_bVisible == VARIANT_FALSE)returnS_OK;
... ILegendClassPtr ipLegendClass; ISymbolPtr ipSym; m_ipLegendGroup->get_Class(0, &ipLegendClass); ipLegendClass->get_Symbol(&ipSym); Display->SetSymbol(ipSym); ..
...while(hr != E_FAIL) { hr = NextRecord(&ipPt, &bstrAttr);if(hr != E_FAIL) {if(m_ipLayerSpatialRef) ipPt->Project(m_ipLayerSpatialRef); Display->DrawPoint(ipPt); } }returnS_OK;
Note that the Draw method does not need to consider the layer's MinimumScale and MaximumScale properties when it draws. The Display will consider the draw scale of the map before it asks the layer to draw itself.
The following table summarizes the members of ILayer that have been discussed and describes the implementation of the other members that did not require detailed discussion above.
| ILayer member | Implementation description |
|---|---|
| AreaOfInterest | Return an IEnvelope reference storing the area of interest for the layer. The envelope geometry should have the same spatial reference system as the Map. The AreaOfInterest is usually the same as the combined extent of the features in the layer. |
| Cached | Return or set a boolean indicating if the layer should use its own display cache. This is an informational property and the management of the cache is not done by the layer but by the display container. |
| Draw | Draw the layer to the specified display for the appropriate draw phase. You will set the symbols for the geometries to be drawn, then draw each feature for your layer. |
| MaximumScale | Return or set the maximum scale (representative fraction) at which the layer will display. |
| MinimumScale | Return or set the minimum scale (representative fraction) at which the layer will display. |
| Name | Return or set a string value that indicates the name of the layer. |
| ShowTips | Return or set a boolean indicating if the layer shows map tips. The tip is specified in the TipText property. |
| SpatialReference | Set an ISpatialReference reference passed by the Map to the layer. The layer will need to draw its geometries in this spatial reference. |
| SupportedDrawPhases | Return an esriDrawPhase constant or a combination of esriDrawPhase constants indicating the draw phases supported by the layer. |
| TipText | Return a string value indicating the Map tip text for the specified location. |
| Valid | Return a boolean value indicating if the layer is currently valid. You will need to determine what situations render your layer invalid. |
| Visible | Return or set a boolean value indicating if the layer is currently visible. |
The information about the spatial reference system and spatial extent for your layer's datasource is managed by the members of the IGeoDataset interface. This interface must be implemented for the Map to be able to georeference and project the layer.
The SpatialReference member should return the native spatial reference system for the layer's datasource. In this project, this property has been set to the world Robinson projection. If the metadata for the spatial reference system was stored in the datasource, then this information could be retrieved to dynamically set the spatial reference system for the dataset.
STDMETHODIMP CSimplePointLayer::get_SpatialReference(ISpatialReference * * spref)
{
if (spref == NULL)
return E_POINTER;
// indicate the native spatial reference system for the layer.
if (m_ipDataSpatialRef ==0)
{
ISpatialReferenceFactoryPtr ipSRF(CLSID_SpatialReferenceEnvironment);
IProjectedCoordinateSystemPtr ipPCS;
ipSRF->CreateProjectedCoordinateSystem(esriSRProjCS_World_Robinson, &ipPCS);
m_ipDataSpatialRef = ipPCS;
}
*spref = m_ipDataSpatialRef;
(*spref)->AddRef();
return S_OK;
The read-only SpatialReference property on IGeoDataset should return
the details of the coordinate system in which the data is stored.
The write-only SpatialReference property on ILayer indicates to the Layer the coordinate
system it should use to draw itself and return its other spatial properties
such as Extent.
To complete the implementation of this interface, you will need to specify the spatial extent which contains all the features of the layer. The extent is usually calculated as the minimum bounding rectangle of the layer; however, you may need to incorporate the spatial extent of the symbol or symbols used to display the layer's features as wellthis issue is generally applicable to point data as well as to line data with a thick symbol or polygon data with a thick outline symbol.
HRESULT CSimplePointLayer::GetLayerExtent(IEnvelope** ppEnv)
{
double dxMin, dxMax, dyMin, dyMax;
dxMin = 9999999;
dxMax = -9999999;
dyMin = 9999999;
dyMax = -9999999;
HRESULT hr = S_OK;
double x,y = 0.0;
long lLoopCount = 0;
IPointPtr ipPt;
CComBSTR bstrAttr;
while (hr != E_FAIL)
{
hr = NextRecord(&ipPt, &bstrAttr);
if (hr != E_FAIL)
{
ipPt->get_X(&x);
ipPt->get_Y(&y);
if (x < dxMin) dxMin = x;
if (x > dxMax) dxMax = x;
if (y < dyMin) dyMin = y;
if (y > dyMax) dyMax = y;
}
lLoopCount++;
}
// Handle special case of single point in file
// add a small amount, so that we will end up with an envelope rather than a point
if (lLoopCount == 1)
{
double dDelta = 0.01;
if (dxMax != 0)
dDelta = dxMax/1000;
dxMax = dxMax + dDelta;
dyMax = dyMax + dDelta;
}
IEnvelopePtr ipEnv(CLSID_Envelope);
ISpatialReferencePtr ipSR;
get_SpatialReference(&ipSR);
ipEnv->putref_SpatialReference(ipSR);
ipEnv->put_XMin(dxMin);
ipEnv->put_XMax(dxMax);
ipEnv->put_YMin(dyMin);
ipEnv->put_YMax(dyMax);
*ppEnv = ipEnv;
if (*ppEnv)
(*ppEnv)->AddRef();
return S_OK;IMarkerSymbolPtr ipMarker(ipSym);if(ipMarker) {doubleptsDist, mapDist = 0.0; IDisplayTransformationPtr ipDT; ipMarker->get_Size(&ptsDist); Display->get_DisplayTransformation(&ipDT); ipDT->FromPoints(ptsDist, &mapDist); m_dblMarkerDist = mapDist;//Cached symbol size value.
STDMETHODIMP CSimplePointLayer::get_Extent(IEnvelope * * Extent)
{
if (Extent == NULL)
return E_POINTER;
if (!m_bValid)
{
m_ipExtent = 0;
return S_OK;...if(m_ipExtent == 0) GetLayerExtent(&m_ipExtent);if(m_ipExtent == 0)returnS_OK;doublew, scaleFactor = 0.0; IClonePtr ipClone; IClonePtr(m_ipExtent)->Clone(&ipClone); IEnvelopePtr ipEnv(ipClone);//project extent if map's spatial reference has been setif(m_ipLayerSpatialRef) ipEnv->Project(m_ipLayerSpatialRef); ...
...//expand the extent to consider the size of the symbolsipEnv->get_Width(&w); scaleFactor = (w + m_dblMarkerDist)/w; ipEnv->Expand(scaleFactor, scaleFactor, VARIANT_TRUE); *Extent = ipEnv;if(*Extent) (*Extent)->AddRef();returnS_OK;
The IGeoDataset interface has only two membersExtent and SpatialReferencewhich are read-only properties. Once this interface has been implemented, ArcGIS applications, such as ArcMap, can zoom to the layer, view the layer with other datasets, and project the layer into different map coordinate systems.
To see a layer as an item in the table of contents window in ArcMap, the layer must implement ILegendInfo. Every layer has a LegendGroup which is a collection of the classes used to display the layer. The LegendGroup links the symbols used for the layer with the table of contents. In other words, edits made to the legend group for the layer are passed on to the layer so it can redraw itself using the updated symbol or symbols. (Recall that in the layer's Draw method, the symbol you used to draw the layer's feature geometry is retrieved from the layer's legend group.)
For the SimplePointLayer you will have one legend class.
STDMETHODIMP CSimplePointLayer::get_LegendGroup(LONG Index, ILegendGroup * * LegendGroup)
{
...
*LegendGroup = 0;
if (Index == 0)
Initialize the LegendClass to a simple marker symbol.
if(m_ipLegendGroup ==0) { HRESULT hr;if(FAILED(hr = m_ipLegendGroup.CreateInstance(CLSID_LegendGroup)))returnhr; m_ipLegendGroup->put_Heading(CComBSTR(_T(""))); m_ipLegendGroup->put_Editable(VARIANT_TRUE);//can change symbol with right-click in TOCILegendClassPtr ipLegendClass(CLSID_LegendClass); ISymbolPtr ipSym(CLSID_SimpleMarkerSymbol); ipLegendClass->putref_Symbol(ipSym); ipLegendClass->put_Label(CComBSTR(_T(""))); m_ipLegendGroup->AddClass(ipLegendClass); } *LegendGroup = m_ipLegendGroup; (*LegendGroup)->AddRef(); }returnS_OK;
A user can now edit the symbol used by the layer by double-clicking the legend item in the table of contents.
The legend group for the layer will be discussed more when the topic of saving a layer is pursued in the next section.
As the topic Implementing persistence is discussed in great detail in Chapter 2, this section will only discuss what needs to be saved to the document in order to properly restore the state of the layer.
To save a layer, you will need to persist at least the following:
You will need to save the value of every property of the layer's ILayer implementation when the Save member of IPersistStream (IPersistVariant for Visual Basic users) is called. You must also save the layer's legend group so that when the layer is loaded again, it can be drawn with the same symbology as when it was last saved to the document.
Any other custom properties that are required to completely save the state of the layer need to be persisted as well. For example, for your SimplePointLayer, the path to the datasource must be saved since the layer only references its geographic data and does not actually store it.
STDMETHODIMP CSimplePointLayer::Save(IStream * pStm, BOOL fClearDirty)
{
//persist layer data to stream
HRESULT hr;
if (FAILED(hr = pStm->Write(&cCurVers, sizeof(cCurVers), 0)))
return E_FAIL;
// ILayer members
m_bstrName.WriteToStream(pStm);
pStm->Write(&m_bVisible, sizeof(m_bVisible), 0);
pStm->Write(&m_bCached, sizeof(m_bCached), 0);
pStm->Write(&m_dblMinScale, sizeof(m_dblMinScale),0);
pStm->Write(&m_dblMaxScale, sizeof(m_dblMaxScale),0);
//ISimplePointLayer member
m_bstrLPTFile.WriteToStream(pStm);
// legend group
IObjectStreamPtr ipObjStream(CLSID_ObjectStream);
ipObjStream->putref_Stream(pStm);
if (FAILED(hr = ipObjStream->SaveObject(m_ipLegendGroup)))
return hr;
return S_OK;
}
Note that the first thing you write is the persistence version of the classsee Chapter 2, 'Implementing Persistence', for more information.
If the layer is properly persisted, the document in which it has been saved can be opened in the same state as when it was last saved. You will need to return the layer information when the Load member of IPersistStream/IPersistVariant is called.
STDMETHODIMP CSimplePointLayer::Load(IStream * pStm)
{
short vers;
if (FAILED(pStm->Read(&vers, sizeof(vers), 0)))
return E_FAIL;
if (vers > cCurVers)
return E_FAIL;
HRESULT hr;
m_bstrName.ReadFromStream(pStm);
pStm->Read(&m_bVisible, sizeof(m_bVisible), 0);
pStm->Read(&m_bCached, sizeof(m_bCached), 0);
pStm->Read(&m_dblMinScale, sizeof(m_dblMinScale), 0);
pStm->Read(&m_dblMaxScale, sizeof(m_dblMaxScale), 0);
m_bstrLPTFile.ReadFromStream(pStm);
IObjectStreamPtr ipObjStream(CLSID_ObjectStream);
ipObjStream->putref_Stream(pStm);
hr = ipObjStream->LoadObject((GUID*) &IID_ILegendGroup, 0, (IUnknown**) &m_ipLegendGroup);
if (FAILED(hr)) return hr;
USES_CONVERSION;
m_fLPTFile.open(OLE2CA(m_bstrLPTFile));
if (!m_fLPTFile)
return E_FAIL;
return S_OK;
:Once you have implemented IPersistStream/IPersistVariant, the layer can be saved as part of the map document. It can also be saved outside the map document as a layer (.lyr) file. If you would like to associate a custom icon with the layer file of your custom layer, you need to implement the ILayerInfo interface. If you do not implement this interface, a plain layer icon will be associated with the layer file.
You will need to find a small and large icon to represent your layer. The small icon is typically a 16 by 16 pixel image; the large icon is 32 by 32 pixels. Store the icons as resources in your project. Return the icons from the SmallImage and LargeImage properties.
STDMETHODIMP CSimplePointLayer::get_SmallImage(OLE_HANDLE* phBitmap)
{
if (!g_hSmallImage)
g_hSmallImage = (HBITMAP)::LoadImage(_Module.m_hInst, MAKEINTRESOURCE(IDB_SMALLBEXLYR), IMAGE_BITMAP, 16, 16, LR_LOADTRANSPARENT);
*phBitmap = (OLE_HANDLE)g_hSmallImage;
return S_OK;
You can also add two other images to represent your layer when selected by returning the SmallSelectedImage and LargeSelectedImage properties.
Implement the ILayerDrawingProperties interface so the map knows to redraw the layer when the layer's drawing properties have changed. The DrawingPropsDirty member of this interface is automatically set when any of the layer's attributes are changed in a property page. For example, if the minimum or maximum draw scales are set in the layer's property page, DrawingPropsDirty will be set to True, and the map will be refreshed so that the layer can be redrawn with the new drawing properties.
To implement DrawingPropsDirty, you simply need to store a boolean value.
VARIANT_BOOL m_bDrawDirty;
...
STDMETHODIMP CSimplePointLayer::put_DrawingPropsDirty(VARIANT_BOOL dirty)
{
m_bDrawDirty = dirty;
return S_OK;
To display attributes of features with the identify tool, the layer must implement IIdentify. This interface has a single member, which should identify the feature at the specified location and return an array of objects that implement the interface IIdentifyObj.
Do not carry out the Identify method if the layer is invalid.
STDMETHODIMP CSimplePointLayer::Identify(IGeometry* pGeom, IArray** ppArrObj)
{
if (!m_bValid)
return S_OK;
..
The Identify method is passed in an IGeometry reference, which indicates the location at which to find the feature to be identified. This geometry is an envelope object, which is constructed based on the search tolerance set in the map (IMxDocument::SearchTolerancePixels) and the point specified by the identify tool. You will need to evaluate this geometry (envelope) to see if it actually falls within the layer's extent. If it does not fall within the layer's extent, be sure to return S_FALSE and an empty array of objects.
// Check if input geometry envelope overlaps with spatial extent of layerm_ipExtent->QueryEnvelope(ipLyrExt);//copy geometry
Note that the specified location will use the map's coordinate system. You will need to convert between the map's spatial reference system and the datasource's native spatial reference system when comparing the IGeometry reference with the layer's feature geometry.
if(m_ipLayerSpatialRef) ipLyrExt->Project(m_ipLayerSpatialRef); pGeom->get_GeometryType(&shapeType);if(shapeType != esriGeometryEnvelope) pGeom->get_Envelope(&ipinEnv);elseipinEnv = pGeom; ipinEnv->QueryEnvelope(ipIntersectEnv); ipIntersectEnv->Intersect(ipLyrExt); ipIntersectEnv->get_IsEmpty(&bEmpty);// if the input geometry is not within the layer's extent:// -pass back an empty array (i.e. count = 0// -return S_FALSEif(bEmpty == VARIANT_TRUE) { *ppArrObj = ipArray.Detach();returnS_FALSE; } ...
To identify the feature, check each line in the data text file and find the point that falls within the specified location (envelope). If a point is found and can be identified by the SimplePointIdObj object, the object is added to the array. Note that because the dataset is small, looping through all the records to find the matching feature can be done quickly. For larger files, an algorithm for spatial searches should be written. More details on the implementation of IIdentifyObj follow in the next section, 'Creating the SimplePointIdObj'.
STDMETHODIMP CSimplePointLayer::Identify(IGeometry* pGeom, IArray** ppArrObj)
{
...
while (hr != E_FAIL)
{
hr = NextRecord(&ipPt, &bstrAttr);
if (hr != E_FAIL)
{
// point is currently in the data's spatial reference system
ipPt->Project(m_ipLayerSpatialRef);
ipRelOp = ipPt;
ipRelOp->Within(pGeom, &bWithin);
// if point record matches the input geometry, add it to the array of IdentifyObjs
if (bWithin == VARIANT_TRUE)
{
ipIdObj.CreateInstance(CLSID_SimplePointIdObj);
ipIdObj->CanIdentify(this, &bIdentify);
if (bIdentify == VARIANT_TRUE)
{
ipLyrIdObj = ipIdObj;
ipLyrIdObj->put_Point(ipPt);
ipLyrIdObj->put_Character(bstrAttr);
ipArray->Add(ipIdObj);
}
}
}
}
*ppArrObj = ipArray.Detach();
return S_OK;
At this point, your layer lacks one essential piece of functionality. For a client object to be able to specify the layer's datasource, define a new interface called ISimplePointLayer. Add one read-write property called FileName, and a method called NextRecord, and implement the interface in the SimplePointLayer class.
When the FileName property is set, this will inform the layer where its data can be found and allow the layer to read the data. You will use a stream type from the C++ standard library to read the data from the file.
ifstream m_fLPTFile
STDMETHODIMP CSimplePointLayer::put_File(BSTR file)
{
m_bValid = VARIANT_FALSE;
m_bDrawDirty = VARIANT_TRUE;
m_ipExtent = 0;
m_bstrLPTFile = CComBSTR(file);
if (m_fLPTFile)
{
if (m_fLPTFile.is_open())
{
m_fLPTFile.close();
m_fLPTFile.clear();
}
m_sCurrentRow[0] = '\0';
}//check if input file exists:if(GetFileAttributes(file)==-1) { this->put_Name(CComBSTR(_T("file not found")));returnS_OK; } USES_CONVERSION; m_fLPTFile.open(OLE2CA(m_bstrLPTFile),ifstream::in);if(!m_fLPTFile.good())returnS_OK;
m_bValid = VARIANT_TRUE;
wchar_t* pwchar;
wchar_t* pwchar2;
pwchar = wcsrchr(m_bstrLPTFile.Copy(),'\\');
pwchar2 = wcstok(pwchar+1, _T("."));
CComBSTR bstrName(pwchar2);
m_bstrName.operator =(bstrName);
this->put_Name(bstrName);
return S_OK;To implement the NextRecord method, check that the file is not at its end, and get the next line in the file. Parse the line and create a Point with the coordinates you retrieved. Don't forget to set a reference to the SpatialReference of the layer for each point you create.
STDMETHODIMP CSimplePointLayer::NextRecord(IPoint** ppoint, BSTR *attribute)
{
// Read current row
if (!m_fLPTFile.eof())
{
char sAtt[2];
double x, y;
char* end;
char buf[6];
m_fLPTFile.getline(m_sCurrentRow, c_iMaxRowLen);
// First, parse the attribute out of the current row.
// We know this data source has just one attribute, which is one char wide.
strncpy(sAtt, m_sCurrentRow + 12, 1);
sAtt[1] = '\0'; // add null terminator
CComBSTR bstrAtt = CComBSTR(sAtt);
bstrAtt.CopyTo(attribute);
// Parse the X and Y values out of the current row and into the geometry
x = strtod(strncpy(buf, m_sCurrentRow, 6),&end);
y = strtod(strncpy(buf, m_sCurrentRow + 6, 6),&end);
IPointPtr ipPt(CLSID_Point);
ipPt->putref_SpatialReference(m_ipDataSpatialRef);
ipPt->put_X(x);
ipPt->put_Y(y);
*ppoint = ipPt.Detach();
}
else //eof, return E_FAIL
{
m_sCurrentRow[0] = '\0';
//set file pointer back to beginning
m_fLPTFile.clear();
m_fLPTFile.seekg(ios::beg);
return E_FAIL;
}
return S_OK;
}In the VB sample project, a FileSystemObject is used to check for the presence of the data file, and get the file extension and base name. A TextStream object is used to connect to the data file and read each line in turn. Both of these objects can be found in the Microsoft Scripting Runtime object library.
As the file is not held on to permanently, and only to be read, the data can be updated while the layer is being viewed in ArcMap, and when the data file is saved and the map refreshed, the Draw method will pick up any changes to the file.
As an alternative implementation, you may want to cache the items in the file and check the file to see if it has been edited before redrawing, then rereading and caching the file if it has been edited since the last read. Established data sources include many measures to increase the speed of data refresh, for example, caching, spatial and attribute indexes, or grouping of related records. The discussion of such issues is beyond the scope of this topic.
Now that the SimplePointLayer is complete, you need to provide the Identify class, which is required by the IIdentify interface on your SimplePointLayer.
To identify the features of a layer using the identify tool, a few things are required. First, the layer must implement the IIdentify interface. Second, the layer must have an associated IdentifyObj object to provide the identify results. At a minimum, this IdentifyObj object must implement the IIdentifyObj interface and it must also provide a window in which to display the identify results.
You will need to create a coclass called SimplePointIdObj that implements the IIdentifyObj interface. This is the only ArcGIS interface the SimplePointIdObj will implement.
Recall from the 'Implementing IIdentify' section above that for every feature that the IIdentify::Identify method matches to the input geometry, the IdentifyObj associated with the layer is created. The IdentifyObj object verifies if it can identify the features of the layer in its IIdentifyObj::CanIdentify method.
CanIdentify is passed an ILayer reference to the layer that the specified feature or features belong to. You will need to QI the layer for the interface that uniquely identifies the layer that SimplePointIdObj is associated within this case, ISimplePointLayer. Cache the layer reference so that you can supply the reference when your SimplePointIdObj is asked for its Layer property.
STDMETHODIMP CSimplePointIdObj::CanIdentify(ILayer* pLayer,VARIANT_BOOL* b)
{
*b = VARIANT_FALSE;
m_ipSimplePtLyr = pLayer; //QueryInterface
if(m_ipSimplePtLyr==0)
return S_FALSE;
*b = VARIANT_TRUE;
return S_OK;
Once it is determined that a feature can be identified, the Layer object populates its associated IdentifyObj object with the desired attributes of the specified feature (see the 'Implementing IIdentify' section above). The IdentifyObj object can then populate its Identify Results window with these attribute values. As the standard Identify Results window implementation will not work for the sample data, you will need to create a form or dialog box to display these attribute values. This window will replace the right side of the Identify Results dialog box.
In the sample data, a single character attribute is present for each feature. Add an edit box to the dialog box to display this attribute value. (In the VB sample project, a Label control is used.)
Now that the Identify Results window for the SimplePointIdObj has been completed, you can create this window and provide its window handle in the implementation of the IIdentifyObj::hWnd property. This is also where you should populate any of the window's control boxes with attribute values.
STDMETHODIMP CSimplePointIdObj::get_hWnd(OLE_HANDLE* hWnd)
{
//create window
if (!m_IdentifyDlg)
m_IdentifyDlg =new CIdentifyDialog;
if (!m_IdentifyDlg->m_hWnd)
{
m_IdentifyDlg->Create(0);
m_IdentifyDlg->SetDlgItemText(ID_ATTRIBUTE, m_bstrAttr);
}
*hWnd = (OLE_HANDLE)m_IdentifyDlg->m_hWnd;
return S_OK;
The name of the identified feature is specified in the string property IIdentifyObj::Name. This value is displayed in the left window of the Identify Results dialog box.
The method IIdentifyObj::Flash is where you will put the code to flash the identified feature. Be sure to first verify that there is an object to flash.
STDMETHODIMP CSimplePointIdObj::Flash(IScreenDisplay* pDisplay)
{
if ((pDisplay ==0) || (m_ipPoint==0))
return S_FALSE;
...
Then you can highlight the feature by drawing and redrawing the Point.
... ISymbolPtr ipSym(CLSID_SimpleMarkerSymbol); ipSym->put_ROP2(esriROPNotXOrPen);//erase itself when drawn twicepDisplay->SetSymbol(ipSym);//flashOLE_HANDLE hDC; pDisplay->get_hDC(&hDC); pDisplay->StartDrawing(hDC, esriNoScreenCache); pDisplay->DrawPoint(m_ipPoint); ::Sleep(300); pDisplay->DrawPoint(m_ipPoint);//draw 2nd time to erasepDisplay->FinishDrawing();returnS_OK;
To allow a client to set the point to be flashed and the character attribute to display in the Identify Results window, define a new interface called ISimplePointIdObj. Add a write-only property of type esriGeometry.IPoint and write-only property Char, which takes a Bstr. Implement this interface in the SimplePointIdObj class. Recall that earlier you used the Point and Character properties of this interface in the IIdentify::Identify method of the SimplePointLayer layer.
STDMETHODIMP CSimplePointIdObj::put_Point(IPoint *pPoint)
{
m_ipPoint = pPoint;
return S_OK;
}
STDMETHODIMP CSimplePointIdObj::put_Character(BSTR Attr)
{
m_bstrAttr.operator =(Attr);
return S_OK;Unless a property page is available for your layer, users are limited to editing the properties of the layer programmatically. SimplePointLayerPropPage is a layer property page that will allow the user to change the data source for the layer (for example, the path to the text file). Visual C++ developers will need to implement IPropertyPage and IPropertyPageContext for their custom property page. Visual Basic developers should implement IComPropertyPage. The implementation of these interfaces will be responsible for loading the property page and determining if the property page applies to the specified layer.
Note that the Properties dialog box for a layer will also include other property pages, according to which interfaces are implemented by the layer. For example, the General property page will apply to any layer, as it only requires the ILayer interface to be implemented.
Create a new class called SimplePointLayerPropPage, by using a standard implementation of a property page. See Chapter 2, 'Creating Property Pages', for further information on creating property pagesthis section will discuss only the implementation details that apply specifically to the SimplePointLayer.
You will need to create a dialog box that will display the control or controls to edit the custom properties for your layer. On the custom ISimplePointLayer interface, you added a single editable property for changing the file path to its data source, so add a single EditBox to the dialog box. (The VB sample project uses a TextBox on a Form). You should initialize the value in the edit box control to the existing value of ISimplePointLayer::File.
LRESULT CSimplePointPropPage::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CComBSTR bstrFilePath;
m_ipSimpPtLayer->get_File(&bstrFilePath);
SetDlgItemText(IDEB_FILEPATH, bstrFilePath);
return 0;
You will also need to handle any changes that occur on the property page and write them to the layer object. Capture the OnChange event, which is fired when the text is altered, then flag the property page so it knows that its values have changed.
LRESULT CSimplePointPropPage::OnChangeFilepath(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
if (wNotifyCode == EN_CHANGE)
{
HRESULT hr = m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
if (FAILED(hr)) return hr;
m_bDirty = TRUE;
}
return 0;
Once the property page has been notified of the changes, the changes can be applied in its IPropertyPageContext::QueryObject method.
STDMETHODIMP CSimplePointPropPage::QueryObject(VARIANT theObject)
{
// Check if we have an ISimplePointLayerPtr
// If we do, apply the setting from the page.
CComVariant vObject(theObject);
if (vObject.vt != VT_UNKNOWN) return E_UNEXPECTED;
// Try and QI to ISimplePointLayerPtr
ISimplePointLayerPtr ipLyr(vObject.punkVal);
if (ipLyr != 0)
{
// Read the file Name from the property page and
// set the new value
BSTR bstrFilePath = ::SysAllocStringLen(0, 200);
GetDlgItemText(IDEB_FILEPATH, bstrFilePath);
HRESULT hr = m_ipSimpPtLayer->put_File(bstrFilePath);
if (FAILED(hr)) return hr;
::SysFreeString(bstrFilePath);
}
return S_OK;
}If you are working in VB, you can retrieve changes to the property page values and flag the COM property page site that the changes have occurred.
The property page must then be registered in the Layer Property Pages component category.
At this point, you can programmatically add the layer into ArcMap and change the Symbol. By using the Layer Properties dialog box, you can also change the source of the layer's data, the minimum and maximum display scales, and the layer's name.
You can also identify features.
Just as different types of Layers are used to represent the different types of datasets, different types of GxObjects are used to represent the different data types. If you would like to support a custom data format in ArcCatalog, you will need to start with encapsulating the data in a GxObject.
Once the GxObject is integrated with the framework and associated with a Layer, you will be able to browse to data in ArcCatalog and add the data to a Map using the GxDialog object. If a data source is not tied to a GxObject but does have a layer to represent it, the only way to add it to a map is programmatically (for example, via a VBA macro).
There are many different types of GxObject classes to represent the different types of data. All the items in the tree and list views of ArcCatalog are some type of GxObject. For more information on GxObjects, please the Catalog library in the Library section of the ArcGIS Developer Help system, and also see the examples and discussion of custom GxObjects in Chapter 6 of this book.
Add a new class to your project called SimplePointLayerGxObject, to represent the simple point data file so that it is shown as an item in ArcCatalog. At a minimum, a GxObject should implement the ArcObjects interfaces IGxObject and IGxObjectUI. These interfaces are used mostly to provide identification for the data format and an easily identifiable icon. As with the custom layer, more interfaces can be implemented depending on the level of integration with ArcGIS that is desired (again, see Chapter 6 for more information); for the SimplePointLayerGxObject, you will also implement the IGxLayerSource interface.
As a great detail of information on creating a custom GxObject and GxObjectFactory is covered in Chapter 6, this section will only cover the details of creating and integrating a GxObject so that you can browse for your custom datasource in ArcCatalog and preview its associated layer or add it to a Map.
In addition to creating a custom GxObject, you will need to create a GxObjectFactory that knows how to manufacture the GxObject. In the following section, therefore, you will create the SimplePointLayerGxObjectFactory.
Define an interface called ISimplePointLayerGxObject. The interface should contain one read-write property called FileName, to allow the location of the data to be set by the SimplePointLayerGxObjectFactory (see below). When the SimplePointLayerGxObjectFactory object creates a SimplePointLayerGxObject, it passes the full path of the datasource to its ISimplePointLayerGxObject::FileName property.
Once a GxObject knows its name via the ISimplePointLayerGxObject interface you just created, it can populate the name properties IGxObject::BaseName, IGxObject::FullName, and IGxObject::Name.
STDMETHODIMP CSimplePointLayerGxObject::get_Name(BSTR * Name)
{
if (Name == NULL)
return E_POINTER;
// file name with no extension
wchar_t* pwchar;
pwchar = wcsrchr(m_bstrFileName.Copy(),'\\');
CComBSTR bstrName(pwchar+1);
bstrName.CopyTo(Name);
return S_OK;
}The easiest way to assemble the FullName for the GxObject is by calling IGxCatalog::ConstructFullName on the GxCatalog object and passing in a reference to itself. (The GxCatalog reference is cached when the IGxObject::Attach member is called.)
STDMETHODIMP CSimplePointLayerGxObject::get_FullName(BSTR * Name)
{
// file name and path
if (Name == NULL)
return E_POINTER;
m_ipCatalog->ConstructFullName(this, Name);
return S_OK;
}For the InternalObjectName property, you can return a custom Name object, SimpleLayerPointName. (More details on this object follow in a later section.) The InternalObjectName property is the link to the underlying data which is encapsulated by the GxObject. It is this Name object that references the underlying data and not the GxObject itself.
STDMETHODIMP CSimplePointLayerGxObject::get_InternalObjectName(IName * * InternalObjectName)
{
if (InternalObjectName == NULL)
return E_POINTER;
//InternalObjectName is passed to all layerfactories
ISimplePointLayerNamePtr ipLyrName(CLSID_SimplePointLayerName);
INamePtr ipName(ipLyrName);
if (ipName==0)
return E_FAIL;
ipName->put_NameString(m_bstrFileName);
*InternalObjectName = ipName.Detach();
return S_OK;
}The members of the IGxObjectUI interface provide ArcCatalog with the icons that will represent the GxObject. As with the implementation of the ILayerInfo interface, you will need to provide a small and large icon for the SimplePointLayerGxObject. If this interface is not implemented, a plain icon will be associated with the GxObject.
If IGxObjectUI is not implemented, a plain icon will be associated with the GxObject instead.
If the data associated with a GxObject is to be added to a Map via a GxDialog window, the GxObject must be a Gx dataset object or a Gx layer source. To identify the SimplePointLayerGxObject object as a layer source, it must implement the IGxLayerSource interface. IGxLayerSource is an indicator interface, which has no members.
As you have read, each of the GxObject classes has a corresponding GxObjectFactory. Add a new class to your project, called SimplePointLayerGxObjectFactory, to define a GxObjectFactory responsible for generating SimplePointLayerGxObject objects.
At a minimum, you should implement IGxObjectFactory interface for the factory class; its members will determine if a folder contains the relevant data, and create GxObjects to represent the data if it is the supported data format. While only the IGxObjectFactory interface needs to be implemented to create a functioning GxObjectFactory, every GxObjectFactory object should also implement the IGxObjectFactoryFileExtensions interface. This interface provides a set of file extensions that are handled by the factory. Only those files that match the specified extensions will be inspected by the GxObjectFactory.
When the SimplePointLayerGxObjectFactory determines that a folder contains its simple point datafiles, the SimplePointLayerGxObjectFactory will instantiate a SimplePointLayerGxObject object to encapsulate the data. To facilitate the inspection of the folders for the datafiles, SimplePointLayerGxObjectFactory also implements the interface IGxObjectFactoryFileExtensions. Only those files with the correct file extension (.lpt) will be inspected by the SimplePointLayerGxObjectFactory. This greatly speeds up the process of finding children for the GxObjectFactory. If this interface was not implemented, SimplePointLayerGxObjectFactory would be passed to every filename in the folder to verify if it is a child.
The IGxObjectFactory interface allows GxObjectFactory objects to return the factory's name and information about its potential children. The Name property of IGxObjectFactory indicates which type of data is associated with the GxObjectFactory. The name of the GxObjectFactory will appear in the list of data types registered with ArcCatalog (Tools->Options->General tab).
The method HasChildren is passed an IFileNames reference to inspect for a given folder. If the interface IGxObjectFactoryFileExtensions has been implemented by the factory, only the relevant files will be passed to the HasChildren method by ArcCatalog. Since the SimpleGxObjectFactory object is only interested in a single file extension, you can be sure that every file name passed to this method will have the correct file extension and points to a datasource. However, since the HasChildren method may be called by a client other than ArcCatalog, it would still be prudent to inspect each file for the relevant file extension. If the folder does contain any simple point data types, indicate that the folder does have children.
STDMETHODIMP CSimplePointLayerGxObjectFactory::HasChildren(BSTR parentDir, IFileNames* pFileNames, VARIANT_BOOL* pHasChildren)
{
*pHasChildren = VARIANT_FALSE;
CComBSTR bstrFileName;
pFileNames->Next(&bstrFileName);
while (bstrFileName != 0)
{
bstrFileName.ToUpper();
wchar_t* pwchar;
wchar_t* pwchar2;
pwchar = wcsrchr(bstrFileName.Copy(),'\\');
wcstok(pwchar, _T("."));
pwchar2 = wcstok(NULL, _T("."));
CComBSTR bstrName(pwchar2);
if (bstrName.operator ==(_T("LPT")))
{
*pHasChildren = VARIANT_TRUE;
break;
}
pFileNames->Next(&bstrFileName);
}
return S_OK;
}If more than one file type is associated with your dataset, you should inspect all the filenames in the array to ensure that all the necessary files are contained in the given folder.
The GetChildren method is passed some of the same parameters as were passed to the HasChildren method. GetChildren will only be called by ArcCatalog if the call to HasChildren indicated that the given folder contained the supported data type. To implement GetChildren, iterate the FileNames received for the data type with the appropriate file extension; however, this time a SimplePointLayerGxObject needs to be created and returned in a GxObject enumeration. Additional inspection of the file is recommended to verify that it references a valid dataset.
STDMETHODIMP CSimplePointLayerGxObjectFactory::GetChildren(BSTR parentDir, IFileNames* pFileNames, IEnumGxObject** ppChildren)
{
IGxObjectArrayPtr ipGxObjArray(CLSID_GxObjectArray);
ISimplePointLayerGxObjectPtr ipGxChild;
IGxObjectPtr ipGxObj;
CComBSTR bstrFileName;
pFileNames->Next(&bstrFileName);
while (bstrFileName != 0)
{
wchar_t* pwchar;
wchar_t* pwchar2;
pwchar = wcsrchr(bstrFileName.Copy(),'\\');
wcstok(pwchar, _T("."));
pwchar2 = wcstok(NULL, _T("."));
CComBSTR bstrName(pwchar2);
bstrName.ToUpper();
if (bstrName.operator ==(_T("LPT")))
{
ipGxChild.CreateInstance(CLSID_SimplePointLayerGxObject);
ipGxChild->put_FileName(bstrFileName);
ipGxObj = ipGxChild;
ipGxObjArray->Insert(-1, ipGxObj);
pFileNames->Remove();
}
bstrFileName.Empty();
pFileNames->Next(&bstrFileName);
}
IEnumGxObjectPtr ipEnum(ipGxObjArray);
*ppChildren = ipEnum.Detach();
return S_OK;
}Note that the filename is written to the GxObject so that the GxObject knows where the data is located.
The members of the interface, IGxObjectFactoryFileExtensions, simply indicate which file extensions are associated with the GxObjectFactory. The property ActivationExtensions indicates the minimal set of file extensions that should cause the factory to be activated. The property RelevantExtensions indicates the complete set of file extensions relevant to the factory. If you have a data type that has multiple files associated with it, then you will need to specify every file extension that is required to successfully load your data, separated by a pipe (|) character. For example, a layer file is a single file with the extension .lyr, but it may have an associated .xml file, so the GxLayerFactory returns "lyr" for ActivationExtensions and "lyr|xml" for RelevantExtensions.
For the SimplePointLayer, only one file extension applies, which is '.lpt'. If the specified file extension does not match any of the file extensions in a given folder, the factory will not be activated.
STDMETHODIMP CSimplePointLayerGxObjectFactory::get_RelevantExtensions(BSTR* extSet)
{
CComBSTR bstr(_T("lpt"));
bstr.CopyTo(extSet);
return S_OK;
}
STDMETHODIMP CSimplePointLayerGxObjectFactory::get_ActivationExtensions(BSTR* extSet)
{
CComBSTR bstr(_T("lpt"));
bstr.CopyTo(extSet);
return S_OK;
}To be able to uniquely identify your GxObject factory class, you should define and implement a new interface called ISimplePointLayerGxObjectFactorythis interface does not require any members, as its only function is identification.
Now register the SimplePointLayerGxObjectFactory to the ESRI GX Object Factories component category so that ArcCatalog can find the factory and account for the data source.
Every Layer object should have a LayerFactory that is responsible for generating the layer. CadLayer objects, for example, can be created by the CadLayerFactory. All layer factories must implement the interface ILayerFactory and must be registered in the Layer Factory component category.
Layer factories are used by ArcCatalog to generate map layers for a given GxObject. If a GxObject encapsulates geographic data that can be viewed as a layer, the layer's factory will assume the task of generating the Layer object for the data. Once the layer has been created for a GxObject, it can be viewed in a Map.
For example, before a GxObject can be previewed in the geography view window in ArcCatalog, its associated LayerFactory object is asked to create the layer that will be viewed in the Map. More precisely, what happens is the IGxObject::InternalObjectName property is retrieved from the GxObject to obtain a Name object that represents the data. Name objects are used extensively by ArcCatalog to browse datasets and indicate their location. Remember that it is the Name object that references the data and not the GxObject itself. All registered layer factories are then prompted if they can create (ILayerFactory::CanCreate) the data. If a layer factory does apply to the given object, the method ILayerFactory::Create is finally called to generate the layer or layers to add to the map. This method returns the layer or layers in an enumeration.
The illustration above shows the process for previewing the Layer for a GxObject in ArcCatalog.
Add a new class to your project called SimplePointLayerFactory, which will be responsible for generating the SimplePointLayer. Implement the ILayerFactory interface. This is the only interface you need to implement on the custom layer factory.
The SimplePointLayerName object is returned when the IGxObject::InternalObjectName property of the SimplePointLayerGxObject is retrieved. The layer factory, SimplePointLayerFactory, creates an instance of the custom layer enumeration, SimplePointLayerEnumLayer. More details on these objects will follow in the sections below.
The CanCreate method of ILayerFactory is passed in an IUnknown reference to a Name object, which is the internal object that the given GxObject represents. You will need to query this Name object to determine if it represents the desired dataset. For the simple point data source, the Name object will be SimplePointLayerName). Therefore, to determine if your layer factory can create the layer for the input object, QI the object for the ISimplePointName interface. If the QI succeeds, cache the IName::NameString value and indicate that the layer can be created.
STDMETHODIMP CSimplePointLayerFactory::get_CanCreate(IUnknown * inputObject, VARIANT_BOOL * ok)
{
if (!ok)
return E_POINTER;
*ok = VARIANT_FALSE;
//see if input is an SimplePointLayerName, get File path
ISimplePointLayerNamePtr ipLyrName(inputObject);
if (ipLyrName)
{
INamePtr ipName(ipLyrName);
ipName->get_NameString(&m_bstrFileName);
}
else
return S_OK;
*ok = VARIANT_TRUE;
return S_OK;
}If you returned true from CanCreate, then the Create method will be called by ArcCatalog to generate the Layer object. This method is passed the same IUnknown reference to the Name object that CanCreate was passed, but this time you must create the SimplePointLayer object for the dataset. Use the ISimplePointLayer interface, which you created earlier to set the location of the data represented by the layer. You should then return a reference to the layer object in a layer enumeration.
STDMETHODIMP CSimplePointLayerFactory::Create(IUnknown * inputObject, IEnumLayer * * Layers)
{
ISimplePointLayerNamePtr ipLyrName(inputObject);
if (ipLyrName)
{
INamePtr ipName;
ipName = ipLyrName.Detach();
ipName->get_NameString(&m_bstrFileName);
}
else
return E_FAIL;
ISimplePointLayerPtr ipSPLyr(CLSID_SimplePointLayer);
ipSPLyr->put_File(m_bstrFileName);
ILayerPtr ipLyr(ipSPLyr);
ISimplePointEnumLayerPtr ipSimplePtEnumLyr;
CComObject<CSIMPLEPOINTENUMLAYER>* pobj = NULL;
CComObject<CSIMPLEPOINTENUMLAYER>::CreateInstance(&pobj);
pobj->Init(ipLyr);
pobj->QueryInterface(&ipSimplePtEnumLyr);
if (ipSimplePtEnumLyr ==0)
return E_FAIL;
IEnumLayerPtr ipEnum(ipSimplePtEnumLyr);
*Layers = ipEnum.Detach();
return S_OK;
}As shown in the code above, it is still prudent to evaluate the Name object in case the Create method is called by a different client that does not call the CanCreate method first.
The reason that a layer enumeration is used is to account for data sources, which may contain multiple datasets for a single Namefor example, a CAD file.
To be able to uniquely identify your layer factory class, you should define a new interface called ISimplePointLayerFactory and implement this in the layer factory class. This interface does not require any members, as its only function is identification.
Now create the custom Name object you will need to identify the simple point datasource. Add a new class to your project called SimplePointLayerName. Although there is no Name abstract class to base your Name object on, you can see by the other Name objects that such a class should implement at least IName and IPersistStream/Variant.
Recall that a SimplePointLayerName object is returned when a SimplePointLayerGxObject is prompted for its IGxObject::InternalObjectName. It is this Name object that references the datasource for the GxObject.
The SimplePointLayerName object will need to identify and locate the simple point data file. The NameString property will be populated when the IGxObject::InternalNameObject property is retrieved.
STDMETHODIMP CSimplePointLayerName::put_NameString(BSTR NameString)
{
m_bstrName = NameString;
return S_OK;
}
STDMETHODIMP CSimplePointLayerName::get_NameString(BSTR * NameString)
{
if (NameString == NULL)
return E_POINTER;
*NameString = m_bstrName.Copy();
return S_OK;
}Name Objects can also be used as lightweight references to the objects they represent. The Open method can be called to actually instantiate a dataset. Since the simple point datasource has been integrated as a Layer object, the Open method should return the SimplePointLayer object.
STDMETHODIMP CSimplePointLayerName::Open(IUnknown * * unknown)
{
if (unknown == NULL)
return E_POINTER;
if (m_bstrName.operator !())
return S_FALSE;
// instantiate the object this Name represents
ISimplePointLayerPtr ipSPLyr(CLSID_SimplePointLayer);
ipSPLyr->put_File(m_bstrName);
*unknown = ipSPLyr.Detach();
return S_OK;
}To be able to uniquely identify your name class, you should define a new interface called ISimplePointLayerName and implement this in the name class. This interface does not require any members, as its only function is identification.
Once the IName::Open method has been implemented, the Open method can be called by client code to add a simple point layer to a Map. This is demonstrated by the following ArcMap macro code:
DimpMxDocAsIMxDocumentSetpMxDoc = ThisDocumentDimpMapAsIMapSetpMap = pMxDoc.FocusMapDimpGxDialogAsIGxDialogSetpGxDialog =NewGxDialogDimboolAsBooleanDimpEnumGxObjAsIEnumGxObject bool = pGxDialog.DoModalOpen(0, pEnumGxObj)IfboolThenDimpGxObjectAsIGxObjectSetpGxObject = pEnumGxObj.NextDimpNameAsINameSetpName = pGxObject.InternalObjectNameDimpUnkAsIUnknownSetpUnk = pName.OpenIf TypeOfpUnkIsILayerThenDimpLyrAsILayerSetpLyr = pUnk pMap.AddLayer pLyrEnd If End If
When a layer factory creates a Layer object, it is put into a layer enumeration, and a reference to the IEnumLayer interface of the enumeration object is returned (see the 'Implementing ILayerFactory' section above. Since there are no ArcGIS classes that implement IEnumLayer, you will need to provide your own implementation.
Add a new class to your project called SimplePointEnumLayer, to work with the SimplePointLayerFactory object, and in it implement the IEnumLayer interface.
The IEnumLayer interface has two methods: Next and Reset. The Next method returns an ILayer reference to the next SimplePointLayer object in the set and advances the internal pointer. There will be only one layer in the collection so the end of the set will be reached after the first call. When the end of the enumeration is reached, be sure to return S_FALSE. For the SimplePointEnumLayer class, you can store the collection of layers in a Standard Template Library (STL) vector class.
The IEnumLayer interface provides access to members that allow iteration through a set of Layer objects. You will need to pass the collection of layers to the enumerator class so it can traverse the list of items. Please see the project source code for one possible solution for linking the enumerator class with the collection of layersas the class uses STL, these details will not be discussed in this section.
STDMETHODIMP CSimplePointEnumLayer::Next(ILayer * * Layer)
{
if (Layer == NULL)
return E_POINTER;
if (m_LayerVecIdx > 0)
{
*Layer = NULL;
return S_FALSE;
}
ILayerPtr ipLyr;
ipLyr = m_vectLayer.front();
*Layer = ipLyr.Detach();
m_LayerVecIdx++;
return S_OK;
}In the Reset method, reset the internal pointer to the beginning of the set.
STDMETHODIMP CSimplePointEnumLayer::Reset()
{
m_LayerVecIdx =0;
return S_OK;
}To be able to uniquely identify your layer enumeration class, define a new interface called ISimplePointEnumLayer, and implement this in your SimplePointEnumLayer class. This interface does not require any members, as its only function is identification. It will be used in the SimplePointLayerFactory's Create member.
Now that you have a LayerFactory, Name, and enumeration for your SimplePointLayer, you will be able to see the datasource and show the geographical preview in ArcCatalog.
Go to example code
See Also Creating Cartography, About Custom Layers.