Strange TableView.itemsProperty Behavior? - java

Let's say I'll add a ChangeListener to a TableView's itemsProperty. When would the ChangeListener's changed method be called?
I tried adding to the empty List where the TableView's items points. The result - The ChangeListener's changed method didn't get called.
tableView.itemsProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
System.out.println("Changed!");
}
});
final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
//data.add(new Object()); don't call this yet
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);
However, I also tried adding to an empty List and then let TableView's items point on it. The result - The ChangeListener's changed method got called.
tableView.itemsProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
System.out.println("Changed!");
}
});
final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
data.add(new Object());
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);
I looked it up on http://docs.oracle.com/javafx/2/api/javafx/scene/control/TableView.html#itemsProperty() but it only says "The underlying data model for the TableView. Note that it has a generic type that must match the type of the TableView itself."
I'm asking this because I might miss out on some other important circumstances.

A not fully documented fact (aka: implementation detail) is that ObjectProperty only fires on
!oldValue.equals(newValue); // modulo null checking
That's critical for a list-valued object property, as lists are specified to be equal if all their elements are equal. In particular, all empty lists are equal to each other, thus replacing one empty list by another empty list as in your first snippet will not make the property fire:
// items empty initially
TableView table = new TableView()
table.itemsProperty().addListener(....)
ObservableList empty = FXCollections.observableArrayList();
// replace initial empty list by new empty list
table.setItems(empty);
// no change event was fired!
That's nasty if your code wants to listen to changes of the content - it would need to re-wire the ListChangeListeners whenever the identity of the items value changes but can't with a changeListener because that fires based on equality. BTW, even fx-internal code got that wrong and hot-fixed by a dirty hack
And no nice solution available, just a couple of suboptimal options
use an InvalidationListener instead of a changeListener
bind (unidirectionally!) a ListProperty to the list-valued object property and listen to the latter
use an adapter that combines the above to at least have it out off the way
A code snippet I use:
public static <T> ListProperty<T> listProperty(final Property<ObservableList<T>> property) {
Objects.requireNonNull(property, "property must not be null");
ListProperty<T> adapter = new ListPropertyBase<T>() {
// PENDING JW: need weakListener?
private InvalidationListener hack15793;
{
Bindings.bindBidirectional(this, property);
hack15793 = o -> {
ObservableList<T> newItems =property.getValue();
ObservableList<T> oldItems = get();
// force rewiring to new list if equals
boolean changedEquals = (newItems != null) && (oldItems != null)
&& newItems.equals(oldItems);
if (changedEquals) {
set(newItems);
}
};
property.addListener(hack15793);
}
#Override
public Object getBean() {
return null; // virtual property, no bean
}
#Override
public String getName() {
return property.getName();
}
#Override
protected void finalize() throws Throwable {
try {
Bindings.unbindBidirectional(property, this);
property.removeListener(hack15793);
} finally {
super.finalize();
}
}
};
return adapter;
}

TableView does not have implemented the method add view documentation
My aproach is the following:
To initialize the TableView itemList:
tableX.setItems(itemListX);
you could also initialize it by using the default list of the TableView:
tableX.getItems.addAll(itemListX);
in this case it will copy the list.
And the aproach to add items dynamically:
1-If you still have a reference to itemListX:
itemListX.add(item);
this you will update the TableView since the table observes the ObservableList itemListX.
2-Else, if you dont any more:
tableX.getItems().add(item);

Related

ISIS: Moving from deprecated #Action(invokeOn=...) to #Action(associateWith=...)

