All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Pages
Observers

Overview

The Qtilities::Core::Observer class provides a powerful implementation of the subject - observer programming pattern. The observer class is the observer in this implementation (as the name suggests) and any QObject based class can be a subject. One way to think about an observer is to think of it as a context in your application to which certain subjects can be attached or detached. An example of such a context is the observer which manages plugins in the Qtilities extension system. When plugins are loaded they are attached to this context and become visible in the list of loaded plugins. Another example would be different instances of a scripting engine. When new objects are created inside the scripting engine they exist in that context. It can then for example be possible to have multiple scripting engines within the same application, where different objects belongs to different script engines or are shared between the engines using Observers.

The Qtilities implementation of this pattern is a very powerful addition to any Qt programmer's toolbox and this article will explain the many different features of the implementation.

Table of contents:

First Steps

Overviews

Introduction through simple examples

This section will introduce basic observer operations through blocks of example code. It is important to understand these example blocks of code before moving on to the sections that follows.

First steps: Creating an observer contexts and attaching objects to it

This example shows how to create your first observer class and attach objects to it.

#include <QtilitiesCore>
using namespace QtilitiesCore;
// Create the observer
Observer* observerA = new Observer;
// Create the objects
QPointer<QObject> object1 = new QObject;
QPointer<QObject> object2 = new QObject;
// Attach objects to observers
observerA->attachSubject(object1);
observerA << object2;

Object sharing: Attaching an object to multiple contexts

A very powerful feature of the way observers handle attached subjects is that they can be attached to multiple observers. We can append the following to the above example:

// Create another observer
Observer* observerB = new Observer;
// Attach objects to observers
observerB->attachSubject(object1);
observerB->attachSubject(object2);
// Get the references to all children attached to an observer
QList<QObject*> children_B = observerB->subjectReferences();
// Get the number of children
int number_children_B = observerB->subjectCount();
// Get the number of parents
int parent_count_1 = Observer::parentCount(object1);
// Get the references of any objects' parents
QList<Observer*> parents_object1 = Observer::parentReferences(object1);

The above code example also shows how to get the references to an object's parents, and how to get the number of subjects inside an observer context along with and their references.

Observer & Subject IDs: Accessing objects using unique IDs

Every observer created in your application will have an unique integer ID associated with it. This value is assigned to the observer by the object manager in its constructor. The ID of an observer can be accessed using the Qtilities::Core::Observer::observerID() function. It is also possible to access any observer instance through the Qtilities::Core::ObjectManager::observerReference() function.

When an object is attached to one or more observers it is assigned an unique ID within each context. The ID is stored on the object using the Qtilities::Core::Properties::qti_prop_OBSERVER_MAP dynamic property. The Dynamic properties used and managed by Observers section explores dynamic properties in detail.

Object categories: Organizing objects into categories inside a context

When objects should be managed into categories inside an observer it can be done by setting the Qtilities::Core::Properties::qti_prop_CATEGORY_MAP property on an object before attaching it to an observer which has its Qtilities::Core::Observer::HierarchicalDisplay hint set to categorized hierarchy. When viewing such an observer using the Qtilities::CoreGui::ObserverWidget class it will show the objects under such observers using the categories specified on the objects.

For examples of how observer widgets visualize categories, see the Hierarchical (categorized) display options section of the Observer Widgets article.

Object lifetimes: Managing object lifetimes using observers

When attaching objects to observers, it is possible to specify the way the object should be managed by the observer context. The possible management options are defined in the Qtilities::Core::Observer::ObjectOwnership enumeration. The ownership of any object observed by one or more observers is stored using the Qtilities::Core::Properties::qti_prop_OWNERSHIP property.

Dynamic properties used and managed by Observers

Observer classes use the dynamic property features of QObject classes extensively to manage objects in contexts. This section will explain how to use these properties, which properties exists and how property changes can be monitored. The sections to follow will explain the different properties which is used in Qtilities, however new custom properties can easily be created and added to objects since the observer property classes are basically wrappers around QVariant.

The object manager provides the Qtilities::Core::ObjectManager::propertyExists() function which can be used to check if a property exists on an object.

Different property types

At present, two types of properties are used:

Getting and setting of properties are done by the Qtilities::Core::Observer class which provides easy to use functions to set and get properties and their values. For shared properties it is easy to get and set the value of the property because we do not need to know the context since the value is the same for all contexts. For normal observer properties we need to know the context since the value is different for different contexts. See the respective property class documentation for more information about setting and getting different properties.

Important properties and how they are interpreted

