Click here to get the sample associated with this walkthrough.
In this topic
This scenario is designed to introduce the ArcGIS Engine C++ API Control widgets. Its purpose is not to teach how to set up a C++ environment, how to compile using the make utility, or how to program with the Motif toolkit. Throughout this scenario it is assumed that the developer has a functional C++ environment and knows how to compile a C++ and Motif program in that environment. What this scenario does provide are the steps to take and the code to write to create a standalone ArcGIS controls application for viewing map documents, complete with a popup menu and custom tool.
Rather than walk through this scenario, you can get the completed application from the samples installation location. The sample is installed as part of the ArcGIS developer samples.
If you do not have the developer samples installed, rerun the ArcObjects Software Development Kit (SDK) for Cross Platform C++ install wizard, select Modify, and click the samples feature.
Motif and GTK ArcGIS C++ control widgets are only available on Solaris and Linux. However, GUI applications can be built on Windows with the COM API, including Visual C++, and the ActiveX ArcGIS controls.
Project description
The 'Building applications with C++ and control widgets' scenario demonstrates the steps required to develop and deploy a GIS application using the standard ArcGIS controls within the ArcGIS Engine C++ API. This scenario uses the MapControl, PageLayoutControl, TOCControl, and ToolbarControl as Motif widgets in code written using a standard text editor and compiled using the make utility.
Although only C++ and Motif code samples are shown here, GIMP Toolkit (GTK) C++ code has also been written and can be found with the associated sample.
This scenario demonstrates the steps required to create a GIS application for viewing preauthored ESRI map documents, or MXDs. The scenario covers the following techniques:
- Programming with the ArcGIS Engine in a standard text editor
- Programmatically placing the ArcGIS Engine controls in Motif widget forms
- Loading preauthored map documents into the MapControl and PageLayoutControl
- Setting ToolbarControl and TOCControl buddy controls
- Adding control commands and tools to the ToolbarControl
- Creating popup menus
- Managing label editing in the TOCControl
- Drawing shapes on the MapControl
- Creating a custom tool to work with the MapControl, PageLayoutControl, and ToolbarControl
- Customizing the ToolbarControl
- Deploying the application on a Solaris or Linux platform
Concepts
This scenario is implemented using a text editor, the make utility, and Motif widget ArcGIS controls.
The ArcGIS Engine C++ API provides reusable Motif widget components corresponding to each ArcGIS Engine control. This developer scenario will show how these components can be embedded in a Motif form to build a map viewer application.
Each Motif ArcGIS control widget has a corresponding GTK ArcGIS control widget.
Motif itself is a specification for how graphical user interfaces should look and feel. The Open Software Foundation (OSF) Motif Toolkit, used with the ArcGIS Engine C++ API in this scenario, uses X as the window system and X Toolkit Intrinsics as the API platform. The Motif widgets discussed here are one part of the Motif toolkit. Each widget is a reusable and configurable component of the user interface. The Motif toolkit provides widgets for common tasks, such as the toggle button used in this scenario. Each built-in Motif widget knows how to draw itself as well as some generic behavior; for example, the toggle button knows to call a function when it is clicked. However, it is up to the programmer to implement the callback function, giving the widget its specific behavior. In addition to providing widgets the Motif toolkit does other work for the developer, including handling many user interactions, managing window layout, and redrawing.
The Motif widget ArcGIS controls are custom widgets made available through the C++ API of the ArcGIS Engine Developer Kit. Placed in a top-level application widget, the controls have events, properties, and methods that the developer can access, and like the built-in Motif toolkit widgets, each ESRI control widget knows how to draw itself. The objects and functionality within each control can be combined with other ESRI ArcObjects as well as with custom controls to easily create customized end user applications.
This scenario was written in C++ using the Motif toolkit from the Open Software Foundation. It was chosen to create an application that would run on a Solaris or Linux platform. The same application could also be written in Java. Whichever API you use, your future success with the ArcGIS controls depends on your skill in both the language and ArcObjects.
Design
This scenario has been designed to highlight how the ArcGIS controls interact with each other and to expose a part of each ArcGIS control's object model to the developer.
The scenario starts with building a GUI using Motif to place and manage the controls. The controls are then connected to each other through the SetBuddy methods. At this stage, the application is ready to function as a simple map viewer. The GUI functionality is then extended through a custom tool and event handling. To achieve this, the scenario further explores the API of the visual Motif widgets, as well as the other nonvisual ArcGIS Engine components.
A buddy control is a control that is designed to work in conjunction with another control. For example, the Table of Contents, or TOC, Control is a buddy of the MapControl.
Although the GUI design will be different for GTK developers, the majority of the rest of the application code will be the same. Throughout the scenario, sidebars will highlight areas where GTK programmers will take different steps.
Requirements
To successfully follow this scenario you need the following (the requirements for deployment are covered later in the 'Deployment' section):
-
An installation of the ArcObjects SDK for Cross Platform C++ with an authorization file enabling it for development use.
-
Your favorite text editor.
-
A supported C++ compiler.
-
Solaris: Sun WorkShop (Forte) 6 update 2
-
Linux: GCC version 3.2
-
A configured C++ environment (for example, your compiler configured and your path set).
-
A configured ArcObjects environment. To get your machine ready for ArcObjects development, you must source the file <install_location>/init_engine.sh (or .csh, depending on your shell of choice). If you prefer, that can be done in your shell's RC file. Otherwise, you must source that file for each shell.
-
Familiarity with your operating system and a basic foundation in both C++ and Motif programming. Although this scenario uses the Motif toolkit, it is not intended to teach the basics of Motif programming. For Motif-specific details, see the Motif Programming Manual resource listed at the end of this example.
-
While no experience with other ESRI software is required, previous experience with ArcObjects and a basic understanding of ArcGIS applications, such as ArcMap and ArcCatalog, are advantageous.
-
Access to the sample data, code, and makefiles that come with this scenario. Code and makefiles are located at <install_location>/developerkit10.0/Samples/ArcObjectsCPP/Map_Viewer. The sample data is located at <install_location>/developerkit10.0/Samples/data.
If you do not have the developer samples installed, rerun the ArcObjects Software Development Kit (SDK) for Cross Platform C++ install wizard, select Modify, and click the samples feature.
The GTK implementation can be found in the same Map_Viewer directory.
The controls and ArcGIS libraries used in this scenario are as follows:
- MapControl
- PageLayoutControl
- TOCControl
- ToolbarControl
- Carto Object Library
- System Object Library
- Display Object Library
- SystemUI Object Library
- Geometry Object Library
In Motif C++, you must include the following files to use those controls and object libraries:
- ArcSDK.h
- Ao/AoMotifControls.h
In GTK C++ you would include ArcSDK.h and Ao/AoGTKControls.h. You would use libarcsdk.so, libgtkctl.so, and libaoctl.so. GTK developers will also need to link against some GTK libraries and include some GTK-specific directories. It is recommended that pkg-config be used for this. These steps are shown in the provided Makefile.SolarisGTK.Template and Makefile.LinuxGTK.Template.
They also use the ArcGIS Engine C++ API libraries:
-
libarcsdk.so
-
libmotifctl.so
- libaoctl.so
In addition, for Motif you must link against:
- libpthread.so (Linux only)
- libXm.so
- ibX11.so
To implement a custom tool, you must include the following file:
- Ao/AoToolBase.h
Implementation
The implementation below provides you with all the code you will need to successfully complete the scenario. It does not provide step-by-step instructions for developing C++ applications with Motif, as it assumes that you have a working knowledge of the development environment already.
During this scenario, the steps assume that you are programming on Solaris. However, to follow this same scenario on Linux all you will need to do is to use the Linux makefile, Makefile.LinuxMotif, instead of the Solaris makefile, Makefile.SolarisMotif.
Creating the makefile
To easily compile the application you make use of a makefile. This scenario is not designed to teach you the basics of project management with the make utility, so if you are unfamiliar with them, please see the Oram and Talbott reference at the end of this scenario.
Makefiles greatly simplify the build process for large applications. ESRI's Makefile samples don't always use all of the functionality available in the make utility (for example, SUFFIXES and pattern rules) because they are designed to work across many different versions of make. However, the makefiles do use various predefined variables that are available for most versions of make. These predefined variables include both commands (for example, CXX and RM) and command arguments (for example, CXXFLAGS and LDFLAGS). Consult the documentation for the make utility for more information about its advanced features.
To create the makefile for this scenario, copy either the Makefile.SolarisMotif.Template (for Solaris programming) or Makefile.LinuxMotif.Template (for Linux programming). Those files are located with the code in the Motif_Cpp folder. Remove the ".Template" from the end of the filename, and replace all instances of "motif_sample" with "MapViewer." The Makefile.SolarisMotif.Final and Makefile.LinuxMotif.Final, in the same directory, show what your makefile should look like at the end of this scenario.
Creating the Motif Application form by placing the ArcGIS Engine controls on a Motif Application form
To use the controls, you must create them as Motif widgets and place them into a Motif application form.
- Open MapViewer.h, a new file, in your text editor. Place the following lines to ensure that the class is only declared once. The remainder of code for this file will fall between #define and #endif.
#ifndef __ENGINE_CONTROL_MOTIF_EXAMPLE__
#define __ENGINE_CONTROL_MOTIF_EXAMPLE__
#endif// __ENGINE_CONTROL_MOTIF_EXAMPLE__
- Include the necessary files for the Motif toolkit. Make sure that String, Cursor, Object, and ObjectClass have all been defined as ESRI types, as shown below:
Some of the code shown has already been entered in previous steps. It is given here to illustrate the accurate placement of the code you are adding in this step.
#define __ENGINE_CONTROL_MOTIF_EXAMPLE__
// Motif Headers#define String esriXString
#define Cursor esriXCursor
#define Object esriXObject
#define ObjectClass esriXObjectClass
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/Form.h>
#include <Xm/Protocols.h>
#undef String
#undef Cursor
#undef Object
#undef ObjectClass
#endif// __ENGINE_CONTROL_MOTIF_EXAMPLE__
For GTK you will need to replace the Motif headers with a single include of gtk/gtk.h.
- Following the Motif headers include the ArcGIS Engine C++ API header file:
// ArcObjects Headers
// Engine
#include <ArcSDK.h>
- Open MapViewer.cpp, a new file, in your text editor to implement your application. Include the MapViewer.h file just created.
#include
"MapViewer.h"
- Begin writing the main function. First initialize the ArcGIS Engine with AoInitialize, then set up the licensing for the product using IAoInitialize > Initialize(). If a user attempts to run the application without an appropriate ArcGIS Engine runtime or license, the application will exit. Notice that the scope is set to prevent ipInit from existing when ::AoUninitialize() is called later.
int main(int argc, char *argv[])
{
// Initialize the engine
::AoInitialize(NULL);
{
IAoInitializePtr ipInit(CLSID_AoInitialize);
esriLicenseStatus status;
ipInit->Initialize(esriLicenseProductCodeEngine, &status);
if (status != esriLicenseCheckedOut)
AoExit(0);
}
}
Although the names are similar, AoInitialize() and the IAoInitialize interface have different purposes. While AoInitialize() is a C++ API call that initializes libraries, the IAoInitialize interface is used to handle licensing for the application.
- To increase code readability, use a helper function FormSetup. You will need to pass in an XtAppContext, which will be set in that function, as well as the parameters passed into main. First, place a definition of this file in MapViewer.h before the #endif line. You will also need to inform the compiler where to find the Resize function, which you will be using in FormSetup().
void FormSetup(int argc, char *argv[], XtAppContext *app_context);
extern"C"void XtResizeWidget(Widget, _XtDimension, _XtDimension, _XtDimension)
;
For the equivalent GTK setup, see form_setup in the provided MapViewer.cpp file in the GTK version of the sample. You will notice that the GTK function names differ slightly from the Motif ones. This is to maintain the programming styles associated with each.
- After initialization in MapViewer.cpp, call FormSetup, which you will write in the following steps.
XtAppContext app_context;
FormSetup(argc, argv, &app_context);
- In MapViewer.cpp, after the main function, begin writing FormSetup by setting the language procedure for your application.
void FormSetup(int argc, char *argv[], XtAppContext *app_context)
{
XtSetLanguageProc(NULL, NULL, NULL);
}
- Continuing in FormSetup after the XtSetLanguageProc call, create the Motif form for the MapViewer.
- First initialize the Motif toolkit and create your top-level application Motif widget.
// Initialize the Motif toolkit and create the parent widget
Widget topLevel = XtVaAppInitialize(app_context, "XApplication", NULL, 0, &argc,
argv, NULL, NULL);
XtVaSetValues(topLevel, XmNtitle, "MapViewer", NULL);
- Set the application's initial size to 800 x 640, using the resize function that you placed in MapViewer.h earlier.
XtVaSetValues(topLevel, XmNtitle, "MapViewer", NULL);
// Set the application size by resizing the created widget
XtResizeWidget(topLevel, 800, 640, 1);
- Create the main application window after the resize. Create the main form and attach it to the main window so that it fills the window. The main form widget will be the parent widget for each of the ESRI Engine Control widgets.
Widget mainWindow = XtVaCreateWidget("mainwindow", xmMainWindowWidgetClass,
topLevel, NULL);
Widget mainForm = XtVaCreateWidget("mainform", xmFormWidgetClass, mainWindow,
XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, mainWindow,
XmNbottomAttachment, XmATTACH_WIDGET, XmNbottomWidget, mainWindow,
XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, mainWindow,
XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, mainWindow,
XmNfractionBase, 100, NULL);
- Manage the child widgets.
// Manage the non-parent widgets
XtManageChild(mainWindow);
XtManageChild(mainForm);
- Listen for window manager events to exit the application on command. To set this up, you will use a callback on the entire application that will respond to window manager protocols. When the window manager's close message is received, the CloseAppCallback function is executed. Here just listen—you will implement the callback function in a later step.
// Handle the "close" window manager message
Atom wm_delete_window = XmInternAtom(XtDisplay(topLevel), "WM_DELETE_WINDOW",
FALSE);
XmAddWMProtocolCallback(topLevel, wm_delete_window, CloseAppCallback, NULL);
GTK deals with closing differently, as shown in form_setup of GTK's MapViewer.cpp.
- With all the application widgets created and managed and all the callbacks registered, the application can start running. Realizing the top-level widget recursively creates the actual windows for all the application widgets. The call to the function is made at the end of your FormSetup function.
// Start the application running
XtRealizeWidget(topLevel);
- In the previous step you set up a callback to listen for window manager protocols to close on command. Now implement the callback for when the application is closed. First give a forward declaration of the function. This is placed right after the declaration for XtResizeWidget at the top of MapViewer.h.
void CloseAppCallback(Widget w, XtPointer client_data, XtPointer call_data);
- Write the callback function for closing the applicaiton in MapViewer.cpp after FormSetup. Shut down and uninitialize the ArcGIS Engine, then exit.
// Function called when WM_DELETE_WINDOW protocol is passed
void CloseAppCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
// Uninitialize the engine
{
IAoInitializePtr ipInit(CLSID_AoInitialize);
ipInit->Shutdown();
}
::AoUninitialize();
AoExit(0);
}
Although it might look like a new instance of AoInitialize is created, it is a singleton object, so this returns a pointer to the same AoInitialize object created before.
- A final ArcGIS Engine C++ API call is required to turn the application over to the X Toolkit Intrinsics, which handles passing events to the widgets. After this call, the application code sits idle and waits for user-generated events. This will end your main function, so place it after the call to FormSetup.
// Start the application running
XtAppMainLoop(app_context);
- Compile the application by typing "make f Makefile.SolarisMotif" at the command line.
- Run the application by typing either "make f Makefile.SolarisMotif run" or "./MapViewer" at the command line. You will see an empty form titled MapViewer:
Placing the ArcGIS Engine Controls
Now that you have a Motif form ready, you can create the ArcGIS controls as Motif widgets and place them on the form.
- Start by including the ArcGIS controls header file in MapViewer.h after the ArcGIS Engine header file.
// Controls
#include <Ao/AoMotifControls.h>
- In MapViewer.cpp, continue implementing your application. Set up global variables for the control interfaces, placing them before the main function.
// Control Interfaces
IToolbarControlPtr g_ipToolbarControl;
IMapControl3Ptr g_ipMapControl;
ITOCControlPtr g_ipTOCControl;
IPageLayoutControlPtr g_ipPageLayoutControl;
- You are now ready to create the ESRI control widgets for the PageLayoutControl, MapControl, TOCControl, and ToolbarControl. You will do this in the FormSetup function after the mainForm is created and before the widgets are managed. The widget class for all of the ESRI controls is mwCnlWidgetClass, and each widget must be given the MwNProgID that corresponds to its control type. Place the MapControl and the TOCControl in their own frames. Set the widget attachments to position the controls into the application window so that the ToolbarControl is along the top of the application, the TOCControl is along the left with the MapControl below it, and the PageLayoutControl is to the right of the TOCControl. Also set the height of the toolbar, the width of the TOC, and the dimensions of the map, which you want constant even if the application is resized.
GTK ArcGIS control widgets are created with gtk_axctl_new.
// ToolbarControl setup
Widget toolbarWidget = XtVaCreateWidget("toolbarwidget", mwCtlWidgetClass,
mainForm, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM, MwNprogID, AoPROGID_ToolbarControl, NULL);
XtVaSetValues(toolbarWidget, XmNheight, 25, NULL);
// Create a sub-form to place TOCControl and MapControl on
Widget leftFormPanel = XtVaCreateWidget("leftformpanel", xmFormWidgetClass,
mainForm, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, toolbarWidget,
XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM,
XmNwidth, 200, NULL);
// MapControl setup
Widget mapWidget = XtVaCreateWidget("mapwidget", mwCtlWidgetClass,
leftFormPanel, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment,
XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, MwNprogID,
AoPROGID_MapControl, NULL);
XtVaSetValues(mapWidget, XmNheight, 200, XmNwidth, 200, NULL);
// TOCControl setup
Widget tocWidget = XtVaCreateWidget("tocwidget", mwCtlWidgetClass,
leftFormPanel, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment,
XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNbottomAttachment,
XmATTACH_WIDGET, XmNbottomWidget, mapWidget, MwNprogID, AoPROGID_TOCControl,
NULL);
XtVaSetValues(tocWidget, XmNwidth, 200, NULL);
// PageLayoutControl setup
Widget pageWidget = XtVaCreateWidget("pagewidget", mwCtlWidgetClass, mainForm,
XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, toolbarWidget,
XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, leftFormPanel,
XmNbottomAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM,
MwNprogID, AoPROGID_PageLayoutControl, NULL);
- Once the widgets are created, you can get an interface pointer to each control through Ao<ControlName>ControlGetInterface. Throughout this scenario smart pointers are used. The line of code to get each interface is done after the XtVaSetValues call for that control. In the code below some lines that are already in place in the file are included to help place the new lines of code; however, the long parameter lists have been left out to keep the code easier to follow. You should not change the parameter list in the file.
The GTK controls get interface pointers with gtk_axctl_get_interface.
// ToolbarControl setup
Widget toolbarWidget = XtVaCreateWidget( /* parameters list is in place here */)
;
XtVaSetValues(toolbarWidget, XmNheight, 25, NULL);
MwCtlGetInterface(toolbarWidget, (IUnknown **) &g_ipToolbarControl);
/* other code is in place here already */// MapControl setup
g_mapWidget = XtVaCreateWidget( /* parameters list is in place here */);
XtVaSetValues(mapWidget, XmNheight, 200, XmNwidth, 200, NULL);
MwCtlGetInterface(mapWidget, (IUnknown **) &g_ipMapControl);
// TOCControl setup
g_tocWidget = XtVaCreateWidget( /* parameters list is in place here */);
XtVaSetValues(tocWidget, XmNwidth, 200, NULL);
MwCtlGetInterface(tocWidget, (IUnknown **) &g_ipTOCControl);
// PageLayoutControl setup
g_pageWidget = XtVaCreateWidget( /* parameters list is in place here */);
MwCtlGetInterface(pageWidget, (IUnknown **) &g_ipPageLayoutControl);
- Call XtManageChild on each control widget. The parent widget will take care of the size and placement. This call should be done after XtManageChild is called on the main form, as show in the code below:
XtManageChild(mainForm);
XtManageChild(leftFormPanel);
XtManageChild(toolbarWidget);
XtManageChild(mapWidget);
XtManageChild(tocWidget);
XtManageChild(pageWidget);
- Since you are using the ArcGIS controls, you must use a new method for the main application loop. In main, change XtAppMainLoop to MwCtlAppMainLoop:
// Start the application running
//XtAppMainLoop(app_context);
MwCtlAppMainLoop(app_context);
In GTK there is no ArcGIS-specific method for the main application loop, and gtk_main continues to be used. However, gtk_axctl_initialize_message_queue must be used before gtk_main to enable MainWin message delivery.
- Before you shut down the application, you need to clean up the global ArcObjects. Do this by setting the pointer for each control to 0 at the start of the CloseAppCallback function.
void CloseAppCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
g_ipToolbarControl = 0;
g_ipMapControl = 0;
g_ipTOCControl = 0;
g_ipPageLayoutControl = 0;
// Uninitialize the engine
- Compile the application by typing "make f Makefile.SolarisMotif" at the command line.
- Run the application by typing either "make run f Makefile.SolarisMotif" or "./MapViewer" at the command line. Notice how the controls have been placed in the application window. At this point the controls are all empty because no commands or data have been added. Try resizing the main form, and see that the TOCControl maintains its width, the ToolbarControl its height, and the MapControl its dimensions, but the other dimensions and controls resize themselves.
Loading map documents into the PageLayoutControl and MapControl
Individual data layers or preauthored ESRI map documents can be loaded into the MapControl and PageLayoutControl. You can either load the sample map document provided, or you can load your own map document. Later you will add an ArcGIS command to the ToolbarControl, which will allow you to browse to a map document.
- Programmatically add data to the PageLayoutControl. To do so you will write a new function, LoadData.
- Place a forward declaration in MapViewer.h after the forward declaration for the CloseAppCallback function .
void LoadData();
- Define LoadData in MapViewer.cpp following the definition for the CloseAppCallback function.
void LoadData()
{
CComBSTR MX_DATAFILE;
MX_DATAFILE = L "../data/GulfOfStLawrence/Gulf_of_St._Lawrence.mxd";
VARIANT_BOOL bValidDoc;
g_ipPageLayoutControl->CheckMxFile(MX_DATAFILE, &bValidDoc);
if (bValidDoc)
g_ipPageLayoutControl->LoadMxFile(MX_DATAFILE);
}
Data can also be loaded to the toolbar's buddy at runtime by using esriControlCommands.ControlsOpenDocCommand, a command that will be placed on the toolbar later in this scenario.
- Call LoadData from the main function after the call to FormSetup.
FormSetup(argc, argv, &app_context);
LoadData();
- You want the same map to appear in the MapControl. When the document in the PageLayoutControl changes, the contents of the MapControl must be updated. To do that, you must listen for events in the PageLayoutControl by writing a class that inherits from IPageLayoutControlEventsHelper. Open a new text file called PageLayoutControlEvents.h. Place the following code into that file, making sure to include the macro IUNKNOWN_METHOD_DEFS to implement IUnknown:
By using Ao/AoControls.h, this class works with both GTK and Motif applications.
#ifndef __PAGELAYOUTCONTROLEVENTS_H_
#define __PAGELAYOUTCONTROLEVENTS_H_
// ArcObjects Headers // Engine#include <ArcSDK.h>
#include <Ao/AoControls.h>
class PageLayoutControlEvents: public IPageLayoutControlEventsHelper
{
public:
// IUnknown
IUNKNOWN_METHOD_DEFS
// IPageLayoutControlEventsvoid OnAfterDraw(VARIANT display, long viewDrawPhase);
void OnAfterScreenDraw(long hdc);
void OnBeforeScreenDraw(long hdc);
void OnDoubleClick(long button, long shift, long x, long y, double mapX,
double mapY);
void OnExtentUpdated(VARIANT displayTransformation, VARIANT_BOOL
sizeChanged, VARIANT newEnvelope);
void OnFullExtentUpdated(VARIANT displayTransformation, VARIANT
newEnvelope);
void OnKeyDown(long keyCode, long shift);
void OnKeyUp(long keyCode, long shift);
void OnFocusMapChanged();
void OnPageLayoutReplaced(VARIANT newPageLayout);
void OnPageSizeChanged();
void OnMouseDown(long button, long shift, long x, long y, double mapX,
double mapY);
void OnMouseMove(long button, long shift, long x, long y, double mapX,
double mapY);
void OnMouseUp(long button, long shift, long x, long y, double mapX,
double mapY);
void OnOleDrop(esriControlsDropAction dropAction, VARIANT
dataObjectHelper, long *effect, long button, long shift, long x, long y)
;
void OnSelectionChanged();
void OnViewRefreshed(VARIANT ActiveView, long viewDrawPhase, VARIANT
layerOrElement, VARIANT envelope);
};
#endif// __PAGELAYOUTCONTROLEVENTS_H_
- However, you need to have access to the global PageLayoutControl and MapControl from MapViewer.cpp. You will define them as extern after the #include lines in the PageLayoutControlEvents.h file. This tells the compiler that they are defined in another file. This will be accomplished by placing the following code in PageLayoutControlEvents.h:
extern IPageLayoutControlPtr g_ipPageLayoutControl;
extern IMapControl3Ptr g_ipMapControl;
- Place the following implementation for IPageLayoutControlEventsHelper's functions into PageLayoutControlEvents.cpp, another new file. Since they are void functions, they can be left empty. Implementation for some of them will be done later in this scenario.
Although all of the functions are left empty, they must all be defined here to prevent the class from being an abstract class.
#include
"PageLayoutControlEvents.h"
void PageLayoutControlEvents::OnAfterDraw(VARIANT display, long viewDrawPhase){}
void PageLayoutControlEvents::OnAfterScreenDraw(long hdc){}
void PageLayoutControlEvents::OnBeforeScreenDraw(long hdc){}
void PageLayoutControlEvents::OnDoubleClick(long button, long shift, long x,
long y, double mapX, double mapY){}
void PageLayoutControlEvents::OnExtentUpdated(VARIANT displayTransformation,
VARIANT_BOOL sizeChanged, VARIANT newEnvelope){}
void PageLayoutControlEvents::OnFullExtentUpdated(VARIANT displayTransformation,
VARIANT newEnvelope){}
void PageLayoutControlEvents::OnKeyDown(long keyCode, long shift){}
void PageLayoutControlEvents::OnKeyUp(long keyCode, long shift){}
void PageLayoutControlEvents::OnFocusMapChanged(){}
void PageLayoutControlEvents::OnPageLayoutReplaced(VARIANT newPageLayout){}
void PageLayoutControlEvents::OnPageSizeChanged(){}
void PageLayoutControlEvents::OnMouseDown(long button, long shift, long x, long
y, double mapX, double mapY){}
void PageLayoutControlEvents::OnMouseMove(long button, long shift, long x, long
y, double mapX, double mapY){}
void PageLayoutControlEvents::OnMouseUp(long button, long shift, long x, long y,
double mapX, double mapY){}
void PageLayoutControlEvents::OnOleDrop(esriControlsDropAction dropAction,
VARIANT dataObjectHelper, long *effect, long button, long shift, long x, long
y){}
void PageLayoutControlEvents::OnSelectionChanged(){}
void PageLayoutControlEvents::OnViewRefreshed(VARIANT ActiveView, long
viewDrawPhase, VARIANT layerOrElement, VARIANT envelope){}
- Now you can do the actual update of the MapControl's ActiveView. Enter the following code into the OnPageLayoutReplaced event of the PageLayoutControl, which is called whenever a document is loaded into the PageLayoutControl. It will be found in PageLayoutControlEvents.cpp, which you created in the last step.
// Load the same pre-authored map document into the MapControl
CComBSTR DocFileName;
IPageLayoutControl2Ptr ipPage2 = g_ipPageLayoutControl;
ipPage2->get_DocumentFilename(&DocFileName);
g_ipMapControl->LoadMxFile(DocFileName);
- Since events have been added, you need to tell the main application to listen for them. Do this by creating an instance of the new class in the main function of MapViewer.cpp and by using the IEventListenerHelper interface.
- First include PageLayoutControlEvents.h at the top of MapViewer.h after the include for Ao/AoMotifControls.h.
// Events
#include
"PageLayoutControlEvents.h"
- In MapViewer.cpp, declare the global variables to use in listening for events. These declarations should follow those for the control pointers, as shown below.
IPageLayoutControlPtr g_ipPageLayoutControl;
// Events
PageLayoutControlEvents *g_pageLayoutEvents;
IEventListenerHelperPtr g_ipPageLayoutControlEventHelper;
- Place the code to begin listening after the FormSetup in main, and before the data is loaded.
- Before closing the application, you must clean up the events by calling UnadviseEvents and Shutdown, as well as deleting the instance of PageLayoutControlEvents. This is done in CloseAppCallback before the control interface pointers are set to 0.
In GTK, the event cleanup will be done in delete_event, a callback used with destroy_event to close the application.
// End event listening
g_ipPageLayoutControlEventHelper->UnadviseEvents();
g_ipPageLayoutControlEventHelper->Shutdown();
g_ipPageLayoutControlEventHelper = 0;
delete g_pageLayoutEvents;
g_ipToolbarControl = 0;
- Update the makefile. Include PageLayoutControlEvents.cpp as a source, add the header for the events to the MapViewer.o dependencies list, and add a dependencies list for PageLayoutControlEvents.o.
- Compile and run the application. The map document is now loaded into the PageLayoutControl; and the TOCControl lists the data layers in the MapDocument. By default, the focus map of the map document is loaded into the MapControl.
Setting the Buddy Controls for the TOCControl and ToolbarControl
For the purpose of this application, the TOCControl and ToolbarControl will work in conjunction with the PageLayoutControl rather than the MapControl. To do this the PageLayoutControl must be set as the buddy control. The TOCControl uses the buddy's ActiveView to populate itself with maps, layers, and symbols, while any command, tool, or menu items present on the ToolbarControl will interact with the buddy control's display.
- The buddy control is set after the widgets are created and their interface pointers have been assigned, so you will set each one in main after FormSetup() and before the event listening.
// Buddy the toolbar and TOC with the PageLayoutControl
g_ipToolbarControl->SetBuddyControl(g_ipPageLayoutControl);
g_ipTOCControl->SetBuddyControl(g_ipPageLayoutControl);
- Remake the application and run it again. Notice that now the TOCControl displays a layer icon as well as the data for the current document. Use the TOCControl to toggle layer visibility by checking and unchecking the boxes. At this point the ToolbarControl is empty because no commands have been added to it.
Adding commands to the ToolbarControl
ArcGIS Engine comes with more than 120 commands and tools that work with the MapControl, PageLayoutControl, and ToolbarControl directly. These commands and tools provide you with a lot of frequently used GIS functionality for map navigation, graphics management, and feature selection. You will now add some of these commands and tools to your application.
ArcGIS Engine also provides commands for use with the SceneControl and GlobeControl.
- Create a new function to add commands and tools to the ToolbarControl. First place a forward declaration of the function below that for LoadData() in MapViewer.h.
void AddToolbarItems();
- Implement the AddToolbarItems function in MapViewer.cpp after the implementation of the LoadData() function.
void AddToolbarItems()
{
long itemIndex;
CComVariant varTool;
varTool = L "esriControlCommands.ControlsOpenDocCommand";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
// Add PageLayout Navigation Commands
varTool = L "esriControlCommands.ControlsPageZoomInTool";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_TRUE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsPageZoomOutTool";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsPagePanTool";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsPageZoomWholePageCommand";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsPageZoomPageToLastExtentBackCommand";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L
"esriControlCommands.ControlsPageZoomPageToLastExtentForwardCommand";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
// Add Map Navigation Commands
varTool = L "esriControlCommands.ControlsMapZoomInTool";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_TRUE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsMapZoomOutTool";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsMapPanTool";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
varTool = L "esriControlCommands.ControlsMapFullExtentCommand";
g_ipToolbarControl->AddItem(varTool, 0, - 1, VARIANT_FALSE, 0,
esriCommandStyleIconOnly, &itemIndex);
}
- Call the new AddToolbarItems function from main following the lines that buddy the controls, as shown below:
g_ipTOCControl->SetBuddyControl(g_ipPageLayoutControl);
AddToolbarItems();
- Remake and run the application. The ToolbarControl now contains ArcGIS Engine commands and tools that you can use to navigate the map document loaded into the PageLayoutControl. Use the page layout commands to navigate around the page layout and the map commands to navigate around the data present in the data frames. Use the open document command (all the way to the left) to browse to and load other map documents. Notice that not all of the tools are initially enabled. If there is no previous or next extent, you cannot zoom to it, and if there is no data, there are no features to select.
Information on the ArcGIS Engine control commands, including GUIDS, descriptions, and which controls each command can interact with, can be found in the ArcObjects SDK for Cross Platform C++ Help system under Technical Documents, Names and Ids, Control Commands.
Creating a popup menu for the MapControl
As well as adding control commands to the ToolbarControl to work with the buddy control, as in the previous step, you can also create popup menus from the control commands. You will add a popup menu that works with the PageLayoutControl to your application. The popup menu will display whenever the right mouse button is clicked in the display area of the PageLayoutControl.
- To implement the popup menu, you will use the IToolbarMenu interface. Define the popup menu after the definition of g_ipPageLayoutControlEventHelper in MapViewer.cpp.
IToolbarMenuPtr g_ipPopupMenu;
- Create an instance of the popup menu, attaching it to the PageLayoutControl. This is done in main following the setting of buddy controls and before AddToolbarItems is called.
// Associate the popup menu with the PageLayoutControl
g_ipPopupMenu.CreateInstance(CLSID_ToolbarMenu);
g_ipPopupMenu->SetHook(g_ipPageLayoutControl);
Instead of using the IToolbarMenu interface, this could also be done using a Motif popup menu.
Remember to clean up g_ipPopupMenu when the application closes. This is done by setting it to 0 in the CloseAppCallback function after the g_pageLayoutEvents is deleted.
[Motif C++]
delete g_pageLayoutEvents;
g_ipPopupMenu = 0;
- Place some commands on the popup menu. This is done by creating an AddPopupItems function.
- Again, start with a forward declaration in the MapViewer.h header file. It will follow the declaration for the AddTooolbarItems function.
void AddToolbarItems();
void AddPopupItems();
- Implement the function at the bottom of MapViewer.cpp after the implementation of AddToolbarItems.
void AddPopupItems()
{
CComVariant varTool;
long popupItemIndex;
varTool = L "esriControlCommands.ControlsPageZoomInFixedCommand";
g_ipPopupMenu->AddItem(varTool, 0, - 1, VARIANT_FALSE,
esriCommandStyleIconAndText, &popupItemIndex);
varTool = L "esriControlCommands.ControlsPageZoomOutFixedCommand";
g_ipPopupMenu->AddItem(varTool, 0, - 1, VARIANT_FALSE,
esriCommandStyleIconAndText, &popupItemIndex);
varTool = L "esriControlCommands.ControlsPageZoomWholePageCommand";
g_ipPopupMenu->AddItem(varTool, 0, - 1, VARIANT_FALSE,
esriCommandStyleIconAndText, &popupItemIndex);
varTool = L "esriControlCommands.ControlsPageZoomPageToLastExtentBackCommand";
g_ipPopupMenu->AddItem(varTool, 0, - 1, VARIANT_TRUE,
esriCommandStyleIconAndText, &popupItemIndex);
varTool = L
"esriControlCommands.ControlsPageZoomPageToLastExtentForwardCommand";
g_ipPopupMenu->AddItem(varTool, 0, - 1, VARIANT_FALSE,
esriCommandStyleIconAndText, &popupItemIndex);
}
Note that only tools and commands that are registered on the system as COM components can be added to the popup menu using the AddItem method. Custom C++ commands and tools, like the one you will build later, cannot be added to the popup menu, as they are not registered as COM components in the system registry.
- Add commands to the popup menu by calling the new AddPopupItems function. This will be called from main right before AddToolbarItems is called.
AddPopupItems();
AddToolbarItems();
- To display the popup menu by right-clicking, use the PageLayoutControlEvents class created earlier.
- First provide the PageLayoutControlEvents class with access to the global ToolbarMenu defined in MapViewer.cpp. Update PageLayoutControlEvents.h to have the following line after the extern line for g_ipMapControl:
extern IToolbarMenuPtr g_ipPopupMenu;
- Implement the PageLayoutControlEvents class' OnMouseDown event so that the popup menu displays upon right-clicking in the PageLayoutControl's area. This is accomplished by placing the following code inside the PageLayoutControlEvents::OnMouseDown function in PageLayoutControlEvents.cpp.
// Popup the ToolbarMenu
if (button == 2)
{
long lHWndParent;
g_ipPageLayoutControl->get_hWnd(&lHWndParent);
g_ipPopupMenu->PopupMenu(x, y, lHWndParent);
}
If using a Motif popup menu instead of the IToolbarMenu interface, this would be done with a Motif event handler. Mixing Motif widget implementations with ArcObjects event handling, or ArcGIS objects with Motif error handling will result in undetermined behavior and is not recommended.
- Remake and run the application. Right-click the PageLayoutControl's display area to display the popup menu, and navigate around the page layout.
Controlling label editing in the TOCControl
By default, the TOCControl allows users to automatically toggle the visibility of layers and to change map and layer names as they appear in the table of contents. You will add code to prevent users from editing a name and replacing it with an empty string.
- The TOCControl label editing events must be triggered. To do so, add the following code to MapViewer.cpp in main after the call to FormSetup:
g_ipTOCControl->pub_LabelEdit(esriTOCControlManual);
- You must listen for events in the TOCControl by writing a class that inherits from ITOCControlEventsHelper, as you did for the PageLayoutControl earlier. Start by creating TOCControlEvents.h, a new text file, with the following contents:
#ifndef __TOCCONTROLEVENTS_H_
#define __TOCCONTROLEVENTS_H_
#include <ArcSDK.h>
#include <Ao/AoControls.tlh>
class TOCControlEvents: public ITOCControlEventsHelper
{
public:
// IUnknown
IUNKNOWN_METHOD_DEFS
// ITOCControlEventsvoid OnMouseDown(long button, long shift, long x, long y);
void OnMouseUp(long button, long shift, long x, long y);
void OnMouseMove(long button, long shift, long x, long y);
void OnDoubleClick(long button, long shift, long x, long y);
void OnKeyDown(long keyCode, long shift);
void OnKeyUp(long keyCode, long shift);
void OnBeginLabelEdit(long x, long y, VARIANT_BOOL *CanEdit);
void OnEndLabelEdit(long x, long y, BSTR newLabel, VARIANT_BOOL *CanEdit);
};
#endif// __TOCCONTROLEVENTS_H_
- Place the implementation for ITOCControlEventsHelper's functions into TOCControlEvents.cpp, another new file. Since they are void functions, they can be left empty. However, you will implement OnEndLabelEdit. In that function, tell the TOC to forbid the edit if the new label is an empty string.
#include
"TOCControlEvents.h"
void TOCControlEvents::OnMouseDown(long button, long shift, long x, long y){}
void TOCControlEvents::OnMouseUp(long button, long shift, long x, long y){}
void TOCControlEvents::OnMouseMove(long button, long shift, long x, long y){}
void TOCControlEvents::OnDoubleClick(long button, long shift, long x, long y){}
void TOCControlEvents::OnKeyDown(long keyCode, long shift){}
void TOCControlEvents::OnKeyUp(long keyCode, long shift){}
void TOCControlEvents::OnBeginLabelEdit(long x, long y, VARIANT_BOOL *CanEdit){}
void TOCControlEvents::OnEndLabelEdit(long x, long y, BSTR newLabel,
VARIANT_BOOL *CanEdit)
{
if (CComBSTR("") == newLabel)
*CanEdit = VARIANT_FALSE;
}
- Now that the events have been implemented, the main application can listen for them. This is done the same way it was done for the PageLayoutControl's events.
- First include TOCControlEvents.h in MapViewer.h after the include for PageLayoutControlEvents.h.
#include
"TOCControlEvents.h"
- Declare some global variables for the TOCControl events at the top of MapViewer.cpp after the declarations for the PageLayout events.
TOCControlEvents *g_tocEvents;
IEventListenerHelperPtr g_ipTOCControlEventHelper;
- Next place the code to start listening for the TOCControlEvents after that for the PageLayoutControlEvents in MapViewer.cpp's main function.
g_ipPageLayoutControlEventHelper->AdviseEvents(g_ipPageLayout, NULL);
g_tocEvents = new TOCControlEvents();
g_ipTOCControlEventHelper.CreateInstance(CLSID_TOCControlEventsListener);
g_ipTOCControlEventHelper->Startup(static_cast < ITOCControlEventsHelper * >
(g_tocEvents));
g_ipTOCControlEventHelper->AdviseEvents(g_ipTOCControl, NULL);
- Don't forget to clean up the TOC's events in CloseAppCallback. This is done the same as it was for the PageLayout's events.
// End event listening
g_ipPageLayoutControlEventHelper->UnadviseEvents();
g_ipPageLayoutControlEventHelper->Shutdown();
g_ipPageLayoutControlEventHelper = 0;
delete g_pageLayoutEvents;
g_ipTOCControlEventHelper->UnadviseEvents();
g_ipTOCControlEventHelper->Shutdown();
g_ipTOCControlEventHelper = 0;
delete g_tocEvents;
- Update the makefile. Include TOCControlEvents.cpp as a source, add the header of the events to the MapViewer.o dependencies list, and add a dependencies list for TOCControlEvents.o.
- Compile and run the application. To edit a map, layer, heading, or legend class label in the TOCControl, click it once, then click it a second time to invoke label editing. Try replacing the label with an empty string. You can use the Esc key on the keyboard at any time during the edit to cancel it.
Drawing an overview rectangle on the MapControl
You will now use the MapControl as an overview window and draw the current extent of the focus map within the PageLayoutControl on its display. As you navigate around the data within the data frame of the PageLayoutControl, you will see the MapControl overview window update.
Navigating around the focus map using the map navigation tools will change the extent of the focus map in the PageLayoutControl and cause the MapControl to update. Navigating around the page layout with the page layout navigation tools will change the extent of the page layout (not the extent of the focus map in the PageLayoutControl), so the MapControl will not update.
- First add IFillSymbol and IEnvelop pointers to the top of MapViewer.cpp after the definition for the popup menu.
IToolbarMenuPtr g_ipPopupMenu;
IFillSymbolPtr g_ipFillSymbol;
IEnvelopePtr g_ipCurrentExtent;
- You will use a new function to create the rectangle used on the MapControl to highlight the current PageLayoutControl extent.
- First place the forward declaration in MapViewer.h after that for AddPopupItems.
HRESULT CreateOverviewSymbol();
- Place the implementation of that function at the end of MapViewer.cpp following AddPopupItems.
HRESULT CreateOverviewSymbol()
{
// IRgbColor interface
IRgbColorPtr ipColor(CLSID_RgbColor);
ipColor->put_Red(255);
ipColor->put_Green(0);
ipColor->put_Blue(0);
// ILine symbol interface
ILineSymbolPtr ipOutline(CLSID_SimpleLineSymbol);
ipOutline->put_Width(2);
ipOutline->put_Color(ipColor);
// IFillSymbol properties
g_ipFillSymbol.CreateInstance(CLSID_SimpleFillSymbol);
g_ipFillSymbol->put_Outline(ipOutline);
((ISimpleFillSymbolPtr)g_ipFillSymbol)->put_Style(esriSFSHollow);
return S_OK;
}
Alternatively, symbols can be retrieved from style galleries. When working with style galleries and the C++ API, ServerStyleGallery should be used as it is across platforms.
- Create the symbol in the main function of MapViewer.cpp by calling CreateOverviewSymbol before the call to put_LabelEdit for the TOCControl.
CreateOverviewSymbol();
g_ipToolbarControl->put_LabelEdit(esriTOCControlManual);
- Remember to clean up the global variables when the application closes by setting the pointers to 0 in the CloseAppCallback after the pointer for the popupMenu was set to 0.
g_ipPopupMenu = 0;
g_ipFillSymbol = 0;
g_ipCurrentExtent = 0;
- To have the MapControl work as an overview window, it must display the full extent of the data. Since this needs to be done every time there is a new map document, the following code should be placed added to the PageLayoutControlEvents.cpp file's OnPageLayoutReplaced event following the code preforming the load of the same preauthored map document into the MapControl.
// Set the extent of the MapControl to be the full extent
IEnvelopePtr ipFullExtentEnv;
g_ipMapControl->get_FullExtent(&ipFullExtentEnv);
g_ipMapControl->put_Extent(ipFullExtentEnv);
- The global variable g_ipCurrentExtent, which will be used to draw the overview on the MapControl, needs to be updated with every new PageLayout. To implement this, you will need access to the g_ipCurrentExtent global variable from within the PageLayoutControlEvents class. Add the following into PageLayoutControlEvents.h after the extern statement for the popup menu:
extern IEnvelopePtr g_ipCurrentExtent;
- To update the extent rectangle on the overview map to match the extent shown in every new PageLayout, you need to set the current extent rectangle to match the visible extent of the PageLayout's map. Do this in the OnPageLayoutReplaced event (in PageLayoutControlEvents.cpp). This code should come before that for loading the same preauthored map document into the MapControl.
// Get the extent of the PageLayout's focus map
IActiveViewPtr ipActiveView;
g_ipPageLayoutControl->get_ActiveView(&ipActiveView);
IMapPtr ipFocusMap;
ipActiveView->get_FocusMap(&ipFocusMap);
IActiveViewPtr ipMapActiveView(ipFocusMap);
ipMapActiveView->get_Extent(&g_ipCurrentExtent);
- The PageLayoutControlEvents do not indicate when the extent of the map within the data frame changes. To receive that information you will use the ITransformEvents interface of the PageLayoutControl's focus map. Implementing a class, TransformEvents, that extends ITransformEvents accomplishes this. It will need to update the extent envelope and refresh the MapControl in VisibleExtentUpdated. To do so, your new class will need access to the g_ipMapControl and g_ipCurrentExtent global variables. Create a new file, TransformEvents.h, with the following code:
#ifndef __TRANSFORMEVENTS_H_
#define __TRANSFORMEVENTS_H_
// ArcObjects Headers// Engine#include <ArcSDK.h>
// Controls#include <Ao/AoControls.h>
extern IMapControl3Ptr g_ipMapControl;
extern IEnvelopePtr g_ipCurrentExtent;
class TransformEvents: public ITransformEvents
{
public:
// IUnknown
IUNKNOWN_METHOD_DEFS
// ITransformEvents
HRESULT BoundsUpdated(IDisplayTransformation *sender);
HRESULT DeviceFrameUpdated(IDisplayTransformation *sender, VARIANT_BOOL
sizeChanged);
HRESULT ResolutionUpdated(IDisplayTransformation *sender);
HRESULT RotationUpdated(IDisplayTransformation *sender);
HRESULT UnitsUpdated(IDisplayTransformation *sender);
HRESULT VisibleBoundsUpdated(IDisplayTransformation *sender, VARIANT_BOOL
sizeChanged);
};
#endif// __TRANSFORMEVENTS_H_
- Implement that class by placing the following code in TransformEvents.cpp, another new file. In particular, pay attention to VisibleBoundsUpdated. This event is triggered whenever the extent of the map is changed and is used to set the envelope to the new visible bounds of the map. By refreshing the MapControl you force it to redraw the shape on its display.
#include
"TransformEvents.h"
HRESULT TransformEvents::BoundsUpdated(IDisplayTransformation *sender)
{
return E_NOTIMPL;
}
HRESULT TransformEvents::DeviceFrameUpdated(IDisplayTransformation *sender,
VARIANT_BOOL sizeChanged)
{
return E_NOTIMPL;
}
HRESULT TransformEvents::ResolutionUpdated(IDisplayTransformation *sender)
{
return E_NOTIMPL;
}
HRESULT TransformEvents::RotationUpdated(IDisplayTransformation *sender)
{
return E_NOTIMPL;
}
HRESULT TransformEvents::UnitsUpdated(IDisplayTransformation *sender)
{
return E_NOTIMPL;
}
HRESULT TransformEvents::VisibleBoundsUpdated(IDisplayTransformation *sender,
VARIANT_BOOL sizeChanged)
{
// Set the extent to the new visible extent
sender->get_VisibleBounds(&g_ipCurrentExtent);
// Refresh the MapControl's foreground phase
HRESULT hr = g_ipMapControl->Refresh(esriViewForeground);
return hr;
}
- Although the TransformEvents class has been implemented, those events are not yet listened for.
- First, include the new TransformEvents.h header file in MapViewer.h:
#include
"TOCControlEvents.h"
#include
"TransformEvents.h"
- Next start up these events in MapViewer.cpp's main, but you will not advise them there. Remember to place the variable declarations at the top of MapViewer.cpp.
// these lines go at the top of MapViewer.cpp
IEventListenerHelperPtr g_ipTOCControlEventHelper;
TransformEvents *g_transEvents;
IEventListenerHelperPtr g_ipTransEventHelper;
[Motif C++]
/* these lines go after the TOCControl events have been advised
in the main function of MapViewer.cpp */
g_transEvents = new TransformEvents();
g_ipTransEventHelper.CreateInstance(CLSID_TransformEventsListener);
g_ipTransEventHelper->Startup(static_cast < TransformEvents * > (g_transEvents))
;
- You need to trap for the TransformEvents from the PageLayoutControlEvents OnPageLayoutReplaced event.
- To do this PageLayoutControlEvents will need to know about g_ipTransEventHelper, so declare it with extern in PageLayoutControlEvents.h.
extern IEnvelopePtr g_ipCurrentExtent;
extern IEventListenerHelperPtr g_ipTransEventHelper;
- Now advise the events in OnPageLayoutReplaced in PageLayoutControlEvents.cpp. This code should follow the lines for getting the extent of the PageLayout's focus map and precede the lines for loading the same map document into the MapControl.
// Trap focus map's ITransformEvents
IScreenDisplayPtr ipScreenDisp;
ipMapActiveView->get_ScreenDisplay(&ipScreenDisp);
IDisplayTransformationPtr ipDisplayTrans;
ipScreenDisp->get_DisplayTransformation(&ipDisplayTrans);
CComBSTR bsGUID;
::StringFromIID(IID_ITransformEvents, &bsGUID);
IUIDPtr ipUID(CLSID_UID);
ipUID->put_Value(CComVariant(bsGUID));
g_ipTransEventHelper->AdviseEvents(ipDisplayTrans, ipUID);
- Clean up the transform events in MapViewer.cpp's CloseAppCallback after the deletion of g_tocEvents.
delete g_tocEvents;
g_ipTransEventHelper->UnadviseEvents();
g_ipTransEventHelper->Shutdown();
g_ipTransEventHelper = 0;
delete g_transEvents;
- Update the makefile to reflect the new source file. Also, add a dependencies list for TransformEvents.o and add the TransformEvents class to the PageLayoutControlEvent and MapViewer dependencies lists.
- To do the actual drawing of the symbol on the MapControl, you need to listen for the MapControl's OnAfterDraw event. You will implement an event class for the MapControl as you have done the PageLayoutControl and TOCControl. This class will need to know about the global MapControl, extent, and fill symbol. Start with a new file, MapControlEvents.h:
#ifndef __MAPCONTROLEVENTS_H_
#define __MAPCONTROLEVENTS_H_
// ArcObjects Headers// Engine#include <ArcSDK.h>
// Controls#include <Ao/AoControls.h>
extern IMapControl3Ptr g_ipMapControl;
extern IEnvelopePtr g_ipCurrentExtent;
extern IFillSymbolPtr g_ipFillSymbol;
class MapControlEvents: public IMapControlEvents2Helper
{
public:
// IUnknown
IUNKNOWN_METHOD_DEFS
// IMapControlEventsvoid OnAfterDraw(VARIANT display, long viewDrawPhase);
void OnAfterScreenDraw(long hdc);
void OnBeforeScreenDraw(long hdc);
void OnDoubleClick(long button, long shift, long x, long y, double mapX,
double mapY);
void OnExtentUpdated(VARIANT displayTransformation, VARIANT_BOOL
sizeChanged, VARIANT newEnvelope);
void OnFullExtentUpdated(VARIANT displayTransformation, VARIANT
newEnvelope);
void OnKeyDown(long keyCode, long shift);
void OnKeyUp(long keyCode, long shift);
void OnMapReplaced(VARIANT newMap);
void OnMouseDown(long button, long shift, long x, long y, double mapX,
double mapY);
void OnMouseMove(long button, long shift, long x, long y, double mapX,
double mapY);
void OnMouseUp(long button, long shift, long x, long y, double mapX,
double mapY);
void OnOleDrop(esriControlsDropAction dropAction, VARIANT
dataObjectHelper, long *effect, long button, long shift, long x, long y)
;
void OnSelectionChanged();
void OnViewRefreshed(VARIANT ActiveView, long viewDrawPhase, VARIANT
layerOrElement, VARIANT envelope);
};
#endif// __MAPCONTROLEVENTS_H_
- Implement those events in MapControlEvents.cpp. Leave all of the functions blank except OnAfterDraw, in which the rectangle will be drawn on the MapControl.
#include
"MapControlEvents.h"
void MapControlEvents::OnAfterDraw(VARIANT display, long viewDrawPhase)
{
if (g_ipCurrentExtent == 0)
return ;
// If the foreground phase has drawn, viewDrawPhase will be 32
esriViewDrawPhase drawPhase = esriViewDrawPhase(viewDrawPhase);
if (drawPhase == esriViewForeground)
{
// Draw the shape on the MapControl
CComVariant varSymbol = CComVariant(g_ipFillSymbol);
g_ipMapControl->DrawShape((IGeometryPtr)g_ipCurrentExtent, &varSymbol);
}
}
void MapControlEvents::OnAfterScreenDraw(long hdc){}
void MapControlEvents::OnBeforeScreenDraw(long hdc){}
void MapControlEvents::OnDoubleClick(long button, long shift, long x, long y,
double mapX, double mapY){}
void MapControlEvents::OnExtentUpdated(VARIANT displayTransformation,
VARIANT_BOOL sizeChanged, VARIANT newEnvelope){}
void MapControlEvents::OnFullExtentUpdated(VARIANT displayTransformation,
VARIANT newEnvelope){}
void MapControlEvents::OnKeyDown(long keyCode, long shift){}
void MapControlEvents::OnKeyUp(long keyCode, long shift){}
void MapControlEvents::OnMapReplaced(VARIANT newMap){}
void MapControlEvents::OnMouseDown(long button, long shift, long x, long y,
double mapX, double mapY){}
void MapControlEvents::OnMouseMove(long button, long shift, long x, long y,
double mapX, double mapY){}
void MapControlEvents::OnMouseUp(long button, long shift, long x, long y,
double mapX, double mapY){}
void MapControlEvents::OnOleDrop(esriControlsDropAction dropAction, VARIANT
dataObjectHelper, long *effect, long button, long shift, long x, long y){}
void MapControlEvents::OnSelectionChanged(){}
void MapControlEvents::OnViewRefreshed(VARIANT ActiveView, long viewDrawPhase,
VARIANT layerOrElement, VARIANT envelope){}
- These events need to be listened for as well.
- First, include the necessary header file in MapViewer.h after the include for the TransformEvents.h file.
#include
"MapControlEvents.h"
- Listen for the events from MapViewer.cpp in the same way you did for the other controls.
// place these lines at the top of the file after the definition for g_ipTransEventHelper
MapControlEvents *g_mapEvents;
IEventListenerHelperPtr g_ipMapControlEvent2Helper;
[Motif C++]
// place these lines in the main function after startup of g_ipTransEventHelper
g_mapEvents = new MapControlEvents();
g_ipMapControlEvent2Helper.CreateInstance(CLSID_MapControlEvents2Listener);
g_ipMapControlEvent2Helper->Startup(static_cast < IMapControlEvents2Helper * >
(g_mapEvents));
g_ipMapControlEvent2Helper->AdviseEvents(g_ipMapControl, NULL);
- Clean up the map control's events in CloseAppCallback as you did for the other events. This code should follow the deletion of the g_transEvents.
delete g_transEvents;
g_ipMapControlEvent2Helper->UnadviseEvents();
g_ipMapControlEvent2Helper->Shutdown();
g_ipMapControlEvent2Helper = 0;
delete g_mapEvents;
- Update the makefile to reflect the MapControlEvents source file. Also, add a dependencies list for MapControlEvents.o and add the MapControlEvents class to the MapViewer.o dependencies list.
- Compile and run the application. Use the map navigation tools that you added earlier to change the extent of the focus map in the PageLayoutControl. The new extent is drawn on the MapControl as a red rectangle.
Creating a custom tool
You are not limited to placing the ArcGIS Engine commands and tools on the ToolbarControl. Next, you will create a custom tool that adds to the PageLayoutControl a text element containing today's date at the location of a mouse click. However, this tool will be created as a generic tool so that it could instead work with the MapControl and ToolbarControl as well as the PageLayoutControl.
The code for this custom tool is available with the rest of this scenario's source code. If you want to use the custom command directly, rather than creating it yourself, copy the AddDate.h and AddDate.cpp files, along with the Res folder from the MapViewer folder, to the directory you are using for this scenario and proceed to step 5 below.
- In your text editor, start a new file, AddDate.h.
- In AddDate.h, create a new class, AddDateTool, which inherits from CAoToolbase. Include a public constructor and destructor.
#ifndef __ADD_DATE_H_
#define __ADD_DATE_H_
// ArcObjects Headers// Engine#include <ArcSDK.h>
// Controls#include <Ao/AoControls.h>
// Custom Tool#include <Ao/AoToolBase.h>
class AddDateTool: public CAoToolBase
{
public:
AddDateTool();
~AddDateTool();
};
#endif// #define __ADD_DATE_H_
Since the non-GUI-specific Ao/AoControls.h and Ao/AoToolBase.h are used, the custom tool, like the custom events, will work with both GTK and Motif applications.
- Since you are writing a new tool, it must implement both ICommand and ITool, both defined in CAoToolBase. In AddDate.h place the declarations for the functions supported by ICommand and ITool. This code should follow the public destructor for the AddDateTool class.
For this command to work with all the different controls, you will use the IHookHelper interface, storing the hook in a private member variable, m_ipHookHelper. You will also provide member variables for the tool's icon and bitmap.
// ICommand
HRESULT get_Enabled(VARIANT_BOOL *Enabled);
HRESULT get_Checked(VARIANT_BOOL *Checked);
HRESULT get_Name(BSTR *Name);
HRESULT get_Caption(BSTR *Caption);
HRESULT get_Tooltip(BSTR *Tooltip);
HRESULT get_Message(BSTR *Message);
HRESULT get_Bitmap(OLE_HANDLE *bitmapFile);
HRESULT get_Category(BSTR *categoryName);
HRESULT OnCreate(IDispatch *hook);
HRESULT OnClick();
// ITool
HRESULT get_Cursor(OLE_HANDLE *cursorName);
HRESULT OnMouseDown(LONG Button, LONG Shift, LONG X, LONG Y);
HRESULT OnMouseMove(LONG Button, LONG Shift, LONG X, LONG Y);
HRESULT OnMouseUp(LONG Button, LONG Shift, LONG X, LONG Y);
HRESULT OnDblClick();
HRESULT OnKeyDown(LONG keyCode, LONG Shift);
HRESULT OnKeyUp(LONG keyCode, LONG Shift);
HRESULT OnContextMenu(LONG X, LONG Y, VARIANT_BOOL *handled);
HRESULT Refresh(OLE_HANDLE ole);
HRESULT Deactivate(VARIANT_BOOL *complete);
private:
IHookHelperPtr m_ipHookHelper;
OLE_HANDLE m_hBitmap;
OLE_HANDLE m_hCursor;
- Open a new file in your text editor and name it AddDate.cpp. Here you will implement your custom tool.
- Include AddDate.h.
#include
"AddDate.h"
- In the constructor, you will load the bitmap and cursor, as well as create the IHookHelper. If you want to use the provided icon and mouse cursor, copy the resources from arcgis/developerkit10.0/Samples/ArcObjectsCPP/Map_Viewer/Motif_Cpp/Res/ to your code directory.
Place this constructor code following the include statement.
AddDateTool::AddDateTool()
{
m_ipHookHelper.CreateInstance(CLSID_HookHelper);
// Load the cursor
ISystemMouseCursorPtr ipSysMouseCur(CLSID_SystemMouseCursor);
ipSysMouseCur->LoadFromFile(CComBSTR(L "../Res/date.cur"));
OLE_HANDLE hTmp;
HRESULT hr = ipSysMouseCur->get_Cursor(&hTmp);
if (SUCCEEDED(hr))
{
m_hCursor = hTmp;
}
// Load the bitmap
IRasterPicturePtr ipRastPict(CLSID_BasicRasterPicture);
IPicturePtr ipPict;
hr = ipRastPict->LoadPicture(CComBSTR(L "../Res/date.bmp"), &ipPict);
if (SUCCEEDED(hr))
{
OLE_HANDLE hBitmap;
hr = ipPict->get_Handle(&hBitmap);
if (SUCCEEDED(hr))
m_hBitmap = hBitmap;
}
}
- Following the constructor, implement the destructor, where you will clean up all the interface member variables.
AddDateTool::~AddDateTool()
{
m_ipHookHelper = 0;
m_hBitmap = 0;
m_hCursor = 0;
}
- You now need to stub out all the functions from ICommand in AddDate.cpp, even if you are not going to use some of these. Add the following code to the ICommand properties and methods following the destructor:
HRESULT AddDateTool::get_Enabled(VARIANT_BOOL *Enabled)
{
if (!Enabled)
return E_POINTER;
*Enabled = VARIANT_TRUE;
return S_OK;
}
HRESULT AddDateTool::get_Checked(VARIANT_BOOL *Checked)
{
if (!Checked)
return E_POINTER;
return S_OK;
}
HRESULT AddDateTool::get_Name(BSTR *Name)
{
if (!Name)
return E_POINTER;
*Name = ::AoAllocBSTR(L "CustomCommands_AddDate");
return S_OK;
}
HRESULT AddDateTool::get_Caption(BSTR *Caption)
{
if (!Caption)
return E_POINTER;
*Caption = ::AoAllocBSTR(L "Add Date");
return S_OK;
}
HRESULT AddDateTool::get_Tooltip(BSTR *Tooltip)
{
if (!Tooltip)
return E_POINTER;
*Tooltip = ::AoAllocBSTR(L "Add date");
return S_OK;
}
HRESULT AddDateTool::get_Message(BSTR *Message)
{
if (!Message)
return E_POINTER;
*Message = ::AoAllocBSTR(L "Adds a date element to the page layout");
return S_OK;
}
HRESULT AddDateTool::get_Bitmap(OLE_HANDLE *bitmap)
{
if (!bitmap)
return E_POINTER;
if (m_hBitmap != 0)
{
*bitmap = m_hBitmap;
return S_OK;
}
return E_FAIL;
}
HRESULT AddDateTool::get_Category(BSTR *categoryName)
{
if (!categoryName)
return E_POINTER;
*categoryName = ::AoAllocBSTR(L "CustomCommands");
return S_OK;
}
// Create the command and set who it will work with
HRESULT AddDateTool::OnCreate(IDispatch *hook)
{
if (!hook)
return E_POINTER;
m_ipHookHelper->putref_Hook(hook);
return S_OK;
}
HRESULT AddDateTool::OnClick()
{
return S_OK;
}
The ICommand_OnCreate event is passed a handle or hook to the application that the command will work with. In this case it can be a MapControl, PageLayoutControl, or ToolbarControl. Rather than adding code to the OnCreate event to determine the type of hook that is being passed to the command, you will use the HookHelper to handle this. A command or tool needs to know how to handle the hook it gets passed, so a check is needed to determine the type of ArcGIS control that has been passed. The HookHelper is used to hold the hook and return the ActiveView regardless of the type of hook (in this case a MapControl, PageLayoutControl, or ToolbarControl).
- Write a function that will format the date for display on the PagelayoutControl.
- Before the class in AddDate.h, include the following header files:
#include <time.h>
#include <stdio.h>
- Add a private function to the AddDate class in AddDate.h to take care of the formatting.
OLE_HANDLE m_hCursor;
char *FormatDate();
- Implement the function at the bottom of AddData.cpp.
char *AddDateTool::FormatDate()
{
time_t dateInfo = time(NULL);
tm *todaysDate = localtime(&dateInfo);
int month = todaysDate->tm_mon + 1;
int day = todaysDate->tm_mday;
int year = todaysDate->tm_year + 1900;
char *dateDisplay = newchar[12];
sprintf(dateDisplay, "%d/%d/%d\n", month, day, year);
return dateDisplay;
}
- Continue implementing your custom tool by stubbing out all the properties and events of the ITool interface after the OnClick function and before the FormatDate function in AddDate.cpp. Pay attention to the implementation of the OnMouseDown method, as it creates the date text element and adds it to the graphics container of the application.
HRESULT AddDateTool::get_Cursor(OLE_HANDLE *cursorName)
{
if (cursorName == NULL)
return E_POINTER;
if (m_hCursor != 0)
{
*cursorName = m_hCursor;
return S_OK;
}
return E_FAIL;
}
// Add the date to the page layout where the mouse is
HRESULT AddDateTool::OnMouseDown(LONG Button, LONG Shift, LONG X, LONG Y)
{
if (Button == 1)
{
// Format the date & create a text elementchar *dateDisplay = FormatDate();
ITextElementPtr ipDateTextElem(CLSID_TextElement);
ipDateTextElem->put_Text(CComBSTR(dateDisplay));
delete [] dateDisplay;
ITextSymbolPtr ipDateTextSymb(CLSID_TextSymbol);
// Add it to the text element
ipDateTextElem->put_Symbol(ipDateTextSymb);
// Get point in map display coordinates
IActiveViewPtr ipActiveView;
m_ipHookHelper->get_ActiveView(&ipActiveView);
IScreenDisplayPtr ipScreenDisplay;
ipActiveView->get_ScreenDisplay(&ipScreenDisplay);
IDisplayTransformationPtr ipDisplayTrans;
ipScreenDisplay->get_DisplayTransformation(&ipDisplayTrans);
IPointPtr ipPoint;
ipDisplayTrans->ToMapPoint(X, Y, &ipPoint);
// Set the element's geometry
((IElementPtr)ipDateTextElem)->put_Geometry(ipPoint);
// Add element to the page layout's graphics container
IGraphicsContainerPtr ipGraphicsContainer;
ipActiveView->get_GraphicsContainer(&ipGraphicsContainer);
ipGraphicsContainer->AddElement((IElementPtr)ipDateTextElem, 0);
ipActiveView->PartialRefresh(esriViewGraphics, NULL, NULL);
}
return S_OK;
}
HRESULT AddDateTool::OnMouseMove(LONG Button, LONG Shift, LONG X, LONG Y)
{
return E_NOTIMPL;
}
HRESULT AddDateTool::OnMouseUp(LONG Button, LONG Shift, LONG X, LONG Y)
{
return E_NOTIMPL;
}
HRESULT AddDateTool::OnDblClick()
{
return E_NOTIMPL;
}
HRESULT AddDateTool::OnKeyDown(LONG keyCode, LONG Shift)
{
return E_NOTIMPL;
}
HRESULT AddDateTool::OnKeyUp(LONG keyCode, LONG Shift)
{
return E_NOTIMPL;
}
HRESULT AddDateTool::OnContextMenu(LONG X, LONG Y, VARIANT_BOOL *handled)
{
return E_NOTIMPL;
}
HRESULT AddDateTool::Refresh(OLE_HANDLE ole)
{
return E_NOTIMPL;
}
HRESULT AddDateTool::Deactivate(VARIANT_BOOL *complete)
{
if (!complete)
return E_POINTER;
*complete = VARIANT_TRUE;
return S_OK;
}
- Now that you have a working command, it can be incorporated into the application.
- Make the application aware of the new command by including AddDate.h in MapViewer.h after the MapControlEvents.h include.
#include
"AddDate.h"
- Create an instance of the command at the top of MapViewer.cpp following the definition of g_ipCurrentExtent.
AddDateTool *g_dateTool;
- The custom AddDate command will be added as the last item on the toolbar. This is done using the AoToolbarAddTool C++ API function. Place the call at the end of the AddToolbarItems function of MapViewer.cpp.
// Add custom date placement command to the tools toolbar
g_dateTool = new AddDateTool();
AoToolbarAddTool(g_ipToolbarControl, g_dateTool, esriCommandStyleIconOnly);
- This instance of the tool must be deleted in CloseAppCallback following the deletion of g_mapEvents.
// delete the instance of the tool
delete g_dateTool;
- Update the makefile. List AddDate.cpp as a source and create a dependencies list for AddDate.o. Make sure that the MapViewer.cpp file's dependency on AddDate.h is reflected.
- Remake and run the application. If you used the provided icon, there will be a new button with a D, underlined twice, on the toolbar. Select your new tool and click the PageLayoutControl to add a text element containing today's date.
Customizing the ToolbarControl
In addition to adding Controls commands to the ToolbarControl programmatically, you can also add them at runtime by customizing the ToolbarControl using the Customize dialog box. To do this, you will place the ToolbarControl in customize mode and display the Customize dialog box.
- You will place a toggle button next to the ToolbarControl to turn the toolbar customization mode on and off. To follow Motif standards, you will create a new panel for the top of the screen, and it will hold both the ToolbarControl and the toggle button.
- First include Xm/ToggleB.h in MapViewer.h following the Xm/Protocols.h include.
#include <Xm/ToggleB.h>
Note that only tools and commands that are registered on the system as COM components can be added to the toolbar using the Customize dialog box. Custom C++ commands and tools, such as the one built in the previous step, do not appear in the Customize dialog box, as they are not registered as COM components in the system registry.
- In MapViewer.cpp, add declarations for the toggle button widget. Also declare the ICustomizeDialog interface pointer.
IEnvelopePtr g_ipCurrentExtent;
ICustomizeDialogPtr g_ipCustomizeDialog;
AddDateTool *g_dateTool;
Widget g_customizeToggle;
- In FormSetup, create the toggle button (after mainForm is created) that will allow you to customize the toolbar as well as its panel. Update the widget attachments to reflect the new panel, replacing the location of the ToolbarControl.
The GTK toggle button's setup is shown in the GTK MapViewer.cpp's form_setup.
// Create a sub-form to place ToolbarControl and customizeToggle on
Widget topFormPanel = XtVaCreateWidget("topformpanel", xmFormWidgetClass,
mainForm, XmNtopAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM,
XmNleftAttachment, XmATTACH_FORM, XmNheight, 25, NULL);
// customizetoggle setup
XmString label = XmStringCreateLocalized("Customize");
g_customizeToggle = XtVaCreateWidget("customizetoggle",
xmToggleButtonWidgetClass, topFormPanel, XmNlabelString, label,
XmNtopAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM, XmNheight, 25, XmNwidth, 150, NULL);
XmStringFree(label);
// ToolbarControl setup
Widget toolbarWidget = XtVaCreateWidget("toolbarwidget", mwCtlWidgetClass,
topFormPanel, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment,
XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNrightAttachment,
XmATTACH_WIDGET, XmNrightWidget, g_customizeToggle, MwNprogID,
AoPROGID_ToolbarControl, NULL);
XtVaSetValues(toolbarWidget, XmNheight, 25, NULL);
MwCtGetInterface(toolbarwidget, (IUnknown **) &g_ipToolbarControl);
// Create a sub-form to place TOCControl and MapControl on
Widget leftformpanel = XtVaCreateWidget("leftformpanel", xmFormWidgetClass,
mainForm, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, topFormPanel,
XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM,
XmNwidth, 200, NULL);
/* some additional code is already in place here in your file */// PageLayoutControl setup
pagewidget = XtVaCreateWidget("pagewidget", mwCtlWidgetClass, mainform,
XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, topFormPanel,
XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, leftFormPanel,
XmNbottomAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM,
MwNprogID, AoPROGID_PageLayoutControl, NULL);
MwCtlGetInterface(pagewidget, (IUnknown **) &g_ipPageLayoutControl);
- Manage the new widgets when the others are managed.
XtManageChild(leftFormPanel);
XtManageChild(topFormPanel);
XtManageChild(g_customizeToggle);
XtManageChild(toolbarWidget);
- Add a new function called CreateCustomizeDialog. This is where you will create the Customize dialog box.
- You will place the forward declaration in MapViewer.h.
HRESULT CreateCustomizeDialog();
- Define it at the bottom of MapViewer.cpp.
HRESULT CreateCustomizeDialog()
{
g_ipCustomizeDialog.CreateInstance(CLSID_CustomizeDialog);
// Set the title
g_ipCustomizeDialog->put_DialogTitle(CComBSTR(L "Customize Toolbar Items"));
// Don't show the "Add From File" Button// Adding from file is not an option for your custom C++ Commands.// The C++ API requires programmatic placement of custom commands// onto the toolbar control. With the built-in ArcGIS Engine// Commands already visible in the dialog, nothing needs to be// added from file.
g_ipCustomizeDialog->put_ShowAddFromFile(VARIANT_FALSE);
// Set the ToolbarControl that the new items will be added to
g_ipCustomizeDialog->SetDoubleClickDestination(g_ipToolbarControl);
return S_OK;
}
- Remember to clean up g_ipCustomizeDialog when the application closes.
g_ipCurrentExtent = 0;
g_ipCustomizeDialog = 0;
- Call CreateCustomizeDialog from the main function sometime after the ToolbarControl is initialized. If this is done any earlier, the Customize dialog box will not be associated with the ToolbarControl. For this scenario, call the function after the call to AddToolbarItems.
CreateCustomizeDialog();
- Create a callback function for the toggle button. When the user clicks the toggle button to the on state, you want to show the Customize dialog box. When it is clicked off, the Customize dialog box should disappear.
If the ArcGIS Engine commands are not appearing in the Customize dialog box, there is a problem with your registry. Those commands can be added from the file, and to have that option in the Customize dialog box, pass VARIANT_TRUE into put_ShowAddFromFile().
- Place the forward declaration in MapViewer.h after the one for CreateCustomizeDialog.
void ToggleCallback(Widget w, XtPointer client_data, XtPointer call_data);
- At the bottom of MapViewer.cpp, place the definition of the callback.
void ToggleCallback(Widget w, XtPointer client_data, XtPointer call_data)
{
XmToggleButtonCallbackStruct *customize = (XmToggleButtonCallbackStruct*)
call_data;
long hWnd;
g_ipToolbarControl->get_hWnd(&hWnd);
if (customize->set)
g_ipCustomizeDialog->StartDialog(hWnd);
else
g_ipCustomizeDialog->CloseDialog();
}
- Also set up the callback right after the customizeToggle has been created in FormSetup.
XtAddCallback(g_customizeToggle, XmNvalueChangedCallback, ToggleCallback, NULL);
- To put the toolbar into the customize state when the dialog box is started, listen for the CustomizeDialog events. Implement a class, CustomizeDialogEvents, which inherits from ICustomizeDialogEvents.
- First make the header file for the class: CustomizeDialogEvents.h. The class will need to have access to the global ToolbarControl and the g_customizeToggle widget. Make sure to include the files needed for the ToolbarControl and the toggle widget. ICustomizeDialogEvents is declared with the other classes for the ToolbarControl, so be sure to include toolbarcontrol.tlh and toolbarcontrol_events.tlh.
This class will be Motif specific since it must access the toggle button, a Motif widget. For the GTK-specific code, see CustomizeDialogEvents.h and CustomizeDialogEvents.cpp in the GTK zip file.
#ifndef __CUSTOMIZEDIALOGEVENTS_H_
#define __CUSTOMIZEDIALOGEVENTS_H_
// Motif Headers#define String esriXString
#define Cursor esriXCursor
#define Object esriXObject
#define ObjectClass esriXObjectClass
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/Form.h>
#include <Xm/Protocols.h>
#include <Xm/ToggleB.h>
#undef String
#undef Cursor
#undef Object
#undef ObjectClass
// ArcObjects Headers// Engine#include <ArcSDK.h>
// Controls#include <Ao/AoControls.h>
extern IToolbarControlPtr g_ipToolbarControl;
extern Widget g_customizeToggle;
class CustomizeDialogEvents: public ICustomizeDialogEvents
{
public:
// IUnknown
IUNKNOWN_METHOD_DEFS
// ICustomizeDialogEvents
HRESULT OnStartDialog();
HRESULT OnCloseDialog();
};
#endif// __CUSTOMIZEDIALOGEVENTS_H_
- Place the implementation for this class into CustomizeDialogEvents.cpp, making sure you include the header file for the class. Implement the functions so that when the dialog box is opened, the toolbar enters the customize state, and when it is closed, the toolbar leaves that state. When it is closed, also make sure to set the toggle button to false, as the dialog box may be closed with a close button that is on it.
#include
"CustomizeDialogEvents.h"
HRESULT CustomizeDialogEvents::OnStartDialog()
{
g_ipToolbarControl->put_Customize(VARIANT_TRUE);
return S_OK;
}
HRESULT CustomizeDialogEvents::OnCloseDialog()
{
g_ipToolbarControl->put_Customize(VARIANT_FALSE);
XmToggleButtonSetState(g_customizeToggle, false, true);
return S_OK;
}
Although the class itself is Motif or GTK specific, listening for either one is done the same way.
- Like the other event classes, these events must be listened for. Start by including the header file in MapViewer.h after the include for MapControlEvents.h.
#include
"CustomizeDialogEvents.h"
- Declare variables for those events at the top of MapViewer.cpp after the declaration for g_ipMapControlEvents2Helper.
CustomizeDialogEvents *g_customizeEvents;
IEventListenerHelperPtr g_ipCustomizeEventHelper;
- Listen for them right after advising on the MapControl events.
g_ipMapControlEvent2Helper->AdviseEvents(ipMapControl, NULL);
g_customizeEvents = new CustomizeDialogEvents();
g_ipCustomizeEventHelper.CreateInstance(CLSID_CustomizeDialogEventsListener);
g_ipCustomizeEventHelper->Startup(static_cast < CustomizeDialogEvents * >
(g_customizeEvents));
CComBSTR bsGUID;
::StringFromIID(IID_ICustomizeDialogEvents, &bsGUID);
IUIDPtr ipUID(CLSID_UID);
ipUID->put_Value(CComVariant(bsGUID));
g_ipCustomizeEventHelper->AdviseEvents(g_ipCustomizeDialog, ipUID);
- Clean up the events in CloseAppCallback after deletion of g_mapEvents.
g_ipCustomizeEventHelper->UnadviseEvents();
g_ipCustomizeEventHelper->Shutdown();
g_ipCustomizeEventHelper = 0;
delete g_customizeEvents;
- Update the makefile. Add CustomizeDialogEvents.cpp to the sources list and add CustomizeDialogEvents.h to the MapViewer.cpp dependencies list. Make a dependencies list for CustomizeDialogEvents.o.
- Compile and run the application. Check the customize toggle button to put the ToolbarControl into customize mode and open the Customize dialog box.
- On the Commands tab, choose the Graphic Element category and either drag the Select Elements command to the toolbar or double-click it to add it to the ToolbarControl. By right-clicking an item on the toolbar, you can adjust the appearance in terms of style and grouping. Change the icon you have just added to display both image and text.
- Stop customizing the application. Use the select tool to move the text element containing today's date.
Deployment
To successfully deploy this application onto a user's machine:
-
The user machine will require an installation of the ArcGIS Engine Runtime.
-
The user's machine will need to have its ArcGIS Engine Runtime initialized.
-
The executable created at compile time will need to be deployed onto the user's machine.
-
To run: At the command line, type "./MapViewer".
Additional references
The following resources may help you understand and apply the concepts and techniques presented in this scenario.
-
The ArcObjects SDK for Cross Platform C++ documentation. This includes component help, object model diagrams, and samples to help you get started.
-
The ESRI ArcObjects Online Web site and the ESRI online discussion forums.
-
Heller, Dan, Paula M. Ferguson, and David Brennan. Motif Programming Manual (The Definitive Guides to the X Window System, Volume 6A) 2nd Edition. O'Reilly & Associates. 1994.
-
Oram, Andy, and Steve Talbott. Managing Projects with make, 2nd Edition. O'Reilly Press.