I am working on a project using ISIS 1.16.2. I have a superclass, called ConfigurationItem, which has some common properties (name, createdTimestamp etc.).
For example it has a delete action method, annotated with #Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION, ...), which I need to be callable from entitys detail view as well as from collection views with selection boxes.
Example:
public class ConfigurationItem {
#Action(
invokeOn = InvokeOn.OBJECT_AND_COLLECTION,
semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
domainEvent = DeletedDomainEvent.class)
public Object delete() {
repositoryService.remove(this);
return null;
}
// ...
}
public class ConfigurationItems {
#Action(semantics = SemanticsOf.SAFE)
public List<T> listAll() {
return repositoryService.allInstances(<item-subclass>.class);
}
// ...
}
This works pretty well but the "invokeOn" annotation is now deprecated. The JavaDoc says that one should switch to #Action(associateWith="...") but I don't know how to transfer the semantics of 'InvokeOn' since I have no collection field for reference.
Instead I only have the collection of objects returned by the database retrieve action.
My question is: How do I transfer the deprecated #Action(invokeOn=...) semantics to the new #Action(associateWith="...") concept for collection return values with no backed property field?
Thanks in advance!
Good question, this obviously isn't explained well enough in the Apache Isis documentation.
The #Action(invokeOn=InvokeOn.OBJECT_AND_COLLECTION) has always been a bit of a kludge, because it involves invoking an action against a standalone collection (which is to say, the list of object returned from a previous query). We don't like this because there is no "single" object to invoke the action on.
When we implemented that feature, the support for view models was nowhere near as comprehensive as it now is. So, our recommendation now is, rather than returning a bare standalone collection, instead wrap it in a view model which holds the collection.
The view model then gives us a single target to invoke some behaviour on; the idea being that it is the responsibility of the view model to iterate over all selected items and invoke an action on them.
With your code, we can introduce SomeConfigItems as the view model:
#XmlRootElement("configItems")
public class SomeConfigItems {
#lombok.Getter #lombok.Setter
private List<ConfigurationItem> items = new ArrayList<>();
#Action(
associateWith = "items", // associates with the items collection
semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
domainEvent = DeletedDomainEvent.class)
public SomeConfigItems delete(List<ConfigurationItem> items) {
for(ConfigurationItem item: items) {
repositoryService.remove(item);
}
return this;
}
// optionally, select all items for deletion by default
public List<ConfigurationItem> default0Delete() { return getItems(); }
// I don't *think* that a choices method is required, but if present then
// is the potential list of items for the argument
//public List<ConfigurationItem> choices0Delete() { return getItems(); }
}
and then change the ConfigurationItems action to return this view model:
public class ConfigurationItems {
#Action(semantics = SemanticsOf.SAFE)
public SelectedItems listAll() {
List<T> items = repositoryService.allInstances(<item-subclass>.class);
return new SelectedItems(items);
}
}
Now that you have a view model to represent the output, you'll probably find other things you can do with it.
Hope that makes sense!

JavaFX: How to invalidate an Observable object that contains a list of other Observable objects

I'm a bit confused with listeners in javaFX.
I have a class (let's call it ObservedContainer) that implements Observable, which contains a list of some objects that are also Observables:
public class ObservedContainer implements Observable {
ArrayList<AnotherObservableClass> someOtherClasses;
(...observable implementation omitted for brevity)
}
I can invalidate the class when I add or remove something from the array - that's ok:
public class ObservedContainer implements Observable {
ArrayList<AnotherObservableClass> someOtherClasses = new ArrayList<>();
(...observable implementation omitted for brevity)
public void addAnotherObservableClass(AnotherObservableClass a){
someOtherClasses.add(a);
fireInvalidationEvent();
// This just notifies all listeners that the object has changed
}
public void removeAnotherObservableClass(AnotherObservableClass a){
someOtherClasses.remove(a);
fireInvalidationEvent();
}
}
But how do I have it fire the invalidation event when an object in the array gets modified ? Should the ObservedContainer object subscribe as a listener to each AnotherObservableClass object, and react to that ?
Thank you!
There is a class that should handle this for you: ObservableList
The FXCollections.observableArrayList method can be used to trigger update changes on a modification of a property of a element in the list.
Example
ObservableList<Node> list = FXCollections.observableArrayList(node -> new Observable[] { node.translateXProperty() });
Would trigger the listeners, if the translateX property of one of the list's elements is modified.
If you insist on wrapping the list in your own class, you could simply add a listener that triggers the invalidation event of the wrapper:
list.addListener((Observable o) -> fireInvalidationEvent());

How to make an instance of a class delete itself from an array list

In java, I would like to have an instance of a class in an array list delete itself after its main loop is done, not just a random one, but the instance itself. How would I do that?
The remove method in java's List class removes the exact object you insert into it as determined by the '==' operator.
Something like this might work for you:
public interface IDeleteListener<T> {
void delete(SelfDeleteItem<T> item);
}
public class SelfDeleteItem<T> {
IDeleteListener<T> listener;
T item;
public SelfDeleteItem(T item) {
this.item = item;
}
public void fireDelete() {
listener.delete(this);
}
public void setDeleteListener(IDeleteListener<T> listener){
this.listener = listener;
}
}
public class DeleteList<T> {
private List<SelfDeleteItem<T>> items;
IDeleteListener<T> deleteListener;
public DeleteList() {
this.items = new ArrayList<SelfDeleteItem<T>>();
this.deleteListener = new IDeleteListener<T>() {
public void delete(SelfDeleteItem<T> item) {
items.remove(item);
}
};
}
public SelfDeleteItem<T> add(T item) {
SelfDeleteItem<T> test= new SelfDeleteItem<T>(item);
test.setDeleteListener(this.deleteListener);
this.items.add(test);
}
}
An example:
DeleteList<String> list= //...
SelfDeleteItem<String> deleteItem = list.add("test");
deleteItem.fireDelete();
The way this code works, is each time an instance of some value is added to DeleteList, it is placed in a wrapper, SelfDeleteItem. Self delete item has a listener that it notifies when it wants to be deleted. DeleteList registers itself as a listener for when SelfDeleteItem wants to be deleted, and proceeds to delete SelfDeleteItem in that case.
This decouples the list from the item via a listener, and accomplishes your goal of allowing an item to delete itself.
As for uses, this approach is useful for audio and network buffer caches. Being able to cache or uncache an object without creating a dependency is desireable.
If I am understanding your question correctly, you would like to remove a particular instance of an Object that you have added to an ArrayList. If that is the case, at the end of the main method, make a call to:
public boolean remove(Object o)
a method of ArrayList.
Here is how the method works. Removes the first occurrence of the specified element from this list, if it is present. If the list does not contain the element, it is unchanged. More formally, removes the element with the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))) (if such an element exists). Returns true if this list contained the specified element (or equivalently, if this list changed as a result of the call).
The class instance has to know it is in the list. Presumably the class would have a method that's called when when the instance is added to the list. Then when the instance wants to remove itself from the list it does so using the list's "remove" method, something like: "mainList.remove( this );".
Generally you'd want to handle adding and removing the list from outside the class; a class instance should not know or care whether it's in the list. On the other hand, sometimes a class does have to look out for itself and disentangle itself from lists and other connections.

