Prerequisites:
Esri ArcMap 8.x (ArcGIS Desktop / Arceditor / or Arcinfo)
Borland Delphi 6 (Versions 4 To 6 Should Work, But The Samples Are IN Written in Version 6)
A Lot of coffee j
HOW to Begin
THE FIRST Step Before You Start Coding Is To Install The ArcObjects Controls ActiveX in Delphi.
Select "Import ActiveX Control" from The "Component" Menu in Delphi's IDE.
SELECT "ESRI ArcObjects Controls 8.1 (Version 1.0)" from the list of available activiX Controls (Version Info Might Difer Depending On What Version of ArcGIS You Have Installed).
Click On The "Install" Button and SELECT DESTINATION PACKAGE (You Might Want to Create A New Package Since The Resulting Units Are Quite Large).
After Installing The Control The Following Files Should Appear in 'Imports' Folder Under Delphi Main Folder:
"Esricore_tlb.dcr"
"Esricore_tlb.pas"
"ESRICONTROLS_TLB.DCR"
"Esricontrols_tlb.pas"
The First Project: An Command IMPLANTATION
In this simple project we are going to create an ArcMap Command. Commands can be placed both on the various toolbars found in ArcMap or on the menus. In order to use an ActiveX object as a Command it will have to implement the ICommand interface. Objects That area to be loading by arcmap will have to reside in an activityX library.
Start this project by closing all open files / projects in Delphi's IDE. Then create an ActiveX Library by selecting "File / New / Other" from the menu. Select the tab marked ActiveX and then the ActiveX Library icon.
Now save the project. The name of the completed library (DLL) will be the same as your project name. The shell of the library is now complete but in order for it to be useful we must now create one or more COM objects to fill it with do the same thing as before but select the COM Object icon instead, when you click OK the following wizard will appear:. In the field "Class name", enter the name of the new COM Object (BTW do not enter a T before the name since Delphi will add one automatically). Leave the instancing and threading model at the default "Multiple Instance" and "Apartment" in order not to make it more difficult than need be. When you type a class name Delphi will automatically suggest an interface implementation based on that name but since this simple COM object should only implement IUnknown and ICommand you should clear that field. Next click on the "List" button and select ICommand (make sure that the type library is "esriCore.olb" Finally Click "OK".
You Should Now Have A New Unit with a class declaration Like this and function stubs:
Uses
Comobj, ActiveX, Project1_TLB, STDVCL, ESRICORE_TLB;
Type
TDELPHICOMMAND = Class (tautoobject, iCommand)
protected
Function GET_BITMAP (Out Bitmap: OLE_HANDLE): HRESULT; STDCALL;
Function GET_CATEGORY (OUT CATEGORYNAME: WIDESTRING): HRESULT; stdcall;
Function get_helpContextId (Out Helpid: Integer): HRESULT; stdcall;
Function get_helpfile (Out HelpFile: WideString): hResult; stdcall;
Function GET_MESSAGE (OUT message: WIDESTRING): HRESULT; stdcall;
Function Get_Tooltip (Out Tooltip: WideString): hResult; stdcall;
Function Onclick: hResult; stdcall;
Function Oncreate (Const Hook: Idispatch): hResult; stdcall;
{Protected Declarations}
END;
Some times Delphi's import wizard fails to get the entire interface and you might see messages that the time / date has changed for "esriCore_TLB". The best way to make certain you have the complete interface declared is simply to open the file and copy / paste the interface declaration and write the implementation part yourself (this is also necessary if your COM object implements more than one ArcObjects interface since the wizard only allows you to select one) .Now it's time to complete the code. First add a private variable that will Hold The Pointer to The Main Application Object of Arcmap Like this:
Private
m_papp: ketplication; // ArcMap Application
It's private becault no-one outside the objects needs to access it. Now address:
CommandBitmap: Tbitmap; // Glyph for CommandButton
The Reason It's in The Protected Section Is That It Has To BE Accessible To Arcmap.
Next We finish theimentation of the Most ostant function "oncreate". It Should Look Something Like this:
Function TDELPHICOMMAND.ONCREATE (Const Hook: idispatch): hResult;
Begin
Result: = S_OK;
Try
M_Papp: = hook as ketplication;
Except
Result: = s_false;
END;
END;
As you can see the parameter hook is of the type IDispatch but since we need IApplication we have to cast it as such (as is actually replaced by a call to the IUnknown method "QueryInterface" by the compiler). The return type of all ArcObjects Interfaces IS Always HRESULT SO in Order NOT to HAVE A LOT OF Compiler Warning We Must Give Result A Value.
Next comes the impLementation of "get_enabled" Which is master a bit more intrcmap.
Function TDELPHICOMMAND.GET_ENABED (OUT Enabled: WordBool): hResult; var pmxdoc: imxdocument
PMAP: IMAP;
PlayerCount: Integer;
Begin
{
Add Some Logic Here to Specify In What State The Application
SHOULD BE IN for the command to be enabled. in this example,
THE COMMAND Is Enabled Only When Twhere Is At Least One Data
Layer loaded in arcmap.
m_papp is set in oncreate
}
Result: = s_false;
Try
PMXDoc: = m_papp.document as imxdocument
Olecheck (PMXDoc.get_focusmap;);
Olecheck (pMap.get_layercount);
IF Playercount> 0 THEN
Enabled: = TRUE
Else
Enabled: = false;
Result: = S_OK;
Except
ON E: Exception DO
Begin
Messagedlg ('exception in delphicommand.get_enabled:' E.MESSAGE, MTERROR, [MBOK], 0);
Enabled: = false;
END;
END;
END;
. --...
The "get_bitmap" function is where the bitmap this you see on arcmap's button is fetched.
In this sample I added the bitmap to the projects ".res" File Using Delphi's Image Editor.
IN this function the bitmap is create and loaded as a resource the the is why the variable for the bitmap is declared as protected.
Function TDELPHICOMMAND.GET_BITMAP (Out Bitmap: OLE_HANDLE): HRESULT
Begin
Result: = S_OK;
Try
// Create and Load Bitmap from resource if not already done.
// Warning! This Code May Result in a Slight Memory Leakage
// if the library is loaded and realaged multiple time.
IF not assigned (commandbitmap) THEN
Begin
CommandBitmap: = Tbitmap.create;
CommandBitmap.LoadFromResourceName (Hinstance, 'Command1');
END;
Bitmap: = commandbitmap.handle; Except
Result: = s_false;
END;
END;
The next function is self-explaining.
Function TDELPHICOMMAND.GET_CATEGORY (OUT CATEGORYNAME: WIDESTRING): HRESULT
Begin
// set the category of this command. This determines where the
// Command Appears in the Commands Panel of the customer z.
CategoryName: = 'mycommands';
Result: = S_OK;
END;
For the rest of the implementation see the incruded sample-code.
Now the our command object is complete and the projects has been built it's time to include it in ArcMap. First though we have to register our COM object in Windows registry. This is done by selecting "Run / Register ActiveX Server" from Delphi's menus. After IT Has Been Success, Registered It's Time To Start Arcmap. Commands Areadded by Using The "Tools / Customize" MENU IN ArcMap.
Click the "Add from file" button and select ActiveX DLL you just made. The COM objects inside (in this case our command) will be added a toolbar or to the list of available commands. To use it simply drag it from the dialog to a menu.
The Second Project: An Extension with Events
This Project is a homewhat more advanced version of the commist project. This Time We will make an extension to arcmap's editor and we will also listen to the events fastein.
We start the project the same way as the first example by creating an ActiveX Library and then a COM object. This time we choose to implement IExtension instead of ICommand but since this object will implement two interfaces and the wizard only allows one we will have to Complete it by Hand. Open the file "esricore_tlb.pas" and copy the interface specifications for iextension and ieditevents. The class declaration shop now Look Something Like this: Type
TDELPHIEXTENSION = Class (Tautoobject, Iextension, IEDITEVENTS)
Private
FSERVER: IEDITOR;
FCOOKIE: Integer;
protected
{Protected Declarations}
{Ioxtension}
Function get_name (out extensionname: wideString): hResult; stdcall;
Function Startup (Var InitializationData: HRESULT; STDCALL;
Function shutdown: hResult; stdcall;
{IeditEvents}
Function OnSelectionChanged: HRESULT; stdcall;
Function oncurrentlayERCHANGED: HRESULT; stdcall;
Function oncurrenttaskchanged: hResult; stdcall;
Function Onsketchmodified: hResult; stdcall;
Function Onsketchfinished: hResult; stdcall;
FunctionAfterDrawsketch (const pdpy: idisplay): hResult; stdcall;
Function onstartediting: hResult; stdcall;
Function OnStopeDiting (Save: WordBool): HRESULT; stdcall;
Function onconflictsdtected: hresult; stdcall;
Function Onundo: HRESUNDO: HRESULT; STDCALL;
Function onRedo: HRESULT; stdcall;
Function OncreateFeature (const object; stdcall): HRESULT; stdcall;
Function Onchangefeature (const object; stdcall;): hRESULT; stdcall;
Function Ondeetefeature (Const Obj: IObject): hResult; stdcall;
END;
As you can see the IExtension interface is very simple and only contains three functions. Startup and Shutdown are executed when ArcMap loads / unloads the library (so be careful of what you try to call on in startup since it might not be loaded at the time ..
First UP IS "GET_NAME" Which Simple Returns the name of the extension so the code bellow is enough.
Function TDELPHIEXTENSION.GET_NAME (OUT EXTENSITIONNAME: WIDESTRING): HRESULT
Begin
EXTENSIONNAME: = 'delphiextension';
Result: = S_OK;
END;
Next is the all important "Startup". In this function we will register ourselves as a implementer of IEditEvents (COM Events works kind of like a mailing list to which you can subscribe if you got the right kind of mailbox (interface) it's also possible To do this through an intermediate object, what's knower...................
Function TDELPHIEXTENSITION.STARTUP (VAR InitializationData: Olevariant): HRESULT
Begin
// InitializationData IS A Variant Which is in this case is the editor object
Result: = S_OK;
Messagedlg ('TDELPHIEXTENSITION.STARTUP', Mtinformation, [Mbok], 0);
Try
// if INITIALIZATIONDATA IS EMPTY We Have Major Problems
IF varisempty (inTILALIZATIONDATA) THEN
Raise Exception.create ('TDELPHIEXTENSION.STARTUP: InitializationData EMPTY!');
// Cast Data As IEditor
FSERVER: = IUNKNOWN (InitializationData) AS IEditor;
// Sign Up for Events from IeditEvents
InterfaceConnect (FServer, IeditEvents, Self as IeditEvents, Fcookie);
Except
ON E: Exception Dobegin
Messagedlg ('TDELPHIEXTENSION.STARTUP:' E.MESSAGE, MTERROR, [Mbok], 0);
Result: = s_false;
END;
END;
END;
As you can see we sign up for events using the InterfaceConnect function where we ask the Editor (FServer) for events from the interface IEditEvents and sends our own IEditEvents interface as reception point, the FCookie var will hold a unique connection number which is needed when WE Disconnect.
Finally We Have The "Shutdown" Part Where disconnect from the events and do any additional cleanup.
Function TDELPHIEXTENSITION.SHUTDOWN: HRESULT;
Begin
Result: = S_OK;
Try
Messagedlg ('TDELPHIEXTENSION.SHUTDOWN', MTINFORMATION, [MBOK], 0);
// if Signed Up for Events, Disconnect from Iedites
IF Assigned (FServer) THEN
InterfaceDisconnect (FServer, IeditEvents, Fcookie);
Except
ON E: Exception DO
Begin
Messagedlg ('TDELPHIEXTENSION.SHUTDOWN:' E.MESSAGE, MTERROR, [MBOK], 0);
Result: = s_false;
END;
END;
END;
THE IMPLEMENTATION for IEDITEVENTS Itself is in this sample is Simple Since It's Only Designed to Show The Function. It looks like this for all methods:
Function TDELPHIEXTENSION.ONSELECTIONCHANGED: HRESULT
Begin
Messagedlg ('TDELPHIEXTENSION.ONSELECTIONCHANGED', MTINFORMATION, [MBOK], 0);
Result: = S_OK;
END;
Finally it's time for installation of our new extension. After building the library and registering the ActiveX (see first sample) it's time to make ArcMap take notice of it. This is easiest done using a program "categories.exe" which comes with ArcGIS ( USUALLY IN C: / ArcGIS / Arcexe81 / bin).
When You Run It you willie............