C++Builder 5 Developers Guide
Let's get our feet wet and create a simple wizard that integrates into the C++Builder IDE using the Tools API. So that we develop something practical, the wizard we will create will provide a mechanism for users to gauge the memory performance of their applications under development ”it is called the MemStat Wizard. We won't get into the specifics of how to attain and measure memory performance (examine the code on the CD-ROM for that insight). Instead, our goal here is to understand how to establish a wizard and extend the IDE using the Tools API.
NOTE
You can follow along with the MemStat Wizard example we develop in this book by locating the following folders for this chapter on the CD-ROM that accompanies this book.
-
wizard_part1_simple
-
wizard_part2_services
-
wizard_part3_notifier
-
wizard_part4_editor
-
wizard_part5_dll
Each folder represents progressions of our MemStat Wizard that we build on within this chapter. The label tacked on to the end of each folder identifies the example for the section being discussed. For instance, the example contained in the wizard_part2_services folder is discussed in the "Creating and Using Services," section within this chapter. The project name for each of these examples is wizard.bpk .
Selecting a Wizard Interface
The first step is determining the type of wizard interface we need for the MemStat Wizard. The ToolsAPI.hpp file reveals four different types of wizard interfaces as listed in Table 23.2.
Table 23.2. Tools API Wizard Interfaces
Wizard Interface | Description |
---|---|
IOTAFormWizard | Used to represent a wizard that generates a new unit or form. This type of wizard resides in the Object Repository, which can be accessed through the New Items dialog box. |
IOTAMenuWizard | Used to represent a simple wizard such as a dialog box that is accessed from the IDE's Help menu. |
IOTAProjectWizard | Used to represent a wizard that generates a new project or application. This type of wizard resides in the Object Repository, which can be accessed through the New Items dialog box. |
IOTAWizard | This is the root interface for the other three wizard interfaces. It's used to represents a simple wizard such as a dialog that can be custom configured to the IDE's menu bar or tool bar. |
What differentiates these four interfaces is how each one is invoked. The first three are automatically associated to either the Object Repository or the IDE's Help menu. The last interface, IOTAWizard , must be manually associated to an IDE element.
For simplicity and prototyping the IOTAMenuWizard provides the type of interface we need for building and demonstrating our example. All we want, initially, is a wizard that can be invoked from the Help menu bar.
Next , we need to build a specialized wizard class that inherits the IOTAMenuWizard interface. That sounds fairly simple, but there are other interfaces involved that we need to inherit as well.
Reconstructing TNotifierObject for C++Builder
It might be obvious that IOTAMenuWizard is a descendent of IOTAWizard , but IOTAWizard descends from another interface called IOTANotifier . We need to pay special attention to IOTANotifer because the compiler is expecting our custom class to support its methods. Let's take a look at how this interface is defined to see what methods we need to implement.
__interface IOTANotifier; typedef System::DelphiInterface<IOTANotifier> _di_IOTANotifier; __interface INTERFACE_UUID("{F17A7BCF-E07D-11D1-AB0B-00C04FB16FB3}") IOTANotifier : public IInterface { public: virtual void __fastcall AfterSave(void) = 0 ; virtual void __fastcall BeforeSave(void) = 0 ; virtual void __fastcall Destroyed(void) = 0 ; virtual void __fastcall Modified(void) = 0 ; };
IOTANotifier is a Tools API interface used by the IDE to notify an item (such as a wizard) of important events. We'll talk more about some of the specialized Notifier interfaces provided by the Tools API in the "Creating and Using Notifiers" section, but, for the case of our wizard, we need to understand the underpinnings provided by IOTANotifier . The four methods associated to IOTANotifier are explained in Table 23.3.
Table 23.3. Tools API ” IOTANotifier Methods
Method | Description |
---|---|
AfterSave | Call immediately after the associated item is successfully saved. Not called for IOTAWizard . |
BeforeSave | Called immediately before the associated item is saved. Not called for IOTAWizard . |
Destroyed | The associated item is being destroyed. |
Modified | The associated item is being modified. Note: not called for IOTAWizard . |
Although we are creating a simple wizard that has no real concern for notifications at this point, the IDE still expects our custom class to be capable of supporting the abstract methods of IOTANotifier . We need something in its place that provides empty implementations for IOTANotifer methods because the compiler is expecting to find these methods. To fill-in for IOTANotifier and support custom wizards, Borland provides a convenient class within Delphi called TNotifierObject , which is designed to be inherited by a custom wizard class in place of IOTANotifer . In Delphi, this class integrates seamlessly for supporting wizards. Unfortunately the ToolsAPI header file provided within C++Builder does not include a TNotifierObject class!
So, what do we do? The easiest thing to do is to build our own TNotifierObject C++ class and stick it in a file that we can use to support our Tools API implementations for C++Builder. We need TNotifierObject so that we can build a Wizard class that supports the methods identified in the interfaces it inherits. A newly created header file called ToolsAPIEX.h contains the declaration for this TNotifierObject class, which is shown in Listing 23.1.
Listing 23.1 ToolsAPIEx Header File ” TNotifierObject Class Declaration
#ifndef ToolsAPIExH #define ToolsAPIExH // - #include <ToolsAPI.hpp> #include <typeinfo> // macro for implementing interfaces #define QUERY_INTERFACE(T, iid, obj) \ if ((iid) == __uuidof(T)) \ { \ *(obj) = static_cast<T*>(this); \ static_cast<T*>(*(obj))->AddRef(); \ return S_OK; \ } #ifdef DLL // we'll need this for building a DLL #define BorlandIDEServices LocalIDEServices extern _di_IBorlandIDEServices LocalIDEServices; #endif class PACKAGE TNotifierObject : public IOTANotifier { public: __fastcall TNotifierObject() : ref_count(0) {} virtual __fastcall ~TNotifierObject(); void __fastcall AfterSave(); void __fastcall BeforeSave(); void __fastcall Destroyed(); void __fastcall Modified(); protected: // IInterface virtual HRESULT __stdcall QueryInterface(const GUID&, void**); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); private: long ref_count; }; #endif
Notice one of the first things we do in setting up to use the Tools API is to include the ToolsAPI.hpp file. You'll then notice a macro at the top called QUERY_INTERFACE . This macro was defined and used, as suggested by Borland, to simplify the processing required for querying each of the interfaces that a custom wizard inherits and uses (getting a pointer to an interface object). The effect is identical to what's required with COM using the QueryInterface() method. In a short while, you'll see this macro being utilized.
Following the definition of this macro, you'll see support for handling a DLL implementation, which we will talk about a little later. Finally, we come across the declaration for our TNotiferObject class. In addition to the four methods contained by IOTANotifer , there are three other methods we've defined in our class: QueryInterface() , AddRef() , and Release() . The IOTANotifer interface, which we are modeling, descends from IInterface , which is the base class for all Object Pascal interfaces. In C++Builder, IInterface is a child interface of IUnknown . If you're familiar with COM, you'll recognize that IUnknown is the base class for all COM interfaces. In this case, our TNotiferObject needs to support the methods of IInterface to operate accordingly . The implementation for the entire class, which is contained in our ToolsAPIEx.cpp source file that we've created, is provided in Listing 23.2.
Listing 23.2 ToolsAPIEx Source File ” TNotifierObject Class Implementation
#pragma hdrstop #include "ToolsAPIEx.h" #pragma package(smart_init) HRESULT __stdcall TNotifierObject::QueryInterface(const GUID& iid, void** obj) { QUERY_INTERFACE(IInterface, iid, obj); QUERY_INTERFACE(IOTANotifier, iid, obj); return E_NOINTERFACE; } // - ULONG __stdcall TNotifierObject::AddRef() { return InterlockedIncrement(&ref_count); } // - ULONG __stdcall TNotifierObject::Release() { ULONG result = InterlockedDecrement(&ref_count); if (ref_count == 0) delete this; return result; } // - __fastcall TNotifierObject::~TNotifierObject() {} void __fastcall TNotifierObject::AfterSave() {} void __fastcall TNotifierObject::BeforeSave() {} void __fastcall TNotifierObject::Destroyed() {} void __fastcall TNotifierObject::Modified() {}
The implementation for QueryInterface() method queries each interface that we've inherited or used. In this case it is IInterface and IOTANotifer . AddRef() and Release() handle the interface reference counting (saving and releasing the interface). You'll notice it's very COM-like ”as designed.
For the purposes of our demonstration, and for supporting future custom wizards, the TNotifierObject class interface and implementation have been saved to the ToolsAPIEx.h and ToolsAPIEx.cpp files included on the companion CD-ROM for this chapter. You can use this file as part of any C++Builder project used to build custom wizards for the IDE.
NOTE
Although the Tools API supports C++ implementations through C++Builder, its native language is Delphi. Keep in mind that the IDE for C++Builder is written in Delphi as well. Although this chapter is focused on using C++ to extend the IDE, it's quite possible to extend the IDE for C++Builder using Delphi code units. This is something to consider because many of the examples available on the Internet and in print format are written in Delphi. The companion CD-ROM for this book provides an example, which consists of a C++Builder project mixed with a Delphi unit. This project, titled Wizard_using _ delphi.bpr , can be found under the HelloWorldWizard folder.
If you're like me and you prefer to use C++, you can still benefit from the Delphi code examples that are out there. Examining code examples that are written in Delphi will shed a little bit of light on what will be needed for implementing it in C++Builder.
Defining a Custom Wizard Class
Now we have a TNotifierObject defined and implemented; let's dive in and begin to create our custom C++ wizard class, as shown in Listing 23.3. This code is contained in the wizard_memstatus.h file found in the wizard_part1_simple folder on the companion CD-ROM.
Listing 23.3 MemStatusWizard Class Definition
class PACKAGE MemStatusWizard : public NotifierObject, public IOTAMenuWizard { typedef TNotifierObject inherited; public: __fastcall MemStatusWizard(); __fastcall ~MemStatusWizard(); // IOTAWizard virtual AnsiString __fastcall GetIDString(); virtual AnsiString __fastcall GetName(); virtual TWizardState __fastcall GetState(); virtual void __fastcall Execute(); // IOTAMenuWizard virtual AnsiString __fastcall GetMenuText(); void __fastcall AfterSave(); void __fastcall BeforeSave(); void __fastcall Destroyed(); void __fastcall Modified(); protected: // IInterface virtual HRESULT __stdcall QueryInterface(const GUID&, void**); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); };
This class inherits multiple interfaces, including the TNotifierObject class we just created, and IOTAMenuWizard . The TNotifierObject class needs to be identified as the base class for our wizard. This is done using the following typdef clause.
typedef TNotifierObject inherited;
If you have ever used the Tools API with Delphi, you might notice that our C++Builder class identifies a few more methods than a comparable Delphi example would. Although Delphi and C++Builder are similar, there are also some peculiarities between the two. In this case, Delphi can handle the creation of abstract classes, whereas C++Builder can't. We need to be able to create an instance of our custom wizard when we register it with the IDE. To elevate the C++Builder class from an abstract class, we need override the member functions of IOTAMenuWizard , IOTAWizard , and TNotifierObject .
The code for our custom wizard class is contained in the wizard_memstatus.cpp source file as shown in Listing 23.4.
Listing 23.4 MemStatusWizard Class Implementation
ULONG __stdcall MemStatusWizard::AddRef() { return inherited::AddRef(); } ULONG __stdcall MemStatusWizard::Release() { return inherited::Release(); } HRESULT __stdcall MemStatusWizard::QueryInterface(const GUID& iid, void** obj) { QUERY_INTERFACE(IOTAMenuWizard, iid, obj); QUERY_INTERFACE(IOTAWizard, iid, obj); return inherited::QueryInterface(iid, obj); } void __fastcall MemStatusWizard::AfterSave() {} void __fastcall MemStatusWizard::BeforeSave() {} void __fastcall MemStatusWizard::Destroyed() {} void __fastcall MemStatusWizard::Modified() {} AnsiString __fastcall MemStatusWizard::GetIDString() { return "CBuilderDevelopersGuide.MemStat Wizard"; } AnsiString __fastcall MemStatusWizard::GetName() { return "MemStat Wizard"; } TWizardState __fastcall MemStatusWizard::GetState() { TWizardState result; result << wsEnabled; return result; } AnsiString __fastcall MemStatusWizard::GetMenuText() { return "MemStat Wizard..."; } void __fastcall MemStatusWizard::Execute() { TFormMemStat* FormMemStat = new TFormMemStat(0); FormMemStat->ShowModal(); delete FormMemStat; } __fastcall MemStatusWizard::MemStatusWizard() { } __fastcall MemStatusWizard::~MemStatusWizard() { } namespace Wizard_memstatus { void __fastcall PACKAGE Register() { RegisterPackageWizard(new MemStatusWizard ()); } }
You're encouraged to browse through this code to see what's happening; it's fairly self-explanatory. Again, we use QueryInterface() to latch onto the interfaces we inherit and use. GetIDString() is used to uniquely identify our custom wizard. Similarly, GetName() is used to identify a friendly name for the custom wizard, whereas GetMenuText() is used to identify the text for our menu item found under the Help menu. The GetState() method is used to identify the state for the menu item linked to invoke the custom wizard.
Probably the most important method of our class is Execute() . When the wizard is invoked from the IDE, the Execute() method is called. In this example, a form containing the GUI and processing for attaining and measuring system memory is opened using the familiar ShowForm() method. We could have just as easily called any other form created using C++Builder, or placed a call to a simple ShowMessage() window.
Although it's not depicted in this example, we can also add additional methods to our class, which we will demonstrate a little later.
Registering a Wizard Class
You'll notice that last thing included in the previous code listing is the Register() function wrapped around the unit's namespace. An instance of MemStatusWizard is used with the Tools API RegisterPackageWindow() function to register the custom wizard with the IDE. The Register() function is called when the source file is included with a package. Figure 23.1 illustrates the package used to register the MemStatusWizard . This package, called wizard.bpk , can be found on the companion CD-ROM under the wizard_part1_simple folder for this chapter. To learn more about creating and registering packages see Chapter 4.
Figure 23.1. MemStatusWizard Package.
Also, we need to make sure that our package is a design-time package. Look under the Description tab of the Project Options, as illustrated in Figure 23.2, to see the proper usage.
Figure 23.2. Setting the Project options for a wizard package.
The End Result
Now that we have our custom wizard class registered, it's time to see if it works. Click the Help menu of the IDE and scroll down and select MemStat Wizard as illustrated in Figure 23.3.
Figure 23.3. Launching the MemStat Wizard from the IDE.
The MemStat Wizard should then appear, as seen in Figure 23.4.
Figure 23.4. The MemStat Wizard.
|
|
Top |