Extension of Named Properties

Creating your own named property class is a straightforward, three-step procedure:

Create a subclass of IlvNamedProperty.

Choose the symbol that will be used to access this property.

We remind you that in Views all symbols whose names begin with the characters "_ilv" are reserved for internal use.

Define how this class will be persistent and register this class with Views.

Using a very simple example—the storage of two values—the following section explains how to create a named property that you can associate with graphic objects. You can, however, build named properties with member functions to handle more elaborate data members, or have named properties that store pointers to existing classes. Using named properties, you can link application data with a high level of complexity to light graphic objects, with minimum coding and without altering the API of your application classes.

Example: Creating a Named Property

To illustrate, we are going to create a named property that holds both an integer and a character string and make it easily accessible and persistent.

The general development tasks are:

Declaring the Named Property: Header File

The named property that we are going to create must be a subclass of IlvNamedProperty. The named property stores an integer and a string. Its complete header file is given below:

#include <ilviews/base/graphic.h>

 

class MyProperty

: public IlvNamedProperty

{

public:

MyProperty(int integer,

char* string);

virtual ~MyProperty();

 

int getInteger() const { return _integer; }

void setInteger(int integer) { _integer = integer; }

 

const char* getString() const { return _string; }

void setString(const char* string);

 

static IlSymbol* GetSymbol();

 

DeclarePropertyInfo();

DeclarePropertyIOConstructors(MyProperty);

 

private:

int _integer;

char* _string;

static IlSymbol* _Symbol;

};

In addition to the two data members _integer and _string (and their accessors), we will focus on the _Symbol static data member and on the two macro calls that appear in the declaration part of the class, namely DeclarePropertyInfo and DeclarePropertyIOConstructors.

Note that the destructor of the class is virtual like the one of the base class IlvNamedProperty.

Defining the Symbol for Accessing the Named Property

First we are going to define the property symbol that will be used to access the class. A simple way to define this symbol is to make it a static data member of your property class, _Symbol, and provide it with a public accessor, GetSymbol. By doing this, any application will be able to retrieve an instance of MyProperty without having to know which symbol is used to associate it with an object.

Therefore, the public and static accessor GetSymbol is defined to return the appropriate IlSymbol and create it if necessary.

In the code below, an extract of the implementation file, we define both the accessor to the property symbol and the static data member, which we initialize to 0. Note that the symbol is created the first time it is queried in MyProperty::GetSymbol.

IlSymbol*

MyProperty::GetSymbol()

{

if (!_Symbol)

_Symbol = IlGetSymbol("MyPropertySymbol");

return _Symbol;

}

 

IlSymbol* MyProperty::_Symbol = 0;

Defining the Constructor for the Named Property

Let’s now examine the constructor and the destructor. All we need to do is call the constructor of the parent class, IlvNamedProperty, and initialize our data members:

MyProperty::MyProperty(int integer,

char* string)

: IlvNamedProperty(GetSymbol()),

_integer(integer),

_string(0)

{

setString(string);

}

 

MyProperty::~MyProperty()

{

if (_string)

delete [] _string;

}

The first time a property of the MyProperty type is created, the static member function GetSymbol is called to set the static data member _Symbol to a valid value.

The string parameter is copied by the setString member function that will check whether the data member _string is valid. This is why we initialize this data member to 0 in the initializers of the constructor. This parameter is destroyed in the destructor, if it was valid.

Defining the setString Member Function

Below is the definition of the setString member function that copies and stores the string:

void

MyProperty::setString(const char* string)

{

if (_string)

delete [] _string;

_string = string

? strcpy(new char [strlen(string)+1], string)

: 0;

}

This code is quite simple. If a valid string—a non-null string— was stored, it is destroyed. If the parameter is valid—non-null—a copy of this string is made and stored. If the parameter is not valid, the data member is simply reset to 0.

At this stage, our class can store and retrieve both an integer and a character-string value.

Defining the Persistence and Copy Constructors

For our named property to be complete, we have to add the class-level information and persistence-related member functions. The easiest way to do this is to use the following two macros in the body of the class declaration:

  • DeclarePropertyInfo declares the class information data members for the MyProperty class. These members are used to retrieve information, such as the class name and its hierarchy. It also declares the member functions that are required to implement persistence for this class.

  • DeclarePropertyIOConstructors declares the constructors required for persistence and copy.

These macros make it very easy to add copy and persistence functionality to your class.

Once the macros have been declared, all we have to do to add copy and persistence features to our class is to define a copy constructor and a constructor that take an IlvInputFile reference as its parameter:

MyProperty::MyProperty(const MyProperty& source)

: IlvNamedProperty(GetSymbol()),

_integer(source._integer),

_string(0)

{

setString(source._string);

}

 

MyProperty::MyProperty(IlvInputFile& i, IlSymbol* s)

: IlvNamedProperty(GetSymbol()),

_integer(0),

_string(0)

{

// 's' should be equal to GetSymbol()

i.getStream() >> _integer >> IlvQuotedString();

setString(IlvQuotedString().Buffer);

}

The first constructor initializes a new instance of MyProperty with a copy of its source parameter.

The second constructor reads the provided input stream to initialize its instance with what is read.

Defining the write Member Function

Now that we are able to read a new instance of the class, we can save it. To do so, we have to define a write member function, which is implicitly declared by the macro DeclarePropertyInfo.

void

MyProperty::write(IlvOutputFile& o) const

{

    o.getStream() << _integer << IlvSpc() << IlvQuotedString(_string);

}

The saving order must be the same as the reading order.

Note that you may define a named property that does not have any additional information to save. In this case, you would use the DeclarePropertyInfoRO macro instead of DeclarePropertyInfo in the class declaration and drop the write member function that would be useless.

Providing an Entry Point to the Read and Copy Constructors

To provide Views with an entry point to the read and copy constructors, you must add another macro to the implementation file, outside the body of any function, as follows:

IlvPredefinedPropertyIOMembers(MyProperty)

Calling this macro actually creates a read static member function that invokes the read constructor. It also defines a copy member function that calls the copy constructor.

Registering the Class

The final step to have our named property up and running in our application is to register the MyProperty class with Views as follows:

IlvRegisterPropertyClass(MyProperty, IlvNamedProperty);

Calling the macro IlvRegisterPropertyClass registers the class MyProperty with the Views persistence mechanism.

Using the New Named Property

You can now use this new named property as an extension of any graphic object with which it would be associated:

IlvGraphic* myObject = ...;

myObject->setNamedProperty(new MyProperty(12, "Some text"));

...

MyProperty* property =

          (MyProperty*)(myObject->getNamedProperty(MyProperty::GetSymbol());

if (property && (property->getInteger() == someValue))

    doSomething();

You have extended your graphic object’s API in a persistent manner, without subclassing the base class.