As mentioned earlier in this section, Qtilities uses a number of properties internally to manage objects in different contexts. All these properties are defined and documented in the Qtilities::Core::Properties namespace.

Some of these properties are reserved and cannot be changed (see the Permission section of each property's description) by the developer. These properties should not be modified, and if an attempt is made to modify them the property change event will be filtered by the observer and the property will stay unchanged. A list of reserved properties can be obtained through the Qtilities::Core::Observer::reservedProperties() method and the Qtilities::Core::Observer::propertyChangeFiltered() signal will be emitted when a property change was filtered.

Other properties can be set by the developer before attachment to observers to provide information to the observer about how the object must be treated. The documentation of the different properties provides more details.

Monitoring property changes and changing properties

A powerful feature of the observer and observer property combination is that changes to properties are detected by observers and handled accordingly. A list of the monitored properties for a specific context can be obtained using the Qtilities::Core::Observer::monitoredProperties() function. This function also takes into account the monitored properties of all subject filters installed in the context.

To change a property is easy, but care should be taken when changing properties which are different between contexts:

// First check if the property exists. If it does we need to change only the context we
// are interested in. If we replace the complete property we will lose the information
// for all other contexts.
if (ObjectManager::propertyExists(obj,qti_prop_CATEGORY_MAP)) {
MultiContextProperty category_property = ObjectManager::getMultiContextProperty(obj,qti_prop_CATEGORY_MAP);
category_property.setValue(qVariantFromValue(NEW_CATEGORY),OBSERVER_ID);
ObjectManager::setMultiContextProperty(obj,category_property);
} else {
MultiContextProperty category_property(qti_prop_CATEGORY_MAP);
category_property.setValue(qVariantFromValue(NEW_CATEGORY),OBSERVER_ID);
ObjectManager::setMultiContextProperty(obj,category_property);
}

If an observer has an activity policy filter installed and the Qtilities::Core::Properties::qti_prop_ACTIVITY_MAP property on an object within the context is changed, the change will be detected by the observer and passed to the activity policy filter. The filter will then validate the activity state of all objects in the context. The way the validation is performed depends on the setup of the activity policy filter. The property change is thus validated by all the attached subject filters and by the observer itself.

For properties which has the "Change Notifications" parameter as Yes in their documentation, notifications are available when property changes were valid in the following ways (always both):

It is easy to catch property change events on an object to which it is delivered. For example:

bool MyObject::eventFilter(QObject *object, QEvent *event) {
if (object == this && event->type() == QEvent::User) {
QtilitiesPropertyChangeEvent* qtilities_event = static_cast<QtilitiesPropertyChangeEvent *> (event);
if (qtilities_event) {
// Lets check for example if the qti_prop_NAME property changed:
if (!qstrcmp(qtilities_event->propertyName().data(),qti_prop_NAME)) {
// Remembering that qti_prop_NAME is managed by a name manager and that
// it is sync'ed with objectName(), we can now get the new name of the object.
QString new_name = objectName();
}
}
}
return false;
}

Subject filters

Subject filters are a feature of the observer architecture which allows control over object attachment and detachment, as well as monitoring of properties introduced by the subject filter.

All subject filters must inherit Qtilities::Core::AbstractSubjectFilter. You can install subject filters using the Qtilities::Core::Observer::installSubjectFilter() function and uninstall subject filters using the Qtilities::Core::Observer::uninstallSubjectFilter() function. A subject filter can only be used in one observer at any time.

Attaching an object to an observer context with subject filters

When attaching an object to an observer context which has subject filters installed, these filters are called during the attachment process to validate the attachment and to make sure all other objects in the context are in the correct state as well after the object was attached. Subject filters normally also add properties to the attached object. The diagram below shows when the Qtilities::Core::AbstractSubjectFilter::initializeAttachment() and Qtilities::Core::AbstractSubjectFilter::finalizeAttachment() functions are called during attachment of an object.

subject_filters_attachment.jpg
Subject Filter Attachment Sequence

Detachment of an object can happen in two ways:

The detachment process is slightly different for the two cases. The first diagram below shows the case where the detach function is called directly.

subject_filters_detachment.jpg
Subject Filter Detachment Sequence

The diagram below shows the case where the object is deleted while attached to the observer. In this case the private slot handle_deletedSubject() on the observer instance is called.

subject_filters_object_deletion.jpg
Subject Filter Detachment Sequence (Object Deletion)

Another important feature of the observer implementation is that subject filters can indicate to the observer in which they are installed which properties must be monitored. This allows subject filters to validate property changes to the properties added to subjects by the filter.

Managing context activity

A common requirement in a context is to manage the activity of objects within the context. The Qtilities::Core::ActivityPolicyFilter class was designed for this purpose and has many different options allowing precise control over object activity within a context. See the class documentation for more information about this filter.

Managing names within a context

Another common requirement in a context is to manage the names of objects within the context. The Qtilities::CoreGui::NamingPolicyFilter class was designed for this purpose and has many different options allowing precise control over object names within a context. See the class documentation for more information about this filter.

Managing object types within a context

From a developers perspective it might be useful to control the type of objects which can be attached to a context. The Qtilities::Core::SubjectTypeFilter class was designed for this purpose and has different filtering options allowing precise control over what types of objects can be attached to a context. See the class documentation for more information about this filter.

Creating custom subject filters

Creating custom subject filters is an easy task if you understand how the initialization and finalization diagrams shown above work. The Qtilities::Core::SubjectFilterTemplate class is included in the Core module and should be used as a starting point when creating new subject filters. The new filter class needs to inherit from Qtilities::Core::AbstractSubjectFilter and re-implement the virtual abstract functions in order to work. The source code of the subject filters which comes as part of Qtilities is a good place to start when looking for examples of filter implementations.

Advanced topics

Context dependency: Creating context aware classes

In some cases it is desirable to have a class which depends on an observer context. For such cases the Qtilities::Core::ObserverAwareBase class was created. Classes can inherit from this class which will add two functions to your class:

It is then possible to access the observer context using the protected variable d_observer. This reference is a QPointer, thus it will detect when the observer context is deleted and will be set to 0.

Observer hints: Define the way your context must be displayed

The observer class provides a concept called observer hints. These hints are called display hints since they guide widgets displaying the context of the observer. The available hints are defined in the Qtilities::Core::ObserverHints class and can be used on any observer by calling the Qtilities::Core::Observer::useDisplayHints() function after the observer was constructed. The Observer Widgets article explores these different hints and shows what their effects are on the visualized observer context.

Batch processing: Optimization using processing cycles

When doing intensive processing on an observer, for example attaching 1000 objects to it, things can get pretty slow since each attachment will emit signals which will cause views connected to the observer to be updated. For this reason the observer class provides the concept of processing cycles.

It is possible to start a processing cycle using the Qtilities::Core::Observer::startProcessingCycle() function. After this function call you can do intensive processing on the observer and when you are done the processing cycle can be finished using the Qtilities::Core::Observer::endProcessingCycle(). After the processing cycle was ended the necessary updates can be done manually. For example if you changed the layout of the observer, you can call the Qtilities::Core::Observer::refreshViewsLayout() function. If you only changed data in the context, you can call the Qtilities::Core::Observer::refreshViewsData() function.

Modification state: Check if an observer changed

The observer class implements the Qtilities::Core::Interfaces::IModificationNotifier interface. Thus it is possible to monitor the notification state of an observer and its subjects. When attaching an object to an observer the attachment function will check if the object implements this interface as well, and if it does the observer will take the object's state into account when reporting on its own modification state. Observers also take any changes to themselves into account, this includes hierarchical structure changes, hints etc.

Observers also provide a number of signals which can be monitored if you are interested in specific changes in an observer context. See the Qtilities::Core::Observer class documentation for more information.

Exporting and importing observers

The observer class implements the Qtilities::Core::Interfaces::IExportable interface making it exportable. When a tree is exported, a recursive search for any objects in the tree implementing this interface occurs and the objects found is part of the exported object.

For more information on this topic, see Saving and loading trees using XML for examples of XML exports on observer structures.

Using observers to build hierarchical trees

Observers has the only requirement that objects attached to them must be QObjects, therefore it is also possible to attach observers to other observers because an observer is a QObject. This makes construction of tree structures using observer very easy. For more information, see the Building Trees article.

Access modes: Define user access on a per context basis

Observers provide access control functionality through the Qtilities::Core::Observer::setAccessMode() and Qtilities::Core::Observer::setAccessModeScope() functions. The possible access modes are provided through the Qtilities::Core::Observer::AccessMode enumeration. Item views and observer functions take the access mode into account when displaying observer contexts or when attempting to modify observers. A convenient function that can be used to check if the access mode of an observer allows the observer to be changed is Qtilities::Core::Observer::isConst(). Access modes can also be set on a category level, for more information see Qtilities::Core::Observer::accessModeScope().

The Access mode and access mode scope section of the Observer Widgets article shows how the observer widget class customizes its display of observer contexts for different access modes.



Qtilities : Reference Documentation Back to top Copyright © 2009-2013, Jaco Naudé