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.
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.
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.
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.
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.
typedef s 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.
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.
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.
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.
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.
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.
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).
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.
|