Arcobjects 101 in Delphi Xe2
Arcobjects 101 in Delphi Xe2
1 in
Delphi XE2
Written by Roger Dunn, Senior GIS Programmer/Analyst for Orem, Utah
July 2012
Introduction
Audience
This document explains how to harness the power of ArcObjects within the Delphi XE2
environment. I use the Win32/64 flavor of Delphi, not the .NET version, so this document is aimed at
native Windows Desktop application developers (Is .NET native? I don't know). Also, I have not
programmed any stand-alone applications with ArcObjects as that requires a separate license granted
either by ArcEngine, ArcGIS Run-time, or EDN. This document is intended to help those who want to
develop extensions or custom tools within ArcMap or ArcCatalog.
Prerequisites
You must have the latest ArcGIS Desktop software installed, as well as the latest Delphi XE2
installed. As of this writing, ArcGIS 10.1 Service Pack 1 and Delphi XE2 Update 4 are the latest versions
of those products. However, many of the principles and practices described in this document work with
previous versions of Delphi and previous versions ArcObjects. I used to maintain a separate document
for each version of Delphi, as each version had its own nuances. That became too big a chore, so I am
now going to maintain this document. Previous documentation for programming ArcObjects 9.3.1 in
Delphi 7, 2005, 2006, 2007, and 2009 can be found at http://arcscripts.esri.com/details.asp?dbid=14204
.
I also recommend bookmarking the ArcObjects help on-line, which can be tricky to find. The
location is http://resources.arcgis.com/en/help/arcobjects-net/conceptualhelp . I'll be referring to this
web site sometimes as the ArcObjects Help. As you read the help, remember that with 32-bit Delphi,
you are developing a COM extension, not an Add-In.
Reading Instructions
If you normally read instructions, then you are reading this paragraph. If you skim over
instructions looking for what you need, then you probably missed this paragraph. I want to make a
disclaimer that these are instructions intended to be read by humans who use a computer and are
experienced at doing so. Whenever I use double-quotes around some text, I'm merely naming a lengthy
menu item or file name. The double-quotes help set that text apart from the rest of the sentence and
are not intended to actually BE in the menu item or file name. You should know this by now. For
instance, if I say to go to Tools > Options > Tool Palette and to check on the "Persistent search filter", I
don't actually mean that the Persistent search filter actually has double-quotes around it in the dialog
box.
I am also well aware that there is more than one way to access a given dialog box or a given
function in the Delphi IDE or ArcGIS. Therefore, when I say to right-click something and choose a
particular context menu item, don't complain to me that the stated menu item is also available from the
main menu, or a toolbar, or a keyboard shortcut, and that your way is easier. If I list an instruction to
right-click a project and select Options, that's the same as activating a project (if it's not already) and
then going to Project > Options in the main menu. I know this and don't need to be preached at. I am
also aware of keyboard shortcuts. But since these are customizable for each user, it would be unclear to
have an instruction to press Alt+F7 without an explanation of what I'm assuming that does. I, for one,
use the Visual Studio library of shortcuts, so I run programs with F5, not F9. Use your knowledge of the
Delphi product coupled with these instructions to do things how you want to do them.
Prepare Delphi
Delphi Type Libraries
COM DLLs such as those used in ArcObjects, need to be imported into Delphi using the Type
Library Importer. This can be done manually using the IDE. Since there are a lot of importable files in
ArcGIS Desktop, I have written two batch files to make this job easier. Their names begin with
ImportArcGISCOM. Use either the 32-bit or 64-bit version, depending on your machine CPU type. The
32- and 64-bit versions of the batch file only differ by paths; the 32-bit version uses the C:\Program Files
directory and the 64-bit version uses the C:\Program Files (x86) directory.
You run the batch file from a command prompt, because you have to specify into which version
of Delphi you are importing. In the batch file script (which you’re free to preview and edit in Notepad if
you wish), the specified Delphi version you enter determines where to find the Type Library Importer
executable and where to put the resulting PAS and DCR files. If you wish to import ArcObjects DLLs to
more than one version of Delphi, you must run the command once for each version.
For example, let’s say I am running on 64-bit Windows 7. I want to import ArcObjects DLLs so I
can program with Delphi XE2. I first open a command prompt. Then I use the cd command to change
the current directory to where the ImportArcGISCOM batch file exists. Then I would type the following
and press Enter:
ImportArcGISCOM64 XE2
FYI: When this option is left at "Only dual interfaces", Delphi doesn't create the best PAS files
when importing type libraries. Being a Delphi programmer, you are used to coding with read/write
properties, procedures that return nothing, and functions that return something. But this option makes
it so that every PAS file created with Delphi from ArcObjects contains nothing but functions, all of which
return an HRESULT. Function that would return, say, an Integer, instead have an output parameter of
type Integer, and return an HRESULT. If you want to set the value of a property, you instead call a
function that has the new value as a function parameter, and that function returns an HRESULT. To
further my point, see figures 1 and 2. Which way do you prefer to program? I don't like programming
like Figure 1; it's unnatural. So that's why I change the default setting so my code looks like Figure 2.
Package
The imported Delphi files now need to become part of a package in the IDE. To do this, please
follow these instructions:
1. In the Tool Palette, expand "Delphi Projects" and double click "Package." You'll see
Package1.bpl in the Project Manager.
2. Right-click Package1.bpl and select Options…
3. On the left, click Description.
4. In the Description edit box, type a name for your package, such as "ArcGIS Desktop ArcObjects
10.1". The text you type here will show up in Delphi's Package List dialog.
5. When finished, click OK.
6. Right-click the package again and select Add…
7. In the resulting dialog, browse to your Imports directory where you (or the batch file) put the
imported ArcGIS Desktop files.
8. Select all files that begin with "esri" and then click Open.
9. The files will be added to your project and one of them will be open in the editor window.
10. In the Project Manager window, expand "Build Configurations" and double-click the "Release"
node. This will make that build configuration active. There should be no need to ever build this
library in Debug mode, as the library is merely a bunch of method wrappers around ArcObjects.
11. Right-click the package yet again and select Save As… Select an appropriate place and name for
the package source. Personally, I create the following path and save my package sources there:
C:\Users\<MyUsername>\Documents\RAD Studio\9.0\Projects\Packages\<PackageName>\
12. Right-click the package and select Make.
13. Unfortunately, your project might not compile. You'll have to look at the error message to know
what to do. After each change, I suggest saving the file and recompiling. Appendix A of this
document has a list of the errors that came from my compile today. You will also get a ton of
warnings about the usage of the dispinterface keyword. I reported this problem to
Embarcadero as Quality Central (QC) report number 88680. The report was closed with the
resolution being "As Designed" with no comments from the sysop. So, I don't know what the
deal is.
14. When the project compiles successfully, you're free to do a Build if you want to.
15. Once the package is compiled, you can install it to the IDE. Right-click the package name in the
Project Manager window and select Install. Although the resulting dialog shows that many
components were added to the IDE component palette, my experience is that these cannot be
used unless you have an ArcEngine Developer license. As I do not have one, I cannot help with
how to use these.
16. Go to File > Close All and click Yes if asked to save any changes.
17. If you imported the _TLB.pas files to a custom directory that Delphi didn't create, you need to
tell other projects where to find your definitions. To do so to Tools > Options… > Environment
Options > Delphi Options > Library, click the ellipses for the Library Path. Proceed to add the
path where the compiled .dcu's are. Click OK. Then in the Browsing path, add the location of
the .pas files themselves and click OK. Then click OK in the Options dialog.
18. Browse to the folder where the ESRI Type Library files were generated
19. You are now ready to develop applications in Delphi XE2 for ArcGIS Desktop 10.1.
Documentation
Load up the ArcObjects API web site I mentioned earlier. In the left-hand pane you see a
number of folders with names that might look familiar. These are the names of your imported PAS files,
without the initial "esri" and without the trailing "_TLB.pas". Suppose you are using the site to search
for help, and the thing you find that you need is an IMxDocument interface. Look at the very top of the
page and you'll see ArcMapUI italicized and in parentheses. That means you need to add
esriArcMapUI_TLB to your uses clause.
Creating an ArcObjects Project
Sample Project
Clearly, if you are reading this, you have in mind some functionality you would like to add to
ArcGIS Desktop via programming in Delphi. Now that Delphi is prepared, it's time to develop the tool.
But as there are so many different kinds of tools you could develop, I can't go over them. Rather, I will
walk you through the development of a command button which opens a window. The window's simple
purpose is to tally the consonants, vowels, numbers, and punctuation marks of all the layer names in the
map. The window refreshes itself as layer names are changed. Code for the project is included with this
document, but where's the fun in that? How will you learn how such a tool is built from scratch?
Implementing Interfaces
Rather than insert a dissertation on what COM is, if you don't know, you'll have to research that
on the internet. But what we're trying to do here is tell ArcGIS that we've got an object that implements
(has hard code for) a button. But we can't call our functions whatever we want—we have name our
functions the same way ArcGIS Desktop does, and we do that by implementing interfaces like
ICommand. ArcMap has no idea what we're going to do when the Click procedure is executed, but it
knows we have a Click procedure because we implemented ICommand.
1. In ViewAlphaWindowImpl.pas, we have to put some code in the method stubs. What follows is
the name of each method, followed by the code you need to put in the method body, followed
by a comment or two on what we're doing:
a. Get_Bitmap:
i. Result := 0;
ii. I'll show later how to add a custom bitmap to your button or tool.
b. Get_Caption
i. Result := 'View Alphanumeric Stats';
ii. The Caption is the text of the button in ArcGIS Desktop
c. Get_Category
i. Result := 'Analysis Windows';
ii. The Category string is what you see in ArcGIS Desktop when you are in
Customize mode dialog box, in the Commands tab. Categories are listed on the
left side. In your objects, you can specify existing categories, or new ones of
your own
d. Get_Checked
i. Result := False;
ii. A checked button is depressed button that shows that something is on or active.
For instance, the Edge Snapping command is pressed in when Edge Snapping is
on. This method is executed frequently so it better have quick logic.
e. Get_Enabled
i. Result := True;
ii. An enabled button can be clicked whereas a disabled button cannot. This
method is executed frequently, so the internal logic should be simple.
f. Get_HelpContextID
i. Result := 0;
ii. I have no idea how to implement help, so don't ask me.
g. Get_HelpFile
i. Result := '';
ii. See comment on Get_HelpContextID.
h. Get_Message
i. Result := 'The Alphanumeric statistics window shows you totals for character
types within the names of layers in your map';
ii. This string used to be displayed in the status bar when the user hovered over
your button. In 10.1, it is the body of the tooltip window.
i. Get_Name
i. Result := 'AlphaWindow.ViewAlphaWindow';
ii. This string is not visible to ArcGIS Desktop users, but to you as a programmer
and to other programmers.
j. Get_Tooltip
i. Result := 'View Alphanumeric Statistics Window';
ii. This string used to be what was displayed in the pop-up tooltip window when
the user hovered over your button. In 10.1, it is the bold title of the tooltip
window.
k. OnClick
i. ShowMessage( 'Button Clicked!' );
ii. You have to add Vcl.Dialogs to the uses clause of your implementation section.
l. OnCreate
i. Before you create code here, you need to understand what the OnCreate
procedure is. Your custom object will be "CoCreated" several times before
ArcGIS Desktop is ready to use it. When it IS ready, it will call this OnCreate
method and pass you a handle to the application server in the form of an
IDispatch parameter called Hook. Hook might point to an instance of ArcMap,
ArcCatalog, ArcScene, or other ESRI program. Since this button is only intended
for ArcMap, we want to make sure we grab a reference to ArcMap only.
ii. In the interface section, add esriArcMapUI_TLB to the uses clause.
iii. Make a private section within TViewAlphaWindow.
iv. Add a variable pArcMapApp of type IMxApplication to the private section.
v. In the implementation section's uses clause, add System.SysUtils.
vi. In the OnCreate method, your code will be:
1. Supports( Hook, IMxApplication, pArcMapApp );
2. Save the unit and compile the project. It should compile with blue dots in the method stubs
because we're in debug mode. Also, if you don't like that dialog that comes up and shows you
what unit is being compiled, you can turn that off under Tools > Options… > Environment
Options > Compiling group box > Show compiler progress.
1. On a command line, you can call regsvr32 followed by the full path to your DLL in double-
quotes. Optionally, you can change directory "cd" to your DLL first and THEN call regsvr32
followed by the simple name of your DLL.
2. Within Delphi you can go to Run > ActiveX Server > Register, which will first compile your project
and then register it.
3. You can make a batch file with the register command in it, then double-click the file to register it
in the future without typing anything and without opening the project in Delphi.
4. Another method I really like is being able to register a DLL by right-clicking on it and selecting
Register. To enable this takes some setting up on Windows. If you're interested, follow these
steps:
a. In Windows Explorer, navigate to the following directory, substituting drives and folder
names as appropriate. You might have to enable the showing of hidden directories:
C:\Users\<Username>\AppData\Roaming\Microsoft\Windows\SendTo
b. Right-click an empty area and select New > Shortcut
c. Browse to the following file: C:\Windows\SysWOW64\regsvr32.exe and click Next.
d. Name the shortcut Register Server, or something like that and click Finish.
e. Copy the shortcut file and paste it into the same directory to get Register Server - Copy.
f. Rename Register Server - Copy to Unregister Server.
g. Right-click the shortcut and go into Properties.
h. In the Shortcut tab, find the Target edit box.
i. At the end of the line, type a space and then -u and click OK.
j. Now you can register and unregister servers by right-clicking them and choosing the
appropriate action.
Register with ArcGIS Desktop
To tell ArcMap (or ArcCatalog) to use your DLL, instead of the hundreds already on your
computer, you have two choices. You can add the file to ArcGIS Desktop in Customize mode, but only if
the DLL contains objects that implement ICommand and/or ITool. Currently ours does, but it won't
later. Also, if you have UAC enabled, you have to run ArcMap as administrator to do it successfully.
More on that in a few paragraphs.
The other option is to use a program provided by ESRI to register your DLL with Desktop. This is
more helpful when building an install for your DLL instead of walking your users through the manual
way. The program's path is C:\Program Files (x86)\Common Files\ArcGIS\bin\ESRIRegAsm.exe. It is a
command-line utility that can take an ActiveX or .NET library and register it with Windows and ArcGIS
Desktop at the same time. If you double-click the program from Windows, you get a dialog that
documents the usage of the tool, although poorly.
The ESRIRegAsm program can register your DLLs using three of the four methods described
above: you can do it manually at the command line, in a batch file, or in a custom Windows shortcut.
Delphi can also do it, but you have to set it up first in the Tools menu. To register the DLL in this sample
walk-through, the command would look like this:
You should get a Registration Succeeded message box if everything went fine. However, if you
open ArcMap now, your tool will not be accessible. The reason is that we didn't specify that what the
DLL contained was a button for ArcMap's toolbar. This requires using the /f switch. Meaning, you need
to register the classes in your DLL with component categories. Here's how:
1. Create a new, empty text file in your project folder called AlphaWindowCats.xml.
2. The text inside the XML file should look like this:
<?xml version="1.0"?>
<Categories ver="1">
<Category CATID="X">
<Class CLSID="Y"/>
</Category>
</Categories>
Hopefully you get a successful message. What this does is create a file in C:\Program Files
(x86)\Common Files\ArcGIS\Desktop10.1\Configuration\CATID. The name of the file is the UID of the
type library (not just the class) in curly braces, followed by an underscore, followed by the name of the
DLL, with an ecfg extension instead. The creation of this file is important and I'll tell you why.
Registering your file right within ArcMap or this method creates the ECFG file in a Windows protected
directory. That means that if you or your user attempts to register the DLL without administrative
privileges or without UAC turned down, then nothing will happen or you'll get an exception.
Also, once you successfully register your DLL, you can see it in the Component Category
Manager (categories.exe). You must close and restart it as it doesn't have a Refresh button. Go down to
the Esri Mx Commands category, expand it, and you'll see your new class listed alphabetically with the
others.
Preliminary Test
If you start ArcMap now and enter Customize mode you'll find your simple command under the
category we specified for it: Analysis Windows. You'll notice that the tool has an icon, but that's a
default one. You'll notice the name of the button is the caption: View Alphanumeric Stats. If you
highlight the command and press the Description button, you'll see a Tooltip with the button's Caption
and the Message we specified in code. Drag and drop the button anywhere you want on any toolbar.
Notice the icon goes away.
Close the Customize window and hover over the View Alphanumeric Stats button. Instead of
the Caption being at the top of the tooltip, the result of the Get_Tooltip function is at the top. And the
Message is still the body of the Tooltip. Click the button and you should get a Button Clicked! message.
Add a GIS layer to your map, and rename it to 123_ABCD. This is for testing purposes. There
are three alphabetic characters, one punctuation character, and four letters. Save the map into your
Delphi project folder. This is important since sometimes your custom code will crash ArcMap and it's a
pain to recreate the map document.
Specifying an Icon
Icons are great for obvious user interface reasons. ESRI provides a whole bunch of them to use
for free. Follow these instructions to put an icon on your new button:
if pBitmap = 0 then
pBitmap := LoadBitmap( HInstance, 'ALPHASTATCMD' );
Result := pBitmap;
Note that you can never recompile/rebuild your project while ArcMap is open and your tool is in it.
That's because Windows creates a lock on your DLL that makes it not be overwritten when a client is
using it.
Using Forms
One tricky thing to get working in ArcObjects is form programming, because they're used in
Delphi somewhat differently than in Visual Basic or .NET, and it's hard to interpret the code from those
environments. Our example will have a floating dockable window that shows, as stated before, the
number of consonants, vowels, numbers, and punctuation marks in all the layer names in the map.
8. Create a public procedure called CalcAndShowStats which takes a TStrings object called
CalcAndShowStats and has the following code:
procedure TTAlphaStatsForm.CalcAndShowStats(LayerNames:
TStrings);
var
C, V, N, P: Integer;
begin
Calculate( LayerNames, C, V, N, P );
ShowStats( C, V, N, P );
end;
9. By putting these pieces of logic into three different methods, testing becomes easier. You could
use Delphi's DUnit testing suite, making the private functions public, and testing the results of
various inputs without ever opening ArcMap. None of the three methods described here uses
any ArcObjects. It uses Delphi controls, system units, and data structures independent of GIS.
The DUnit testing suite would create the form as TAlphaStatsForm instead a COM object. You
could even take out the code that doesn't deal with controls and separate it.
10. Save All.
i. Like the OnCreate method of the button, there is some work to do here. And
remember that this isn't when the object gets created—it's when ArcGIS says
it's done creating it.
ii. In the interface section's uses clause, add esriArcMapUI_TLB.
iii. Add a private member called pArcMapApp of type IMxApplication.
iv. In the implementation section's uses class, add System.SysUtils.
v. Notice how we create the form. We don't use the normal Create constructor
because the parent is not a VCL object. But it's not enough to call
CreateParented with a valid handle either. If you don't call Show afterwards,
then ArcMap will show a blank form with no controls on it. It's also interesting
to note that ArcMap remembers which dockable windows were visible and
which weren't when it last closed. If your dockable window was closed last
time, the call to Show won't force it to be visible—ArcMap will control that.
f. OnDestroy
i. FreeAndNil( AlphaStatsForm );
ii. The reason I don't just Free the form is because I also want to set the
AlphaStstForm variable to nil after I free it. In this DLL, the window COM object
is being created in the multi-instance, apartment mode (see the call to
TAutoObjectFactory.Create at the bottom of the unit). We only want to create
one window, and have one pointer to it, and free it once.
22. Save All.
procedure TViewAlphaWindow.FindAlphaWin;
var
pDocWinMgr: IDockableWindowManager;
pWinID: IUID;
begin
if pAlphaWin = nil then
if Supports( pArcMapApp, IDockableWindowManager,
pDocWinMgr ) then
begin
pWinID := CoUID.Create;
pWinID.Value := GUIDToString( CLASS_AlphaStatWindow );
pAlphaWin := pDocWinMgr.GetDockableWindow( pWinID );
end;
end;
procedure TViewAlphaWindow.OnClick;
begin
FindAlphaWin;
if pAlphaWin <> nil then
pAlphaWin.Show( not pAlphaWin.IsVisible )
end;
7. IUID is an interface declared in the esriSystem_TLB unit, so add that to the uses clause of the
implementation section.
8. Since the OnClick procedure isn't showing a message dialog, you can remove Dialogs from the
uses clause of the implementation section.
If you try and compile the project at this point, you'll get a mysterious error: E2037 Declaration
of 'Get_Bitmap' differs from previous declaration. Yet, you didn't change anything related to that
function. This is a classic example of identifier resolution that happens in Delphi. The problem lies in
the fact that OLE_HANDLE is defined in two different ways in units that your project uses.
If you Ctrl+Click OLE_HANDLE on the line that gives you the error, Delphi will open
esriSystem_TLB. It defines it as an Integer. Now go back to your unit and find the declaration of the
Get_Bitmap function. Ctrl+Click on that OLE_HANDLE. Delphi opens Winapi.ActiveX and shows you that
it is an alias type for THandle. Ctrl+Click again to find THandle is an alias for System.THandle. Ctrl-Click
one last time on System.THandle and you'll find it's an alias for NativeUInt, which is NOT the same as
Integer. Delphi doesn't think the declaration and definition of Get_Bitmap match because it interprets
OLE_HANDLE differently in each section. One solution is to fully qualify OLE_HANDLE in the definition of
Get_Bitmap like this: Winapi.ActiveX.OLE_HANDLE;
When it comes to identifier resolution, the order of units in your uses clause completely comes
into play. Delphi will always use the definition for a type by going through the units backwards as they
are listed in the uses clause. Typically, then, the identifiers you have to fully qualify belong in units that
are towards the front of your uses clause. In our example above, if you put esriSystem_TLB in front of
ActiveX, then that also fixes the problem. The only reason I don't like that, personally, is that
esriSystem_TLB is not needed in the interface section; it's needed in the implementation section.
Whatever works for you.
Debugging Your Project
A few steps need to be taken in each ArcObjects project you create in Delphi to make debugging
possible. Those requirements are suggested here:
Anyway, you'll notice that pAlphaWin never stops being nil. Therefore, the button is disabled
forever. The reason is simple, but takes some analysis. Although we have declared the COM wrapper
and the actual form class for the window, we have not registered it with ArcGIS Desktop. So, we'll do
that similarly to how we did the button.
1. Close ArcMap and return to the Delphi IDE.
2. Open AlphaWindowCats.xml in a text editor.
3. The text inside the XML file should already look something like this:
<?xml version="1.0"?>
<Categories ver="1">
<Category CATID="{B56A7C42-83D4-11D2-A2E9-080009B6F22B}">
<Class CLSID="{980ABA12-3B92-4AAA-8999-8682592A3D7F}"/>
</Category>
</Categories>
4. Add another Category tag under the existing one with a Class subtag.
5. Find and run C:\Program Files (x86)\ArcGIS\Desktop10.1\bin\categories.exe.
6. In the "Find Category" edit box, put "mx dock" and then click Find.
7. You'll come to Esri Mx Dockable Windows.
8. When you highlight the Esri Mx Dockable Windows folder, there is a read-only GUI D displayed
near the bottom of the window. Copy that GUID to the clipboard.
9. In your XML file, put the copied GUID, including the curly braces, in the new CATID attribute
(and keep the double-quotes around it).
10. Back in Delphi, in the Type Library Editor, click the AlphaStatWindow class on the left-hand side.
Look at the Attributes window. Copy the GUID to the clipboard.
11. In your XML file, put the copied GUID, including the curly braces, in the new CLSID attribute (and
keep the double-quotes around it).
12. Your new XML should look something like this, although the CLSID values might be different if
you created your own project. They'll be the same if you copied mine.
<?xml version="1.0"?>
<Categories ver="1">
<Category CATID="{B56A7C42-83D4-11D2-A2E9-080009B6F22B}">
<Class CLSID="{980ABA12-3B92-4AAA-8999-8682592A3D7F}"/>
</Category>
<Category CATID="{117623B5-F9D1-11D3-A67F-0008C7DF97B9}">
<Class CLSID="{8B8EEFAF-C160-4DBA-90F0-EE26FF41D494}"/>
</Category>
</Categories>
Examining the ArcObjects documentation shows that we need to have our COM window object
(not the VCL one) implement more interfaces. Luckily, all event-related interfaces have Events at the
end of their name, so it's just a matter of finding the right ones.
Exploring the ArcMapUI namespace reveals the IDocumentEvents interface which has events for
CloseDocument, NewDocument, and OpenDocument. Well, what about layer changes? Well, let's start
with that.
There are a few things that happened behind the scenes that are worth noting. In fact, your
code probably won't compile because of them. First of all, esriArcMapUI_TLB was not added to your
project's list of dependencies. Although that happens when you create a new COM object with the COM
Object Wizard, it doesn't happen when you add an existing interface to an existing object in your
project.
However, esriArcMapUI_TLB was still generated. And, what's more, all of its dependencies were
regenerated and put in your project directory. If you do a Syntax Check on your project, it will fail due to
one or more problems listed in Appendix A. The way to fix this situation is to close the generated units
in the IDE and remove them from the directory. That way, Delphi will use the fixed ones that you
generated at the beginning of this document, which are likely in your Imports directory. Now a Syntax
Check or compile should work correctly.
In the BeforeCloseDocument function, return False because our window will never prevent a
document from closing. This is per the documentation. In the OnContextManu menu, set Handled to
False because we don't handle context menus.
Next, we need a private method that goes through the layers in the document and makes a
string list out of them and then sends it to AlphaStatsForm. Use this code:
procedure TAlphaStatWindow.UpdateStatWindow;
var
pApp: IApplication;
pMxDoc: IMxDocument;
pMaps: IMaps;
slLayerNames: TStringList;
I, J: Integer;
procedure AddName( pLayer: ILayer );
var
pGroup: ICompositeLayer;
iSubLayer: Integer;
begin
slLayerNames.Add( pLayer.Name );
if Supports( pLayer, ICompositeLayer, pGroup ) then
for iSubLayer := 0 to pGroup.Count - 1 do
AddName( pGroup.Layer[ iSubLayer ] );
end;
begin
if Assigned( AlphaStatsForm ) then
if Supports( pArcMapApp, IApplication, pApp ) then
if Supports( pApp.Document, IMxDocument, pMxDoc ) then
begin
slLayerNames := TStringList.Create;
try
pMaps := pMxDoc.Maps;
for I := 0 to pMaps.Count - 1 do
for J := 0 to pMaps.Item[ I ].LayerCount - 1 do
AddName( pMaps.Item[ I ].Layer[ J ] );
AlphaStatsForm.CalcAndShowStats( slLayerNames );
finally
slLayerNames.Free;
end;
end;
end;
17. As an aside, notice that this procedure declares a sub-procedure. This is for recursion if
necessary. If a group layer has a group layer has a group layer, this procedure will take care of
adding all the names.
18. For this to compile, you have to add two units to the uses clause of your implementation
section: System.Classes (for TStringList) and esriCarto_TLB (for IMaps interface).
19. Put the following code in CloseDocument, MapsChanged, NewDocument, MapsChanged, and
OpenDocument:
UpdateStatWindow;
20. You're free to run the program if you want to, but none of the events fire for your document?
Any guesses why? That's right: because you haven't registered your object as a listener to
those events. The code to register a COM object as a listener in Delphi is short, but not trivial.
Keep following along to achieve that.
21. The key procedures we're going to use are InterfaceConnect and InterfaceDisconnect. They
both make use of an integer that serves as a kind of handle.
22. Declare a private variable called iDocEventConn, which in this case stands for Integer,
Document, Event, Connection. Its type is Integer.
23. Three of the parameters to InterfaceConnect are now known:
a. IID is going to the GUID of IDocumentEvents. You won't have to copy and paste it.
You'll use a constant defined in esriArcMapUI_TLB.pas
b. Sink is going to the COM object that we have (are) created, cast to an IUnknown.
c. Connection is going to be iDocEventConn, the variable in our class we just defined.
24. The missing parameter we don't know is Source. We have to use the documentation to figure
out what COM class instance will be firing this event (not a COM interface). In our case, the
COM class that does this is the MxDocument class. But which instance is it? We grab it the
same way we did above: by casting our IMxApplication variable to IApplication, and casting its
Document property to an IMxDocument. So, although we grab a COM interface to the object,
it's not an interface that's firing the event—it's an object that implements that interface.
25. The last question is where to put the InterfaceConnect and InterfaceDisconnect procedures.
That's easy—in the OnCreate and OnDestroy methods of our custom COM object,
TAlphaStatWindow. Those methods now look like this:
procedure TAlphaStatWindow.OnCreate(const hook: IDispatch);
var
pAppl: IApplication;
pMxDoc: iMxDocument;
begin
Supports( hook, IMxApplication, pArcMapApp );
if not Assigned( AlphaStatsForm ) then
begin
if Supports( pArcMapApp, IApplication, pAppl ) then
AlphaStatsForm := TAlphaStatsForm.CreateParented(
pAppl.hWnd );
if Assigned( AlphaStatsForm ) then
AlphaStatsForm.Show;
end;
if Supports( pArcMapApp, IApplication, pAppl ) then
if Supports( pAppl.Document, IMxDocument, pMxDoc ) then
InterfaceConnect( pMxDoc, IID_IDocumentEvents, Self as
IInterface, iDocEventConn );
end;
procedure TAlphaStatWindow.OnDestroy;
var
pAppl: IApplication;
pMxDoc: iMxDocument;
begin
if Supports( pArcMapApp, IApplication, pAppl ) then
if Supports( pAppl.Document, IMxDocument, pMxDoc ) then
InterfaceDisconnect( pMxDoc, IID_IDocumentEvents,
iDocEventConn );
FreeAndNil( AlphaStatsForm );
end;
26. Your project should compile and run. When it does, you'll see your stats form has the following
statistics when you open the document that has the 123_ABCD layer in it:
a. Consonants: 3
b. Vowels: 1
c. Numbers: 3
d. Punctuation: 1
27. Perform other events such as starting a new document or opening another one. Your statistic
window will update. This statistics work even if the map document has two or more data
frames (IMaps). But there are still more events that occur that need to update the
Alphanumeric Statistics Window.
28. More research yields the IActiveViewEvents interface which fires ItemAdded and ItemDeleted.
The trouble is that the currently active view fires this, and this could be the data view, layout
view, or other possible views. So, let's get-er done.
29. IActiveViewEvents is declared in Carto, so in the Type Library Editor, click the AlphaWindow
type library name, click the Uses tab, show all type libraries, and turn on Esri Carto Object
Library 10.1. Then go back to Show Selected.
30. Select the AlphaStatWindow object, click the Implements tab, right-click and insert the
IActiveViewEvents interface. OK.
31. Refresh the implementation.
32. Make the AlphaStatWindowImpl.pas unit active in the IDE editor.
33. Save All.
34. Delete the esri* files generated just now, except those that are explicitly part of your project.
35. Move esriCarto_TLB from the uses clause in the implementation section to the interface
section.
36. Add a call to UpdateStatWindow in the following events: ItemAdded and ItemDeleted. The
documentation says that ContentsChanged is fired with documents opening, but we got that
already.
37. If Delphi can't resolve IDisplay, then add esriDisplay_TLB to the interface uses clause.
38. If esriDrawPhase can't resolve, then add esriSystem_TLB to the same section.
39. Lastly, if IEnvelope can't resolve, then add esriGeometry_TLB also.
40. If you try to compile and Delphi gives you an error that the implementation of
IDockableWindowDef.Get_ChildHWND is missing, then this is another case of identifier
mismatch. Fully qualify OLE_HANDLE with Winapi.ActiveX in the front.
41. Lastly, we have to register our object as a listener to the appropriate event type on the
appropriate objects. Unlike the IDocumentEvents object, where we listen to one object, we
need to listen to various objects in the map document for additions and deletions to the map.
What's more, layers can be added and deleted to a map other than the active map (the active
view), so we need to have multiple listeners.
At this point, I lost interest in writing this document, and decided to post it on-line for you to see
and use. I really have a lot to do so I must abandon this project at this time. The Appendix that follows
was copied and pasted from a previous version of this document.
Appendix A
A list of IDE compiler errors I received when compiling the ArcObjects PAS files into a package.
In most of these cases, the problem was that the generated property differed in exact type from a getter
or setter method. I believe the getter and setter method signatures because that's really what
properties are calling underneath. Therefore, I change the property's type to match that of the method.
Interface: IEnumNetEIDBuilderGEN
Property: EIDs
Solution: I changed PPSafeArray1 to PSafeArray
Interface: IEnumNetEIDBuilder
Property: EIDs
Solution: I comment out the EIDs property since this is a write-only array with two input parameters,
and a void return parameter. Therefore, only the Add procedure is necessary.
Interface: IGxDialog
Property: StartingLocation
Solution: I changed POleVariant1 to OleVariant
Interface: IVB6ReferenceHandler
Property: VBProject
Solution: I added " write _Set_VBProject" after OleVariant and before the semicolon