AppSketcher: Add Your Own Classes


AppSketcher lets you add your own classes so you can use instances of these classes in your modules. At its fullest, a class has four parts:
  • A class definition (mandatory). This is a standard ".h" header file that contains a class declaration.

  • A class template (optional). This is source code that's used to construct an instance of your class when your application is running.

  • Custom view-drawing code (views only; optional). This is an add-on that AppSketcher loads and calls when you add an instance of your class to your module. The add-on tells AppSketcher how to display your view object.

  • An inspector (optional). This is a separate AppSketcher module that defines the look and functionality of the object inspector for your class.
The following sections examine these four parts, and explain what you can do with the classes you've added.


The Class Definition


AppSketcher knows how to parse C++ header files. The parser looks for class declarations and creates a Class record and Class file for each declaration that it finds. This allows the Class Viewer to display your classes so you can include instances of them in your module. See "Classes and the Class Viewer" for details about Class records and files. Here we describe what your header file should look like, how you load (or import) it, and what AppSketcher looks for in your classes.


The Header File

AppSketcher doesn't impose any restrictions on the locations or configurations of the files that contain your class declarations-- except this one:

All class declarations must be in header files that have a ".h" extension.
A single header file can contain any number of class declarations. AppSketcher will parse and import all the declared classes that it finds in your file. Furthmore, the classes that you declare needn't be implemented--when importing a class, AppSketcher is only interested in the class declaration.

To import a header file, you can

  • Drop the file icon onto the AppSketcher application icon.
  • Drop the file icon onto the Class Viewer window.
  • Use the Class Viewer's Import... commands in the Class Menu.

What AppSketcher Looks For

When AppSketcher parses a class, it looks for the following things:

  • The name of the class.

  • The name of the superclass. If the superclass isn't currently imported, your class will be imported anyway. However, it (your class) won't appear in AppSketcher's Class Viewer; it will show up after you've imported all its C++ ancestors.

  • Certain public member functions and data members (as explained below). These members will be displayed in your module's Create Link window.
Everything else in the header file is ignored. Note that commented code is stripped.

Link Candidates

To be a candidate for a link, a member function must...
Be public. Private and protected functions aren't considered.

Return a recognized data type. A "recognized type" is any of the standard C types such as long or (typically) void, as well as any object (or object pointer) of a class that's been imported. typedefs and structures are not recognized.

Take a single argument of a recognized data type.

Constructors and destructors are never considered.
Data members are considered similarly--they must be of a recognized type.

As a demonstration of some of these principles, the following shows the declarations for YourClass and its superclass, and also shows the list of YourClass's candidate functions and data members as displayed in a Create Link window (given as if a link were being created for a BButton object). Notice that the list includes functions (but not data members) that are inherited from the superclass.

For more information on links, see Creating Links in the Instance Viewer documentation.


The Class Template


A template is a C++ source code file that contains instructions for instantiating an object. AppSketcher supplies a number of templates for various classes; you can find these templates in the Templates directory of the AppSketcher package, shown here. Each template is named for the class of object that it creates. For example, the template that creates a BButton is called InstantiateBButton.cpp.
The Templates directory must live in the same directory as the AppSketcher executable. (Templates is the only AppSketcher component that mustn't be moved.)

When you add an object to your module (and save the module), AppSketcher finds the appropriate template (as explained below), and copies it into your module's "source code" directory. This is the (Modules Src)/ModuleName directory that's automatically created for you when you save your module. For example, the source directory for the Sample1 module, which is included with AppSketcher, looks like this:

The "appropriate" template for a given object is...

  • If a template file exists for the class of object that you're adding, that template is added.

  • If there's no template for your object's class, AppSketcher looks for an existing template for your object's nearest ancestor. When it copies the file, it renames the copy to match the name of your derived class.

  • If there's no ancestor template (if you're adding a base class, for example), AppSketcher generates source code that uses a "vanilla" (no-arguments) constructor to instantiate your object. This instantiation code is placed in a file that follows the template naming style: InstantiateClassName.cpp. The file is added to the module's source code directory along with the template copies.
Even if you think the vanilla instantiator is sufficient, you should always check the code that AppSketcher generates and places in your module's source code directory.
The one exception to all this is the BApplication class. When you add a BApplication object (or an instance of a class that descends from BApplication) to your module, AppSketcher creates a "main" file that's slightly more complex than the files it generates for all other no-template classes. See "Adding an Application Object" for details.