Trouble with CellList empty widget and AsyncDataProvider

I have a CellList that I'm populating with an AsyncDataProvider:
#UiField(provided = true)
CellList<PlayerDataEntity> friendCellList;
#Inject
FriendListViewImpl(FriendListController controller) {
friendCellList = new CellList<PlayerDataEntity>(new PlayerCell());
initWidget(uiBinder.createAndBindUi(this));
// if we don't set the row data to empty before trying to get the real data,
// the empty list widget will never appear. But if we do set this,
// then the real data won't show up.
friendCellList.setRowData(Lists.<PlayerDataEntity>newArrayList());
new AsyncDataProvider<PlayerDataEntity>() {
#Override
protected void onRangeChanged(final HasData<PlayerDataEntity> display) {
rpcService.getPlayers(new AsyncCallback<List<PlayerDataEntity>>() {
#Override
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowData(0, result);
}
});
}
}.addDataDisplay(friendCellList);
friendCellList.setEmptyListWidget(new Label("No friends found"));
}
If I don't initially set the row data to an empty list, then the empty list widget won't show up if the RPC service returns an empty list. However, if I do initially set the row data to the an empty list, and the RPC returns a non-empty list, that data won't show up in the widget.
I must be misunderstanding some aspect of the CellList's API. What am I doing wrong?
If you know, that the total list (not just the list in the range) is empty, then you can set display.setRowCount(0), so the CellList will display "No friends found".
If your service always returns the entire list, this can be done easily like
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowCount(result.size());
display.setRowData(0, result);
}
Add the following line to the method:
#Override
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowData(0, result);
((CellList<PlayerDataEntity>) display).redraw();
}

How to get an EasyMock mock to return an empty list multiple times

I would like an EasyMock mock to be able to expect an empty list multiple times, even when the list that is returned the first time has elements added to it.
Is this possible? As the empty list created in the expectation persists for the whole replay and so retains any elements added to it in between calls.
Here is a code example showing what I'm trying to avoid:
public class FakeTest {
private interface Blah {
public List<String> getStuff();
};
#Test
public void theTest(){
Blah blah = EasyMock.createMock(Blah.class);
//Whenever you call getStuff() an empty list should be returned
EasyMock.expect(blah.getStuff()).andReturn(new ArrayList<String>()).anyTimes();
EasyMock.replay(blah);
//should be an empty list
List<String> returnedList = blah.getStuff();
System.out.println(returnedList);
//add something to the list
returnedList.add("SomeString");
System.out.println(returnedList);
//reinitialise the list with what we hope is an empty list
returnedList = blah.getStuff();
//it still contains the added element
System.out.println(returnedList);
EasyMock.verify(blah);
}
}
You can use andStubReturn to generate a new list each time.
//Whenever you call getStuff() an empty list should be returned
EasyMock.expect(blah.getStuff()).andStubAnswer(new IAnswer<List<String>>() {
#Override
public List<Object> answer() throws Throwable {
return new ArrayList<String>();
}
}

Categories