LuciadLightspeed application objects can change during their lifetime. Not only does the object itself change, in most cases the change also affects other application objects. This article describes how application objects can change, how LuciadLightspeed handles these changes automatically, and in which case the program needs to take care of these changes. Finally, the LuciadLightspeed multi-threading fundamentals to handle those changes are discussed.

For information about listening for changes in a data model, see Listening for changes in a model.

Modifying application objects

A LuciadLightspeed application object can change in many ways. Model data, for example, can change directly when the model receives its data from a real-time data stream through a radar feed. In order to visualize the updated model data in the view, the associated layer objects, painter objects, and view objects need to be updated as well. Model data can also change indirectly, for example when a user interacts with the view and moves objects or changes their shape. To visualize the changed objects and save the changes to the model, the model data and its associated layer, painter, and view objects need to change accordingly.

Changing application objects is typically a three-step process:

  1. Change: An action object calls a method on an application object and changes that object.

  2. Notification: The application objects that are interested in the changed object are notified of the change. There are two notification mechanisms as described in Notifying objects of changes.

  3. Action: Each notified object optionally performs an action as a result of the change. This action itself can change another application object, making this the first step of a new change.

Notifying objects of changes

There are two ways to notify application objects of changes in another application object. The applicable notification mechanism depends on the ability of the changed object to register listeners to it. A listener is an application object that registers to another application object to get notified of a change in the object it has been registered to. The two notification mechanisms are:

  1. Notifying objects of changes with listeners. In this case, the changed object automatically fires events to notify its listeners of the change. This mechanism is based on the so called Observer pattern and is applied to most interfaces and classes of LuciadLightspeed . Notifying objects of changes with listeners describes the usage of listeners in detail.

  2. Notifying objects of changes programmatically. In this case, the object that executed the change (the action object) needs to notify other application objects of the change. The use cases for this scenario are limited and are listed in Notifying objects of changes programmatically.

Notifying objects of changes with listeners

The generally applied scenario for notifying application objects of changes in another application object, is based on the Observer (or Listener) pattern. According to this pattern, one or more application objects (listeners or observers) can register to a single application object (subject). When the subject changes, it automatically fires an event to its listeners with a notification of the change. The listeners itself have the ability to receive events and react to it. This mechanism is also referred to as the publish-subscribe interaction. Almost any of the most commonly used LuciadLightspeed interfaces and classes provide methods to add and remove listeners. The limited use cases in which an application object does not have the ability to register listeners are listed in Notifying objects of changes programmatically.

LuciadLightspeed provides a set of listener interfaces for the handling of changes for specific types of application objects. For example, to listen to changes in an ILcdSelection object, the interface ILcdSelectionListener is used as described in Listening to changes in a selection. Similar interfaces are available for ILcdModel and ILcdLayered objects, and other. In many use cases LuciadLightspeed uses a Property Change Listener that listens to changes of specific properties of application objects. The following sections provide more information on selection listeners, model listeners, and Property Change Listeners.

Listening to changes in a selection

This section describes how to listen to changes in a selection. An ILcdSelection is a container for objects that are selected from a model that is wrapped in a layer. ILcdSelection provides methods to add and remove ILcdSelectionListener objects. Whenever the selection state of one of the objects in an ILcdSelection changes, the selection automatically notifies its registered listeners of the change by calling the method selectionChanged. The parameter of selectionChanged is TLcdSelectionChangedEvent, which contains both the selection that fired the event as the changes in the selection. It also provides the following methods to retrieve the objects in the selection that have changed:

  • selectedElements: returns all objects for which the selection state has changed from deselected to selected

  • deselectedElements: returns all objects for which the selection state has changed from selected to deselected

  • elements: returns all objects for which the selection state has changed

selection handling
Figure 1. Listening to changes in a selection

Additionally you can retrieve more information about the change in selection of a given object by using the method retrieveChange. Refer to the API reference for a detailed description of ILcdSelection, ILcdSelectionListener, and TLcdSelectionChangedEvent.

Program: Listening to changes in selection of the ILcdLayer objects of a view shows how to create a selection listener that listens to changes in a layer. The layer selection listener is added to the view and listens to selection changes in each of the layers that are added to the view.