Modifying the Instantiation File

When you're inheriting a template from an ancestor class, or when AppSketcher generates instantiation code for you, you may want or need to modify the "instantiation files" that are added to your module. For example, if you return to the YourClass class declaration that was shown previously, you'll notice that the constructor requires at least one argument:

class YourClass : public YourSuperClass
{
	public:

		YourClass(const char *name, 
			bool enabled=TRUE);
		...

However the instantiation code that AppSketcher generates for a YourClass object looks like this:

#include "YourClass.h"

#pragma export on
YourClass* InstantiateYourClass(BMessage *message);
#pragma export off

BClipboard* InstantiateYourClass(BMessage *message)
{
return new YourClass();
}

This, obviously, won't compile--the constructor is wrong. You have to go into the file and change the constructor:


...

BClipboard* InstantiateYourClass(BMessage *message)
{
return new YourClass("My Object");
}

AppSketcher notices if you've changed an instantiation file in your source code directory. The next time you save your module, your modified file won't be clobbered. However...

Changes to an instantiation file within a source code directory only affect the module that owns that directory. If you want your changes to affect other modules, you should copy your file into the Templates directory.
The relationship between instantiation files (in the source code directory) and the objects in your module isn't one-to-one. AppSketcher only needs one instantiation file for each class of object that you add to your module. For example, if you add three BButtons to your module, you'll only find one InstantiateBButton.cpp in the directory. This has an important implication:
You can't create "custom" instances of a class by creating separate instantiation files for each instance. You can customize the objects that a single instantiation file creates, but you must do so programmatically within the single InstantiateClassName() function. In general, this requires an object inspector.


The Instantiation Function

Let's look again at the instantiation function that we modified above:


...

BClipboard* InstantiateYourClass(BMessage *message)
{
return new YourClass("My Object");
}

You may be wondering...

What's that BMessage argument for? Where does it come from?
The BMessage contains information that's created by the object's inspector, if it has one. If you haven't created an inspector for your class, then you can ignore the BMessage argument.

When (and by whom) is the instantiation function called?
The instantiation code that AppSketcher generates (or copies from a template file) is invoked from within your running application when the module to which it belongs is loaded. You load a module by constructing an LRModule object and invoking its LoadModule() function:

#include "LRModule.h" 
...
	LRModule aMod = new LRModule("MyModule");

	/* LoadModule() returns a boolean indicating success. */
	if (aMode->LoadModule())
		/* success */
	else
		/* failure */

Since you're creating the LRModule object and calling the LoadModule() function yourself, you control "who" (as in "which thread") instantiates the objects in the module.

If you add a BApplication object to your module, AppSketcher generates (and adds to your source code directory) code that creates and opens an LRModule for you. In this case, the module's objects are created in the main thread. This topic is examined in the next section.


Adding an Application Object

The instantiation file that AppSketcher generates when you add a BApplication--or, to stick to our übertopic, any instance of a class that derives from BApplication--is considerably different from that which is generated for all other objects. The five primary features of the file are these:
  • The file is named ModuleName_main.cpp.

  • The file contains a main() function.

  • The instantiation function (for the BApplication object) is given an "app signature" argument. (You set the signature in AppSketcher through the BApplication object inspector.)

  • An LRModule object is created and opened in the main thread.

  • The instantiated BApplication object is told to run (its Run() function is invoked).
The "main" file for the Sample1 module (for example) looks like this:

/* Generated by Lorienne AppSketcher */
#include "Application.h"
#include "LRModule.h"

main()
{
	new BApplication('MVSA');
	LRModule *module = new LRModule ("Module1");
	if (module->LoadModule()) {;
		be_app->Run();
	}
	delete be_app;

	return 0;
}

There's one more important difference between the main file and the other object instantiation files:

The main file is generated every time you save your module. It's never copied from a template file (you can't create an template file for a BApplication-derived class), and it doesn't follow the "no-clobber" rule. If you want to modify the main file that's generated by AppSketcher, you'll have to add to the BeIDE another main file than the one generated by AppSketcher.


Custom Views


Let's say you want to create your own BView-derived class that you can use in AppSketcher. That much is easy: Simply create a header file for the class and drop it into the Class Viewer. When you add an instance of your class to your module (by adding it to a window, most likely), your view will be drawn in the module's interface window. However, it won't look like the object that you wanted--it will look like a normal BView object (or whatever AppSketcher-recognized class that it descends from).

