What observable collections in JavaFX are
How to observe observable collections for invalidations and changes
How to use observable collections as properties
What Are Observable Collections?
An observable list
An observable set
An observable map
ObservableList
ObservableSet
ObservableMap
They support invalidation notifications as they are inherited from the Observable interface.
They support change notifications. You can register change listeners to them, which are notified when their contents change.
The javafx.collections.FXCollections class is a utility class to work with JavaFX collections. It consists of all static methods.
JavaFX does not expose the implementation classes of observable lists, sets, and maps. You need to use one of the factory methods in the FXCollections class to create objects of the ObservableList, ObservableSet, and ObservableMap interfaces.
In simple terms, an observable collection in JavaFX is a list, set, or map that may be observed for invalidation and content changes.
Understanding ObservableList
The methods filtered() and sorted() are missing in the diagram. You can use them for filtering and sorting the list elements. For details, see the API documentation.
The addListener() and removeListener() methods in the ObservableList interface allow you to add and remove ListChangeListeners, respectively. Other methods perform operations on the list, which affect multiple elements.
If you want to receive notifications when changes occur in an ObservableList, you need to add a ListChangeListener interface whose onChanged() method is called when a change occurs in the list. The Change class is a static inner class of the ListChangeListener interface. A Change object contains a report of the changes in an ObservableList. It is passed to the onChanged() method of the ListChangeListener. I will discuss list change listeners in detail later in this section.
void addListener(InvalidationListener listener)
void removeListener(InvalidationListener listener)
Note that an ObservableList contains all of the methods of the List interface as it inherits them from the List interface.
The JavaFX library provides two classes named FilteredList and SortedList that are in the javafx.collections.transformation package. A FilteredList is an ObservableList that filters its contents using a specified Predicate. A SortedList sorts its contents. I will not discuss these classes in this chapter. All discussions of observable lists apply to the objects of these classes as well.
Creating an ObservableList
<E> ObservableList<E> emptyObservableList()
<E> ObservableList<E> observableArrayList()
<E> ObservableList<E> observableArrayList(Collection<? extends E> col)
<E> ObservableList<E> observableArrayList(E... items)
<E> ObservableList<E> observableList(List<E> list)
<E> ObservableList<E> observableArrayList(Callback<E, Observable[]> extractor)
<E> ObservableList<E> observableList(List<E> list, Callback<E, Observable[]> extractor)
The observableArrayList() method creates an ObservableList backed by an ArrayList. Other variants of this method create an ObservableList whose initial elements can be specified in a Collection as a list of items or as a List.
The last two methods in the preceding list create an ObservableList whose elements can be observed for updates. They take an extractor, which is an instance of the Callback<E, Observable[]> interface. An extractor is used to get the list of Observable values to observe for updates. I will cover the use of these two methods in the “Observing an ObservableList for Updates” section.
Creating and Manipulating Observable Lists
Observing an ObservableList for Invalidations
You can add invalidation listeners to an ObservableList as you do to any Observable. Listing 3-2 shows how to use an invalidation listener with an ObservableList.
In the case of the ObservableList, the invalidation listeners are notified for every change in the list, irrespective of the type of a change.
Testing Invalidation Notifications for an ObservableList
Observing an ObservableList for Changes
Observing an ObservableList for changes is a bit tricky. There could be several kinds of changes to a list. Some of the changes could be exclusive, whereas some can occur along with other changes. Elements of a list can be permutated, updated, replaced, added, and removed. You need to be patient in learning this topic because I will cover it in bits and pieces.
Detecting Changes in an ObservableList
Understanding the ListChangeListener.Change Class
Methods in the ListChangeListener.Change Class
Method | Category |
---|---|
ObservableList<E> getList() | General |
boolean next() void reset() | Cursor movement |
boolean wasAdded() boolean wasRemoved() boolean wasReplaced() boolean wasPermutated() boolean wasUpdated() | Change type |
int getFrom() int getTo() | Affected range |
int getAddedSize() List<E> getAddedSubList() | Addition |
List<E> getRemoved() int getRemovedSize() | Removal |
int getPermutation(int oldIndex) | Permutation |
In this code, the change listener will be notified twice: once for the addAll() method call and once for the removeAll() method call. The ListChangeListener.Change object reports the affected range of indexes. In the second change, you remove two elements that fall into two different ranges of indexes. Note that there is an element "two" between the two removed elements. In the second case, the Change object will contain a report of two changes. The first change will contain the information that, at index 0, the element "one" has been removed. Now, the list contains only two elements with the index 0 for the element "two" and index 1 for the element "three". The second change will contain the information that, at index 1, the element "three" has been removed.
In the change type category, methods report whether a specific type of change has occurred. The wasAdded() method returns true if elements were added. The wasRemoved() method returns true if elements were removed. The wasReplaced() method returns true if elements were replaced. You can think of a replacement as a removal followed by an addition at the same index. If wasReplaced() returns true, both wasRemoved() and wasAdded() return true as well. The wasPermutated() method returns true if elements of a list were permutated (i.e., reordered) but not removed, added, or updated. The wasUpdated() method returns true if elements of a list were updated.
In the affected range type category, the getFrom() and getTo() methods report the range of indexes affected by a change. The getFrom() method returns the beginning index, and the getTo() method returns the ending index plus one. If the wasPermutated() method returns true, the range includes the elements that were permutated. If the wasUpdated() method returns true, the range includes the elements that were updated. If the wasAdded() method returns true, the range includes the elements that were added. If the wasRemoved() method returns true and the wasAdded() method returns false, the getFrom() and getTo() methods return the same number—the index where the removed elements were placed in the list.
The getAddedSize() method returns the number of elements added. The getAddedSubList() method returns a list that contains the elements added. The getRemovedSize() method returns the number of elements removed. The getRemoved() method returns an immutable list of removed or replaced elements. The getPermutation(int oldIndex) method returns the new index of an element after permutation. For example, if an element at index 2 moves to index 5 during a permutation, the getPermutation(2) will return 5.
This completes the discussion about the methods of the ListChangeListener.Change class. However, you are not done with this class yet! I still need to discuss how to use these methods in actual situations, for example, when elements of a list are updated. I will cover handling updates to elements of a list in the next section. I will finish this topic with an example that covers everything that was discussed.
Observing an ObservableList for Updates
<E> ObservableList<E> observableArrayList(Callback<E, Observable[]> extractor)
<E> ObservableList<E> observableList(List<E> list, Callback<E, Observable[]> extractor)
The Callback<P,R> interface is used in situations where further action is required by APIs at a later suitable time. The first generic type parameter specifies the type of the parameter passed to the call() method, and the second one specifies the return type of the call() method.
If you notice the declaration of the type parameters in Callback<E,Observable[]>, the first type parameter is E, which is the type of the elements of the list. The second parameter is an array of Observable. When you add an element to the list, the call() method of the Callback object is called. The added element is passed to the call() method as an argument. You are supposed to return an array of Observable from the call() method. If any of the elements in the returned Observable array changes, listeners will be notified of an “update” change for the element of the list for which the call() method had returned the Observable array.
Let’s examine why you need a Callback object and an Observable array to detect updates to elements of a list. A list stores references of its elements. Its elements can be updated using their references from anywhere in the program. A list does not know that its elements are being updated from somewhere else. It needs to know the list of Observable objects, where a change to any of them may be considered an update to its elements. The call() method of the Callback object fulfills this requirement. The list passes every element to the call() method. The call() method returns an array of Observable. The list watches for any changes to the elements of the Observable array. When it detects a change, it notifies its change listeners that its element associated with the Observable array has been updated. The reason this parameter is named extractor is that it extracts an array of Observable for an element of a list.
Observing a List for Updates of Its Elements
The main() method of the ListUpdateTest class creates an extractor that is an object of the Callback<IntegerProperty, Observable[]> interface. The call() method takes an IntegerProperty argument and returns the same by wrapping it in an Observable array. It also prints the object that is passed to it.
The extractor is used to create an ObservableList. Two IntegerProperty objects are added to the list. When the objects are being added, the call() method of the extractor is called with the object being added as its argument. This is evident from the output. The call() method returns the object being added. This means that the list will watch for any changes to the object (the IntegerProperty) and notify its change listeners of the same.
A change listener is added to the list. It handles only updates to the list. At the end, you change the value for the first element of the list from 10 to 100 to trigger an update change notification.
A Complete Example of Observing an ObservableList for Changes
This section provides a complete example that shows how to handle the different kinds of changes to an ObservableList.
A Person Class with Two Properties Named firstName and lastName
A Change Listener for an ObservableList of Person Objects
The ListChangeTest class , as shown in Listing 3-7, is a test class. It creates an ObservableList with an extractor. The extractor returns an array of firstName and lastName properties of a Person object. That means when one of these properties is changed, a Person object as an element of the list is considered updated, and an update notification will be sent to all change listeners. It adds a change listener to the list. Finally, it makes several kinds of changes to the list to trigger change notifications. The details of a change notification are printed on the standard output.
Testing an ObservableList of Person Objects for All Types of Changes
Understanding ObservableSet
void addListener(SetChangeListener<? super E> listener)
void removeListener(SetChangeListener<? super E> listener)
An instance of the SetChangeListener interface listens for changes in an ObservableSet. It declares a static inner class named Change, which represents a report of changes in an ObservableSet.
A set is an unordered collection. This section shows the elements of several sets in outputs. You may get a different output showing the elements of sets in a different order than shown in those examples.
Creating an ObservableSet
<E> ObservableSet<E> observableSet(E... elements)
<E> ObservableSet<E> observableSet(Set<E> set)
<E> ObservableSet<E> emptyObservableSet()
Since working with observable sets does not differ much from working with observable lists, we do not further investigate on this topic. You can consult the API documentation and the example classes in the com.jdojo.collections package to learn more about observable sets.
Understanding ObservableMap
void addListener(MapChangeListener<? super K, ? super V> listener)
void removeListener(MapChangeListener<? super K, ? super V> listener)
An instance of the MapChangeListener interface listens for changes in an ObservableMap. It declares a static inner class named Change, which represents a report of changes in an ObservableMap.
Creating an ObservableMap
<K,V> ObservableMap<K, V> observableHashMap()
<K,V> ObservableMap<K, V> observableMap(Map<K, V> map)
<K,V> ObservableMap<K,V> emptyObservableMap()
Creating ObservableMaps
Since working with observable maps does not differ much from working with observable lists and sets, we do not further investigate on this topic. You can consult the API documentation and the example classes in the com.jdojo.collections package to learn more about observable maps.
Properties and Bindings for JavaFX Collections
The ObservableList, ObservableSet, and ObservableMap collections can be exposed as Property objects. They also support bindings using high-level and low-level binding APIs. Property objects representing single values were discussed in Chapter 2. Make sure you have read that chapter before proceeding in this section.
Understanding ObservableList Property and Binding
SimpleListProperty()
SimpleListProperty(ObservableList<E> initialValue)
SimpleListProperty(Object bean, String name)
SimpleListProperty(Object bean, String name, ObservableList<E> initialValue)
Operations performed on a ListProperty that wraps a null reference are treated as if the operations were performed on an immutable empty ObservableList.
Observing a ListProperty for Changes
An InvalidationListener
A ChangeListener
A ListChangeListener
All three listeners are notified when the reference of the ObservableList, which is wrapped in the ListProperty, changes or the content of the ObservableList changes. When the content of the list changes, the changed() method of ChangeListeners receives the reference to the same list as the old and new value. If the wrapped reference of the ObservableList is replaced with a new one, this method receives references of the old list and the new list. To handle the list change events, please refer to the “Observing an ObservableList for Changes” section in this chapter.
Adding Invalidation, Change, and List Change Listeners to a ListProperty
Binding the size and empty Properties of a ListProperty
A ListProperty exposes two properties, size and empty, which are of type ReadOnlyIntegerProperty and ReadOnlyBooleanProperty, respectively. You can access them using the sizeProperty() and emptyProperty() methods. The size and empty properties are useful for binding in GUI applications. For example, the model in a GUI application may be backed by a ListProperty, and you can bind these properties to the text property of a label on the screen. When the data changes in the model, the label will be updated automatically through binding. The size and empty properties are declared in the ListExpression class.
Using the size and empty Properties of a ListProperty Object
Binding to List Properties and Content
Binding the reference of the ObservableList that it wraps
Binding the content of the ObservableList that it wraps
Binding the References of List Properties
The bindContent() and bindContentBidirectional() methods let you bind the content of the ObservableList that is wrapped in a ListProperty to the content of another ObservableList in one direction and both directions, respectively. Make sure to use the corresponding methods, unbindContent() and unbindContentBidirectional(), to unbind contents of two observable lists.
You can also use methods of the Bindings class to create bindings for references and contents of observable lists.
Binding Contents of List Properties
Binding to Elements of a List
ListProperty provides so many useful features that I can keep discussing this topic for at least 50 more pages! I will wrap this topic up with one more example.
ObjectBinding<E> valueAt(int index)
ObjectBinding<E> valueAt(ObservableIntegerValue index)
The first version of the method creates an ObjectBinding to an element in the list at a specific index. The second version of the method takes an index as an argument, which is an ObservableIntegerValue that can change over time. When the bound index in the valueAt() method is outside the list range, the ObjectBinding contains null.
Binding to the Elements of a List
Understanding ObservableSet Property and Binding
The class diagram for the SetProperty class is similar to the one shown in Figure 3-5 for the ListProperty class. You need to replace the word “List” with the word “Set” in all names.
The SetExpression and Bindings classes contain methods to support high-level bindings for set properties. You need to subclass the SetBinding class to create low-level bindings.
Like the ListProperty, the SetProperty exposes the size and empty properties .
Like the ListProperty, the SetProperty supports bindings of the reference and the content of the ObservableSet that it wraps.
Like the ListProperty, the SetProperty supports three types of notifications: invalidation notifications, change notifications, and set change notifications.
Unlike a list, a set is an unordered collection of items. Its elements do not have indexes. It does not support binding to its specific elements. Therefore, the SetExpression class does not contain a method like valueAt() as the ListExpression class does.
SimpleSetProperty()
SimpleSetProperty(ObservableSet<E> initialValue)
SimpleSetProperty(Object bean, String name)
SimpleSetProperty(Object bean, String name, ObservableSet<E> initialValue)
Using Properties and Bindings for Observable Sets
Understanding ObservableMap Property and Binding
The class diagram for the MapProperty class is similar to the one shown in Figure 3-5 for the ListProperty class. You need to replace the word “List” with the word “Map” in all names and the generic type parameter <E> with <K, V>, where K and V stand for the key type and value type, respectively, of entries in the map.
The MapExpression and Bindings classes contain methods to support high-level bindings for map properties. You need to subclass the MapBinding class to create low-level bindings.
Like the ListProperty, the MapProperty exposes size and empty properties .
Like the ListProperty, the MapProperty supports bindings of the reference and the content of the ObservableMap that it wraps.
Like the ListProperty, the MapProperty supports three types of notifications: invalidation notifications, change notifications, and map change notifications.
The MapProperty supports binding to the value of a specific key using its valueAt() method.
SimpleMapProperty()
SimpleMapProperty(Object bean, String name)
SimpleMapProperty(Object bean, String name, ObservableMap<K,V> initialValue)
SimpleMapProperty(ObservableMap<K,V> initialValue)
Using Properties and Bindings for Observable Maps
Summary
JavaFX extends the collections framework in Java by adding support for observable lists, sets, and maps that are called observable collections. An observable collection is a list, set, or map that may be observed for invalidation and content changes. Instances of the ObservableList, ObservableSet, and ObservableMap interfaces in the javafx.collections package represent observable interfaces in JavaFX. You can add invalidation and change listeners to instances of these observable collections.
The FXCollections class is a utility class to work with JavaFX collections. It consists of all static methods. JavaFX does not expose the implementation classes of observable lists, sets, and maps. You need to use one of the factory methods in the FXCollections class to create objects of the ObservableList, ObservableSet, and ObservableMap interfaces.
The JavaFX library provides two classes named FilteredList and SortedList that are in the javafx.collections.transformation package. A FilteredList is an ObservableList that filters its contents using a specified Predicate. A SortedList sorts its contents.
The next chapter will discuss how to create and customize stages in JavaFX applications.