Program: Listening to changes in selection of the ILcdLayer objects of a view`)
view.addLayerSelectionListener(
    (ILcdSelectionListener<Object>) aSelectionEvent -> {
      ILcdLayer layer = (ILcdLayer) aSelectionEvent.getSelection();
      if (layer.getSelectionCount() == 1) {
        TLcdDomainObjectContext objectContext = new TLcdDomainObjectContext(
            layer.selectedObjects().nextElement(), layer.getModel(), layer, view);
        manager.setBalloonDescriptor(new TLcdModelElementBalloonDescriptor(objectContext));
      } else {
        manager.setBalloonDescriptor(null);
      }
    });

Listening to property changes

A specific use case of the Observer pattern is the use of a java.beans.PropertyChangeListener which listens to changes in a Java bean property. This kind of property is a named attribute of a Java bean that you can read using a get method and that you can change using a similarly named set method. The name of the property is derived from the get/set method pair. It is the name that would be used for the matching private field, according to the Java naming conventions: drop the get or set, and start the name with lower case. For example, for the methods getScale and setScale, the returned property name is scale (in lower case). In the case of the methods setGXYController and getGXYController, the returned property name is GXYController. As there are multiple consecutive capitals in the returned name, all capitals are kept.

Whenever a property of an application object changes, the changed object notifies its Property Change Listeners using the propertyChanged method with a PropertyChangeEvent as parameter. Program: Using a PropertyChangeListener shows how to create a Property Change Listener (ScalePropertyChangeListener) that listens to changes in the return value of ILcdGXYView.getXYWorldReference. It thus listens to the property that is named XYWorldReference. The method getNewValue is used to retrieve the new value of the changed property which is then identical to ILcdGXYView.getXYWorldReference.

Program: Using a PropertyChangeListener (from samples/common/gxy/GXYScaleSupport)
private static class ScalePropertyChangeListener implements PropertyChangeListener {

  @Override
  public void propertyChange(PropertyChangeEvent evt) {
    if ("XYWorldReference".equals(evt.getPropertyName())) {
      ILcdGXYView map = (ILcdGXYView) evt.getSource();
      ILcdXYWorldReference oldWorldReference = (ILcdXYWorldReference) evt.getOldValue();
      ILcdXYWorldReference newWorldReference = (ILcdXYWorldReference) evt.getNewValue();
      if (oldWorldReference != null && newWorldReference != null) {
        map.setMinScale(metersToWorldUnits(worldUnitsToMeters(map.getMinScale(), oldWorldReference), newWorldReference));
        map.setMaxScale(metersToWorldUnits(worldUnitsToMeters(map.getMaxScale(), oldWorldReference), newWorldReference));
      }
    }
  }
}

Finally, the (ScalePropertyChangeListener) is added to the view as shown in Program: Adding a PropertyChangeListener to the view.

Program: Adding a PropertyChangeListener to the view (from samples/common/gxy/GXYScaleSupport)
aGXYView.addPropertyChangeListener(new ScalePropertyChangeListener());

Notifying objects of changes programmatically

Because of efficiency reasons it is in some cases not feasible to register listeners and fire events for every possible change that can occur in an application object. In these cases, the application object that executes the change (the action object), needs to notify other application objects of the change. In the following common use cases a method is required to notify other application objects that a change has occurred:

  • Changing a domain object. When changing a domain object from a model, the action object needs to notify the model of the change using the method elementChanged. Changing domain objects describes this common use case in more detail.

  • Changing indirect layer properties. This applies to changing properties that affect a layer indirectly. In this case, the action object needs to use the method TLcdLayer.invalidate to notify the layer directly of the change or notify the view of the change in the layer. Changing indirect layer properties describes this use case in more detail.

There a few other, advanced use cases which are mentioned in the advanced parts of this guide. In all other cases, listeners are used as described in Notifying objects of changes with listeners. The sections below provide more information on changing domain objects and changing indirect layer properties.

Changing domain objects

When a domain object changes, the model that contains the object is not aware of this change until it gets notified. To notify a model of changes in one of its domain objects, use the method elementChanged. The model can then, in its turn, notify registered ILcdModelListener instances of the change by firing events.

Whenever objects are added to or removed from a model, or when a model is notified of a change in one of its objects, the firing event mode ILcdFireEventMode has to be given to the model as well. There are three modes for firing events:

  • ILcdFireEventMode.FIRE_NOW: to send out the event immediately.

  • ILcdFireEventMode.FIRE_LATER: to collect the event and send out an event later. After all changes have been made, the method fireCollectedModelChanges should be called on an ILcdModel so that the model can fire one event for the collected changes. This mode is useful in case of bulk changes to a model.

  • ILcdFireEventMode.NO_EVENT: to not send an event at all. This mode is mostly used when adding objects to a model that has just been created.

See the Working with models tutorial for examples on how the model gets notified that an object has changed and how the model is instructed to wait with firing an event to its listeners.

Changing indirect layer properties

This section applies to GXY Views only.

When changing one of the layer properties on an ILcdGXYLayer, for example using setVisible or setLabeled, the view automatically gets notified of the change. But when you change properties that indirectly affect a layer, for example the properties of an associated painter, you need to use the invalidate method to notify the layer of the change. You can also notify the view of the fact that one of its layers has changed using the invalidateGXYLayer method.

Program: The color map of a layer has changed and the view is notified of the change so it can repaint the layer shows how a new operator chain is set to an image painter to reflect a change in color style. The painter is responsible for painting the model used by the layer. The last line of the code snippet shows how the view gets notified that one of its layers needs a repaint because the underlying painter has changed.

Program: The color map of a layer has changed and the view is notified of the change so it can repaint the layer (from samples/gxy/colormap/LayerColorCustomizerPanel)
painter.setOperatorChain(new ColorMapOperatorChain(aColorMap));

// Notify the view that the layer needs a repaint
aView.invalidateGXYLayer(aImagePainterLayerSFCT, true,
                         "LayerColorCustomizerPanel#installColorMapOnLayerSFCT", "Changed color model");