So--how do you get AppSketcher to draw your view? In addition to defining the header file and dropping it into the Class Viewer, you have to do the following:

  • Create the implementation for your class.

  • Create an instantiation file for your class and (optionally) add it to the Templates directory. (This is described in "The Class Template.")

  • Create a BeIDE project and compile your class as an add-on. You have to add the instantiation file to this project.

  • Add the add-on to AppSketcher's add-ons directory.
As a demonstration of all this, turn to the Samples/Sample3 directory in the AppSketcher package.


Sample3: The Round Button

In Samples/Sample3 you'll find the file RoundButton.h. Drop this file into the Class Viewer and wait for the RoundButton class to load.

Now, double-click the module file (Round), and you're presented with an interface window that contains two objects. Both objects look like normal BButtons.



However, if you control-click the right button, you'll see, in the Instance Viewer, that this object is really an instance of the RoundButton class.

The implementation for the RoundButton class is provided in the file RoundButton.cpp. You can open and examine the file at your leisure; for the current topic, all we're interested in is compiling the file.

A sample BeIDE project file, Sample3.proj, is also included in the directory. It shows how to set up the project to create an add-on. The essential business is in the PPC Project settings:

    Set the application type to Shared Library (a shared library is anatomically identical to an add-on).

  • Set the target file name to RoundButton.addOn.

  • Set the creator to 'lrmv'.

  • Set the file type to AMAO.
The entire panel is shown below.

Making the project creates the RoundButton.addOn file.

The usual AppSketcher project requirements apply to add-ons. Remember to add the AppSketcher libraries to your project and set the linker's PEF setting to Use #pragma.

At this point, you should quit AppSketcher, and then double-click the Sample3 module. This will re-launch AppSketcher which will load the newly created add-on. When the interface window is displayed, the RoundButton object will be drawn as instructed in the Draw() function that's implemented in the RoundButton.cpp file. If you want other modules to also draw the RoundButton properly, you should move the add-on file into AppSketcher's add-ons. But while you're refining the look of the object it's convenient to keep the add-on local to your module (in the same directory).


The Object Inspector


When you double-click an object that you've added to your module, AppSketcher looks for an inspector that corresponds to the object's class. This "object inspector" is a panel that lets you set various attributes of the object. The inspector shown here is displayed when you double-click a BView object.

AppSketcher provides a number of object inspectors. Each inspector is a self-contained AppSketcher module; you can find these inspectors in the Modules directory in the AppSketcher package.

The rules of inspector selection are similar to those that we saw when we examined templates:

  • If an inspector exists for the class of object that you're inspecting, that inspector is displayed.

  • If there's no inspector for your object's class, AppSketcher looks for an existing inspector for your object's nearest ancestor.
Unlike with templates, there's no "generate an inspector" branch in this search: If the object and none of its ancestors has an inspector, then no inspector is displayed. Prove it to yourself: Add, say, a BSubscriber (from the Media Kit) to your module and double-click it. Nothing.

To create your own inspector you need to explain to AppSketcher what to do when an inspector is requested. Doing this follows the same rules as creating an add-on for a specific view: you have to create an add-on containing three functions:

- an instantiation function,

- a function that shows the inspector

- a function able to archive instances of your object.

Names for these three functions are determine according to the name of your class.

Instantiation functions will be named InstantiateMyClass and will receive an argument that will be a pointer to a BMessage. The fields of this BMessage will contain the data used to archive your instance. This function will return a pointer to the newly instantiated instance.

Inspector functions will be named ShowInspectorMyClass, won't receive any argument and will receive a pointer to the BWindow inspector. This BWindow must inherit from the ObjectInspector class. At run time, the inspector will receive a BMessage and will initialize two of its members: instance (a pointer to the object itself) and archive (a pointer to the BMessage containing data that describes the instance). An inspector will modify the BMessage archive according to user interaction and to data that could be returned by instance when required.

Archive functions will be named ArchiveMyClass, will receive as argument a pointer to the BMessage containing the current archive of the instance, and will return a pointer to the new archive. These two pointers can be identical.


Copyright ©1997 BeatWare Incorporated. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of BeatWare, Inc. is prohibited. Be is a registered trademark, and BeOS, BeBox, BeWare, GeekPort, the Be logo and the BeOS logo are trademarks of Be, Inc. All other trademarks mentioned are the property of their respective owners.
Comments about this page? Please write us at webmaster@beatware.com.
All rights reserved.