Binding an ObservableList to contents of two other ObservableLists? - javafx

If I have two separate ObservableLists, and both are put in a single ObservableList for a TableView... is there a way to create a binding between those two ObservableLists and the aggregated one? I tried to mess around and override the calculate() method for the ObjectBinding, but I don't think this is what I want. Any thoughts on how to tackle this?
UPDATE: Going on a related tangent to show an implication discussed below. This is my ObservableImmutableList implementation that is struggling to work with the checked solution.
package com.nield.utilities.fx;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import com.google.common.collect.ImmutableList;
public final class ObservableImmutableList<T> implements ObservableList<T> {
private volatile ImmutableList<T> backingList;
private final CopyOnWriteArrayList<ListChangeListener<? super T>> listeners = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<InvalidationListener> invalidationListeners = new CopyOnWriteArrayList<>();
private final ObjectProperty<ObservableList<T>> property;
private ObservableImmutableList(ImmutableList<T> immutableList) {
this.backingList = immutableList;
this.property = new SimpleObjectProperty<ObservableList<T>>(this);
}
public static <T> ObservableImmutableList<T> of(ImmutableList<T> immutableList) {
return new ObservableImmutableList<T>(immutableList);
}
public void set(ImmutableList<T> immutableList) {
this.property.setValue(this);
final ImmutableList<T> oldList = this.backingList;
final ImmutableList<T> newList = immutableList;
listeners.forEach(l -> l.onChanged(new Change<T>(this) {
private int changeNum = 0;
#Override
public boolean next() {
changeNum++;
return changeNum <= 2 ? true : false;
}
#Override
public boolean wasUpdated() {
return true;
}
#Override
public void reset() {
// TODO Auto-generated method stub
}
#Override
public int getFrom() {
return 0;
}
#Override
public int getTo() {
return changeNum == 1 ? oldList.size() - 1 : newList.size() - 1;
}
#Override
public List<T> getRemoved() {
return changeNum == 1 ? oldList : ImmutableList.of();
}
#Override
public List<T> getAddedSubList() {
return changeNum == 1 ? ImmutableList.of() : newList;
}
#Override
protected int[] getPermutation() {
int[] permutations = new int[changeNum == 1 ? oldList.size() : newList.size()];
for (int i = 0; i < permutations.length; i++) {
permutations[i] = i;
}
return permutations;
}
}));
this.backingList = immutableList;
invalidationListeners.forEach(l -> l.invalidated(this));
}
public ImmutableList<T> get() {
return backingList;
}
public ObjectProperty<ObservableList<T>> asProperty() {
return property;
}
#Override
public int size() {
return backingList.size();
}
#Override
public boolean isEmpty() {
return backingList.isEmpty();
}
#Override
public boolean contains(Object o) {
return backingList.contains(o);
}
#Override
public Iterator<T> iterator() {
return backingList.iterator();
}
#Override
public Object[] toArray() {
return backingList.toArray();
}
#Override
public <B> B[] toArray(B[] a) {
return backingList.toArray(a);
}
#Override #Deprecated
public boolean add(T e) {
return backingList.add(e);
}
#Override #Deprecated
public boolean remove(Object o) {
return backingList.remove(o);
}
#Override
public boolean containsAll(Collection<?> c) {
return backingList.containsAll(c);
}
#Override #Deprecated
public boolean addAll(Collection<? extends T> c) {
return backingList.addAll(c);
}
#Override #Deprecated
public boolean addAll(int index, Collection<? extends T> c) {
return backingList.addAll(index, c);
}
#Override #Deprecated
public boolean removeAll(Collection<?> c) {
return backingList.removeAll(c);
}
#Override #Deprecated
public boolean retainAll(Collection<?> c) {
return backingList.retainAll(c);
}
#Override #Deprecated
public void clear() {
backingList.clear();
}
#Override
public T get(int index) {
return backingList.get(index);
}
#Override #Deprecated
public T set(int index, T element) {
return backingList.set(index, element);
}
#Override #Deprecated
public void add(int index, T element) {
backingList.add(index, element);
}
#Override #Deprecated
public T remove(int index) {
return backingList.remove(index);
}
#Override
public int indexOf(Object o) {
return backingList.indexOf(o);
}
#Override
public int lastIndexOf(Object o) {
return backingList.lastIndexOf(o);
}
#Override
public ListIterator<T> listIterator() {
return backingList.listIterator();
}
#Override
public ListIterator<T> listIterator(int index) {
return backingList.listIterator(index);
}
#Override
public ImmutableList<T> subList(int fromIndex, int toIndex) {
return backingList.subList(fromIndex, toIndex);
}
#Override
public void addListener(InvalidationListener listener) {
invalidationListeners.add(listener);
}
#Override
public void removeListener(InvalidationListener listener) {
invalidationListeners.remove(listener);
}
#Override
public void addListener(ListChangeListener<? super T> listener) {
listeners.add(listener);
}
#Override
public void removeListener(ListChangeListener<? super T> listener) {
listeners.remove(listener);
}
#Override #Deprecated
public boolean addAll(T... elements) {
return backingList.addAll(ImmutableList.copyOf(elements));
}
#Override #Deprecated
public boolean setAll(T... elements) {
return false;
}
#Override #Deprecated
public boolean setAll(Collection<? extends T> col) {
return false;
}
#Override #Deprecated
public boolean removeAll(T... elements) {
return backingList.removeAll(ImmutableList.copyOf(elements));
}
#Override #Deprecated
public boolean retainAll(T... elements) {
return false;
}
#Override #Deprecated
public void remove(int from, int to) {
}
#Override
public String toString() {
return backingList.toString();
}
}
And here is the static factory to merge two ObservableLists together so far. It works for standard implementations of ObservableList, but it is not working with my ObservableImmutableList implementation.
package com.nield.finance;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import com.google.common.collect.ImmutableList;
import com.nield.utilities.fx.ObservableImmutableList;
public class ObservableMerge {
static ObservableImmutableList<String> a = ObservableImmutableList.of(ImmutableList.of("ABQ","DAL"));
static ObservableImmutableList<String> b = ObservableImmutableList.of(ImmutableList.of("HOU","PHX"));
static ObservableList<String> aandb = FXCollections.observableArrayList();
static ObservableList<String> merge(ObservableList<String> into, ObservableList<String>... lists) {
final ObservableList<String> list = into;
for (ObservableList<String> l : lists) {
list.addAll(l);
l.addListener((javafx.collections.ListChangeListener.Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
list.addAll(c.getAddedSubList());
}
if (c.wasRemoved()) {
list.removeAll(c.getRemoved());
}
if (c.wasUpdated()) {
list.removeAll(c.getRemoved());
list.addAll(c.getAddedSubList());
}
}
});
}
return list;
}
public static void main(String...args) {
merge(aandb, a, b);
System.out.println(""+aandb);
a.set(ImmutableList.of("LAX", "BUR"));
System.out.println(""+aandb);
}
}
And here is the static factory to merge two ObservableLists (it should work with the ObservableImmutableList too, but its not.
package com.nield.finance;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import com.google.common.collect.ImmutableList;
import com.nield.utilities.fx.ObservableImmutableList;
public class ObservableMerge {
static ObservableImmutableList<String> a = ObservableImmutableList.of(ImmutableList.of("ABQ","DAL"));
static ObservableImmutableList<String> b = ObservableImmutableList.of(ImmutableList.of("HOU","PHX"));
static ObservableList<String> aandb = FXCollections.observableArrayList();
static ObservableList<String> merge(ObservableList<String> into, ObservableList<String>... lists) {
final ObservableList<String> list = into;
for (ObservableList<String> l : lists) {
list.addAll(l);
l.addListener((javafx.collections.ListChangeListener.Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
list.addAll(c.getAddedSubList());
}
if (c.wasRemoved()) {
list.removeAll(c.getRemoved());
}
if (c.wasUpdated()) {
list.removeAll(c.getRemoved());
list.addAll(c.getAddedSubList());
}
}
});
}
return list;
}
public static void main(String...args) {
merge(aandb, a, b);
System.out.println(""+aandb);
a.set(ImmutableList.of("LAX", "BUR"));
System.out.println(""+aandb);
}
}

This might be a starting point:
public class ObservableMerge {
static ObservableList<String> a = FXCollections.observableArrayList();
static ObservableList<String> b = FXCollections.observableArrayList();
static ObservableList<String> aandb = FXCollections.observableArrayList();
static ObservableList<String> merge(ObservableList<String> into, ObservableList<String>... lists) {
final ObservableList<String> list = into;
for (ObservableList<String> l : lists) {
list.addAll(l);
l.addListener((javafx.collections.ListChangeListener.Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
list.addAll(c.getAddedSubList());
}
if (c.wasRemoved()) {
list.removeAll(c.getRemoved());
}
}
});
}
return list;
}
public static void main(String...args) {
merge(aandb, a, b);
System.out.println(""+aandb);
a.add("Hello");
b.add("Peter");
System.out.println(""+aandb);
}
}

Just thought I'd document this for future readers. RxJavaFX makes push-driven tasks like this MUCH easier.
ObservableList<String> list1 = FXCollections.observableArrayList();
ObservableList<String> list2 = FXCollections.observableArrayList();
ObservableList<String> combinedList = FXCollections.observableArrayList();
Observable.combineLatest(
JavaFxObservable.fromObservableList(list1),
JavaFxObservable.fromObservableList(list2),
(l1,l2) -> {
ArrayList<String> combined = new ArrayList<>();
combined.addAll(l1);
combined.addAll(l2);
return combined;
}).subscribe(combinedList::setAll);
//return unmodifiable version of combinedList
Here's a full working example of this at work:
ObservableList<String> list1 = FXCollections.observableArrayList();
ObservableList<String> list2 = FXCollections.observableArrayList();
ObservableList<String> combinedList = FXCollections.observableArrayList();
Observable.combineLatest(
JavaFxObservable.fromObservableList(list1),
JavaFxObservable.fromObservableList(list2),
(l1,l2) -> {
ArrayList<String> combined = new ArrayList<>();
combined.addAll(l1);
combined.addAll(l2);
return combined;
}).subscribe(combinedList::setAll);
JavaFxObservable.fromObservableList(combinedList).subscribe(System.out::println);
list1.add("Alpha");
list2.add("Beta");
list1.add("Gamma");
list1.remove("Alpha");
list2.add("Delta");
Thread.sleep(10000);
OUTPUT
[]
[Alpha]
[Alpha, Beta]
[Alpha, Gamma, Beta]
[Gamma, Beta]
[Gamma, Beta, Delta]

Related

ViewPager returning empty array

When I start my app I want my customSwipeAdapter.java to wait until my savedImages ArrayList has received and been populated with the data from firebase. But instead my class is being ran and my whole page is empty because getCount() method is returning savedImages.size() as 0 because my arraylist hasn't been populated in time. Any help on maybe running my class when my array list is populated. Not sure what to do here :)
customSwipeAdapter.java
public class customSwipeAdapter extends PagerAdapter {
private Firebase mRef;
private Context ctx;
private LayoutInflater layoutInflator;
public customSwipeAdapter(Context ctx) {
this.ctx = ctx;
}
private int[] frontImages = {R.drawable.amen_parham, R.drawable.janel_parham, R.drawable.kevin_parham};
// Populate ArrayList with firebase data
List<String> savedImages = new ArrayList<String>();
Boolean goingToCallOnce = false;
Boolean finishedLoadingData = false;
#Override
public int getCount() {
return savedImages.size();
}
#Override
public boolean isViewFromObject(View view, Object o) {
return (view == o);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
getSavedImages_FromDB();
layoutInflator = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View item_view = layoutInflator.inflate(R.layout.swipe_layout, container, false);
final EasyFlipView mYourFlipView = (EasyFlipView) item_view.findViewById(R.id.flipView);
ImageView imageView_Front = (ImageView) item_view.findViewById(R.id.imageView_Front);
imageView_Front.setImageResource(frontImages[position]);
container.addView(item_view);
System.out.println(savedImages);
return item_view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((RelativeLayout)object);
}
public void getSavedImages_FromDB() {
mRef = new Firebase("");
if (goingToCallOnce == false) {
goingToCallOnce = true;
mRef.child("Q6i3fI6lNdYYS0z5Jty4WUYE9g13").child("SavedImages").addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String savedImage = (String) dataSnapshot.child("Image").getValue();
savedImages.add(0, savedImage);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
finishedLoadingData = true;
System.out.println("finishedLoadingData");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
}
}
savedCardsViewController.java
public class savedCardsViewController extends AppCompatActivity {
private Swipe swipe;
ViewPager viewPager;
customSwipeAdapter adapter;
private Firebase mRef;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_saved_cards_view_controller);
viewPager = (ViewPager) findViewById(R.id.view_pager);
adapter = new customSwipeAdapter(this);
viewPager.setAdapter(adapter);
viewPager.setPageTransformer(false, new DefaultTransformer());
}
}
'Context' to 'ValueEventListener'
2'nd 'Context' to 'ValueEventListener'
I suggest you load the data from Firebase on your Activity first and then pass it as a parameter to the adapter's constructor. This way your CustomSwipeAdapter would look similar to this:
public class customSwipeAdapter extends PagerAdapter {
private Firebase mRef;
private Context ctx;
private LayoutInflater layoutInflator;
List<String> savedImages = new ArrayList<String>();
public customSwipeAdapter(Context ctx, List<String> savedImages){
this.ctx = ctx;
this.savedImages = savedImages
}
...
}
Another note on Loading data from firebase on the Activity: use A SingleValueListener with an iterator instead of onChildAdded:
mRef.child("Q6i3fI6lNdYYS0z5Jty4WUYE9g13").child("SavedImages").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Iterator<DataSnapshot> data = dataSnapshot.getChildren().iterator();
while(data.hasNext())
{
String savedImage = (String) data.next().child("Image").getValue();
savedImages.add(0, savedImage);
}
//Data has finished loading. Load your adapter
adapter = new customSwipeAdapter(this, savedImages);
viewPager.setAdapter(adapter);
viewPager.setPageTransformer(false, new DefaultTransformer());
}
#Override
public void onCancelled(DatabaseError databaseError) {}
});

SceneBuilder shows Layout failure ("null") when loading FXML with custom control

I'm tryin to get a custom control working in the SceneBuilder.
What I've build is a control called ContentSection. To play around with the possibility of creating custom controls I nealry copied the code of a titled-pane and made some changes to the skin to match my future requirements of a ContentSection.
My control is working fine in my application, but I can not load it in the SceneBuilder.
Here is my code of the control:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import javafx.beans.DefaultProperty;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableBooleanProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.geometry.Orientation;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleAttribute;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.util.Duration;
#DefaultProperty("content")
public class ContentSection extends Control {
public ContentSection() {
getStyleClass().setAll(DEFAULT_STYLE_CLASS);
// initialize pseudo-class state
pseudoClassStateChanged(PSEUDO_CLASS_EXPANDED, true);
pseudoClassStateChanged(PSEUDO_CLASS_COLLAPSED, false);
}
public ContentSection(final String title, final Node content) {
this();
setTitle(title);
setContent(content);
}
private final StringProperty titleProperty = new StringPropertyBase() {
#Override
protected void invalidated() {
notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT);
}
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "title";
};
};
public final void setTitle(final String value) {
titleProperty().set(value);
}
public final String getTitle() {
return titleProperty == null ? null : titleProperty.get();
}
public final StringProperty titleProperty() {
return titleProperty;
}
private DoubleProperty iconSizeProperty;
public final void setIconSize(final double value) {
iconSizeProperty().set(value);
}
public final double getIconSize() {
return iconSizeProperty == null ? -1 : iconSizeProperty.get();
}
public final DoubleProperty iconSizeProperty() {
if (iconSizeProperty == null) {
iconSizeProperty = new SimpleDoubleProperty(this, "iconSize", -1);
}
return iconSizeProperty;
}
private ObjectProperty<FontAwesomeIcon> iconProperty;
public final void setIcon(final FontAwesomeIcon value) {
iconProperty().set(value);
}
public final FontAwesomeIcon getIcon() {
return iconProperty == null ? null : iconProperty.get();
}
#Override
public String getUserAgentStylesheet() {
return ContentSection.class.getClassLoader().getResource("content-section.css").toExternalForm();
}
public final ObjectProperty<FontAwesomeIcon> iconProperty() {
if (iconProperty == null) {
iconProperty = new SimpleObjectProperty<>(this, "icon");
}
return iconProperty;
}
private ObjectProperty<Node> contentProperty;
public final void setContent(final Node value) {
contentProperty().set(value);
}
public final Node getContent() {
return contentProperty == null ? null : contentProperty.get();
}
public final ObjectProperty<Node> contentProperty() {
if (contentProperty == null) {
contentProperty = new SimpleObjectProperty<Node>(this, "content");
}
return contentProperty;
}
private final BooleanProperty expandedProperty = new BooleanPropertyBase(true) {
#Override
protected void invalidated() {
final boolean active = get();
pseudoClassStateChanged(PSEUDO_CLASS_EXPANDED, active);
pseudoClassStateChanged(PSEUDO_CLASS_COLLAPSED, !active);
notifyAccessibleAttributeChanged(AccessibleAttribute.EXPANDED);
}
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "expanded";
}
};
public final void setExpanded(final boolean value) {
expandedProperty().set(value);
}
public final boolean isExpanded() {
return expandedProperty.get();
}
public final BooleanProperty expandedProperty() {
return expandedProperty;
}
private final BooleanProperty animatedProperty = new StyleableBooleanProperty(true) {
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "animated";
}
#Override
public CssMetaData<ContentSection, Boolean> getCssMetaData() {
return StyleableProperties.ANIMATED;
}
};
public final void setAnimated(final boolean value) {
animatedProperty().set(value);
}
public final boolean isAnimated() {
return animatedProperty.get();
}
public final BooleanProperty animatedProperty() {
return animatedProperty;
}
private final ObjectProperty<Duration> animationDurationProperty = new SimpleObjectProperty<>(this, "animationDuration", Duration.millis(350.0));
public final void setAnimationDuration(final Duration value) {
animationDurationProperty().set(value);
}
public final Duration getAnimationDuration() {
return animationDurationProperty().get();
}
public final ObjectProperty<Duration> animationDurationProperty() {
return animationDurationProperty;
}
private final BooleanProperty collapsibleProperty = new StyleableBooleanProperty(true) {
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public String getName() {
return "collapsible";
}
#Override
public CssMetaData<ContentSection, Boolean> getCssMetaData() {
return StyleableProperties.COLLAPSIBLE;
}
};
public final void setCollapsible(final boolean value) {
collapsibleProperty().set(value);
}
public final boolean isCollapsible() {
return collapsibleProperty.get();
}
public final BooleanProperty collapsibleProperty() {
return collapsibleProperty;
}
private final ObjectProperty<Number> headerSizeProperty = new StyleableObjectProperty<Number>(44.0) {
#Override
public String getName() {
return "headerSize";
}
#Override
public Object getBean() {
return ContentSection.this;
}
#Override
public CssMetaData<? extends Styleable, Number> getCssMetaData() {
return StyleableProperties.HEADER_SIZE;
}
};
public final void setHeaderSize(final Number value) {
headerSizeProperty().set(value);
}
public final Number getHeaderSize() {
return headerSizeProperty().get();
}
public final ObjectProperty<Number> headerSizeProperty() {
return headerSizeProperty;
}
/** {#inheritDoc} */
#Override
protected Skin<?> createDefaultSkin() {
return new ContentSectionSkin(this);
}
private static final String DEFAULT_STYLE_CLASS = "content-section";
private static final PseudoClass PSEUDO_CLASS_EXPANDED = PseudoClass.getPseudoClass("expanded");
private static final PseudoClass PSEUDO_CLASS_COLLAPSED = PseudoClass.getPseudoClass("collapsed");
private static class StyleableProperties {
private static final CssMetaData<ContentSection, Boolean> COLLAPSIBLE =
new CssMetaData<ContentSection, Boolean>("-fx-collapsible", StyleConverter.getBooleanConverter(), Boolean.TRUE) {
#Override
public boolean isSettable(final ContentSection n) {
return n.collapsibleProperty == null || !n.collapsibleProperty.isBound();
}
#Override
public StyleableProperty<Boolean> getStyleableProperty(final ContentSection n) {
return (StyleableProperty<Boolean>) n.collapsibleProperty();
}
};
private static final CssMetaData<ContentSection, Number> HEADER_SIZE =
new CssMetaData<ContentSection, Number>("-fx-header-size", StyleConverter.getSizeConverter(), 44.0) {
#Override
public boolean isSettable(final ContentSection n) {
return n.collapsibleProperty == null || !n.collapsibleProperty.isBound();
}
#Override
public StyleableProperty<Number> getStyleableProperty(final ContentSection n) {
return (StyleableProperty<Number>) n.headerSizeProperty();
}
};
private static final CssMetaData<ContentSection, Boolean> ANIMATED =
new CssMetaData<ContentSection, Boolean>("-fx-animated", StyleConverter.getBooleanConverter(), Boolean.TRUE) {
#Override
public boolean isSettable(final ContentSection n) {
return n.animatedProperty == null || !n.animatedProperty.isBound();
}
#Override
public StyleableProperty<Boolean> getStyleableProperty(final ContentSection n) {
return (StyleableProperty<Boolean>) n.animatedProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
static {
final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
styleables.add(COLLAPSIBLE);
styleables.add(ANIMATED);
styleables.add(HEADER_SIZE);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
#Override
public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
return getClassCssMetaData();
}
#Override
public Orientation getContentBias() {
final Node c = getContent();
return c == null ? super.getContentBias() : c.getContentBias();
}
#Override
public Object queryAccessibleAttribute(final AccessibleAttribute attribute, final Object... parameters) {
switch (attribute) {
case TEXT: {
final String accText = getAccessibleText();
if (accText != null && !accText.isEmpty()) {
return accText;
}
return getTitle();
}
case EXPANDED:
return isExpanded();
default:
return super.queryAccessibleAttribute(attribute, parameters);
}
}
#Override
public void executeAccessibleAction(final AccessibleAction action, final Object... parameters) {
switch (action) {
case EXPAND:
setExpanded(true);
break;
case COLLAPSE:
setExpanded(false);
break;
default:
super.executeAccessibleAction(action);
}
}
}
Here is my code of the skin:
import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView;
import javafx.animation.Animation.Status;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
#SuppressWarnings("restriction")
public class ContentSectionSkin extends BehaviorSkinBase<ContentSection, ContentSectionBehavior> {
private final BorderPane container;
private final HBox header;
private final BorderPane contentContainer;
private Timeline timeline;
private double transitionStartValue;
private DoubleProperty transition;
private FontAwesomeIconView expandCollapseIcon;
private Button expandCollapseButton;
private HBox sectionToolBar;
public ContentSectionSkin(final ContentSection contentSection) {
super(contentSection, new ContentSectionBehavior(contentSection));
transitionStartValue = 0;
container = createContainer();
getChildren().setAll(container);
header = createHeader();
container.setTop(header);
contentContainer = createContentContainer();
container.setCenter(contentContainer);
registerChangeListener(contentSection.contentProperty(), "CONTENT");
registerChangeListener(contentSection.expandedProperty(), "EXPANDED");
registerChangeListener(contentSection.collapsibleProperty(), "COLLAPSIBLE");
sectionToolBar.getChildren().addListener((ListChangeListener<Node>) c -> updateHeader());
if (contentSection.isExpanded()) {
setTransition(1.0f);
setExpanded(contentSection.isExpanded());
} else {
setTransition(0.0f);
if (getSkinnable().getContent() != null) {
getSkinnable().getContent().setVisible(false);
}
}
}
#Override
protected double computeMaxWidth(final double height, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
return Double.MAX_VALUE;
}
#Override
protected double computeMinHeight(final double width, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerHeight = snapSize(header.prefHeight(width));
final double contentHeight = contentContainer.minHeight(width) * getTransition();
final double minHeight = headerHeight + snapSize(contentHeight) + topInset + bottomInset;
return minHeight;
}
#Override
protected double computeMinWidth(final double height, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerWidth = snapSize(header.prefWidth(height));
final double contentWidth = snapSize(contentContainer.minWidth(height));
final double minWidth = Math.max(headerWidth, contentWidth) + leftInset + rightInset;
return minWidth;
}
#Override
protected double computePrefHeight(final double width, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerHeight = snapSize(header.prefHeight(width));
final double contentHeight = contentContainer.prefHeight(width) * getTransition();
final double prefHeight = headerHeight + snapSize(contentHeight) + topInset + bottomInset;
return prefHeight;
}
#Override
protected double computePrefWidth(final double height, final double topInset, final double rightInset, final double bottomInset, final double leftInset) {
final double headerWidth = snapSize(header.prefWidth(height));
final double contentWidth = snapSize(contentContainer.prefWidth(height));
final double prefWidth = Math.max(headerWidth, contentWidth) + leftInset + rightInset;
return prefWidth;
}
private BorderPane createContainer() {
final BorderPane container = new BorderPane();
container.setMinSize(0.0, 0.0);
return container;
}
private BorderPane createContentContainer() {
final BorderPane contentContainer = new BorderPane();
contentContainer.getStyleClass().setAll("content");
contentContainer.setMinSize(0.0, 0.0);
if (getSkinnable().getContent() != null) {
contentContainer.setCenter(getSkinnable().getContent());
}
return contentContainer;
}
private Button createExpandCollapseButton() {
final Button expandCollapseButton = new Button();
expandCollapseButton.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
expandCollapseButton.setOnAction(event -> {
getSkinnable().setExpanded(!getSkinnable().isExpanded());
});
final StackPane expandCollapseIconContainer = new StackPane();
expandCollapseIconContainer.getStyleClass().setAll("icon-container");
expandCollapseIconContainer.setId("last");
expandCollapseIconContainer.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
expandCollapseIconContainer.setPrefSize(21, 21);
expandCollapseIconContainer.setMinSize(21, 21);
expandCollapseButton.setGraphic(expandCollapseIconContainer);
expandCollapseIcon = new FontAwesomeIconView(FontAwesomeIcon.CHEVRON_DOWN);
expandCollapseIcon.setStyleClass("icon");
expandCollapseIconContainer.getChildren().add(expandCollapseIcon);
return expandCollapseButton;
}
private HBox createHeader() {
final HBox header = new HBox();
header.getStyleClass().setAll("header");
header.setPrefHeight(getSkinnable().getHeaderSize().doubleValue());
header.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
header.getChildren().add(createIconContainer());
header.getChildren().add(createTitleLabel());
header.getChildren().add(createPlaceholder());
header.getChildren().add(createSectionToolBar());
return header;
}
private FontAwesomeIconView createIcon() {
final FontAwesomeIconView iconView = new FontAwesomeIconView();
iconView.setStyleClass("icon");
iconView.setIcon(getSkinnable().getIcon());
iconView.glyphSizeProperty().bind(getSkinnable().iconSizeProperty());
return iconView;
}
private StackPane createIconContainer() {
final StackPane iconContainer = new StackPane();
iconContainer.getStyleClass().setAll("icon-container");
iconContainer.setPrefSize(50, 50);
iconContainer.getChildren().add(createIcon());
return iconContainer;
}
private Pane createPlaceholder() {
final Pane placeholder = new Pane();
HBox.setHgrow(placeholder, Priority.ALWAYS);
return placeholder;
}
private HBox createSectionToolBar() {
sectionToolBar = new HBox();
sectionToolBar.getStyleClass().setAll("section-tool-bar");
expandCollapseButton = createExpandCollapseButton();
if (getSkinnable().isCollapsible()) {
sectionToolBar.getChildren().add(sectionToolBar.getChildren().size(), expandCollapseButton);
}
return sectionToolBar;
}
private Label createTitleLabel() {
final Label titleLabel = new Label();
titleLabel.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
titleLabel.textProperty().bind(getSkinnable().titleProperty());
return titleLabel;
}
private void disableCache() {
getSkinnable().getContent().setCache(false);
}
private void doAnimationTransition() {
if (getSkinnable().getContent() == null) {
return;
}
Duration duration;
if (timeline != null && (timeline.getStatus() != Status.STOPPED)) {
duration = timeline.getCurrentTime();
timeline.stop();
} else {
duration = getSkinnable().getAnimationDuration();
}
timeline = new Timeline();
timeline.setCycleCount(1);
KeyFrame k1, k2;
if (getSkinnable().isExpanded()) {
k1 = new KeyFrame(Duration.ZERO, event -> {
// start expand
getSkinnable().getContent().setVisible(true);
}, new KeyValue(transitionProperty(), transitionStartValue));
k2 = new KeyFrame(duration, event -> {
// end expand
}, new KeyValue(transitionProperty(), 1, Interpolator.LINEAR)
);
} else {
k1 = new KeyFrame(Duration.ZERO, event -> {
// Start collapse
}, new KeyValue(transitionProperty(), transitionStartValue));
k2 = new KeyFrame(duration, event -> {
// end collapse
getSkinnable().getContent().setVisible(false);
}, new KeyValue(transitionProperty(), 0, Interpolator.LINEAR));
}
timeline.getKeyFrames().setAll(k1, k2);
timeline.play();
}
private void enableCache() {
getSkinnable().getContent().setCache(true);
}
public BorderPane getContentContainer() {
return contentContainer;
}
private double getTransition() {
return transition == null ? 0.0 : transition.get();
}
#Override
protected void handleControlPropertyChanged(final String property) {
super.handleControlPropertyChanged(property);
if ("CONTENT".equals(property)) {
final Node content = getSkinnable().getContent();
if (content == null) {
contentContainer.setCenter(null);
} else {
contentContainer.setCenter(content);
}
} else if ("EXPANDED".equals(property)) {
setExpanded(getSkinnable().isExpanded());
} else if ("COLLAPSIBLE".equals(property)) {
updateHeader();
}
}
private void setExpanded(final boolean expanded) {
if (!getSkinnable().isCollapsible()) {
setTransition(1.0f);
return;
}
// we need to perform the transition between expanded / hidden
if (getSkinnable().isAnimated()) {
transitionStartValue = getTransition();
doAnimationTransition();
} else {
if (expanded) {
setTransition(1.0f);
} else {
setTransition(0.0f);
}
if (getSkinnable().getContent() != null) {
getSkinnable().getContent().setVisible(expanded);
}
getSkinnable().requestLayout();
}
}
private void setTransition(final double value) {
transitionProperty().set(value);
}
private DoubleProperty transitionProperty() {
if (transition == null) {
transition = new SimpleDoubleProperty(this, "transition", 0.0) {
#Override
protected void invalidated() {
container.requestLayout();
updateExpandCollapseIconRotation();
}
};
}
return transition;
}
private void updateExpandCollapseIconRotation() {
expandCollapseIcon.setRotate(180 * getTransition());
}
private void updateHeader() {
if (getSkinnable().isCollapsible() && !sectionToolBar.getChildren().contains(expandCollapseButton)) {
sectionToolBar.getChildren().add(sectionToolBar.getChildren().size(), expandCollapseButton);
} else if (sectionToolBar.getChildren().contains(expandCollapseButton)) {
sectionToolBar.getChildren().remove(expandCollapseButton);
}
}
}
And last but not least the behavior:
import static javafx.scene.input.KeyCode.SPACE;
import java.util.ArrayList;
import java.util.List;
import com.sun.javafx.scene.control.behavior.BehaviorBase;
import com.sun.javafx.scene.control.behavior.KeyBinding;
import javafx.scene.input.MouseEvent;
#SuppressWarnings("restriction")
public class ContentSectionBehavior extends BehaviorBase<ContentSection> {
private final ContentSection contentSection;
public ContentSectionBehavior(final ContentSection contentSection) {
super(contentSection, CONTENT_SECTION_BINDINGS);
this.contentSection = contentSection;
}
/***************************************************************************
* *
* Key event handling *
* *
**************************************************************************/
private static final String PRESS_ACTION = "Press";
protected static final List<KeyBinding> CONTENT_SECTION_BINDINGS = new ArrayList<KeyBinding>();
static {
CONTENT_SECTION_BINDINGS.add(new KeyBinding(SPACE, PRESS_ACTION));
}
#Override
protected void callAction(final String name) {
switch (name) {
case PRESS_ACTION:
if (contentSection.isCollapsible() && contentSection.isFocused()) {
contentSection.setExpanded(!contentSection.isExpanded());
contentSection.requestFocus();
}
break;
default:
super.callAction(name);
}
}
/***************************************************************************
* *
* Mouse event handling *
* *
**************************************************************************/
#Override
public void mousePressed(final MouseEvent e) {
super.mousePressed(e);
final ContentSection contentSection = getControl();
contentSection.requestFocus();
}
/**************************************************************************
* State and Functions *
*************************************************************************/
public void expand() {
contentSection.setExpanded(true);
}
public void collapse() {
contentSection.setExpanded(false);
}
public void toggle() {
contentSection.setExpanded(!contentSection.isExpanded());
}
}
To test my experiment I just created a simple fxml file:
<?xml version="1.0" encoding="UTF-8"?>
<?import org.test.ContentSection?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.StackPane?>
<ScrollPane fx:id="root" fitToWidth="true" fitToHeight="true" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<content>
<VBox styleClass="content">
<children>
<ContentSection fx:id="contentSection" collapsible="false" VBox.vgrow="ALWAYS">
<content>
<StackPane>
<children>
<Label text="Test" />
</children>
</StackPane>
</content>
</ContentSection>
</children>
</VBox>
</content>
</ScrollPane>
Now while opening this FXML file with the SceneBuilder the only thing happening is I'm getting a warning in top area of the SceneBuilder that says "Layout failure ('null')".
Does someone know what to do next?
I don't see an option to search for the problem...
Best regards
Patrick
The same error has just happened to me, and the problem is the css styles. I had to remove the styles and then add them to the FXML.

mirror one observableList to another

In JavaFX, I have an ObservableList of objects, and want another ObservableList that will mirror the first list but contain a String representation of each object. Is there anything simpler than writing a custom ListChangeListener to do the conversion ? I have a StringConverter which can provide the mirrored value.
Similarly, given an ObservableList<String>, how do I create a second ObservableList<String> that has a constant entry at index 0, and mirrors the first list beginning at index 1?
For the first question, the easiest way to do this is to use the EasyBind framework. Then it is as simple as
ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);
Here is an SSCCE using EasyBind:
import org.fxmisc.easybind.EasyBind;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;
public class MappedAndTransformedListExample {
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
#Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
#Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
namesList.forEach(System.out::println);
namesList.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown#example.com"));
namesList.forEach(System.out::println);
}
public static class Person {
private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
private final StringProperty email = new SimpleStringProperty(this, "email");
public Person(String firstName, String lastName, String email) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.email.set(email);
}
public final StringProperty firstNameProperty() {
return this.firstName;
}
public final String getFirstName() {
return this.firstNameProperty().get();
}
public final void setFirstName(final String firstName) {
this.firstNameProperty().set(firstName);
}
public final StringProperty lastNameProperty() {
return this.lastName;
}
public final java.lang.String getLastName() {
return this.lastNameProperty().get();
}
public final void setLastName(final java.lang.String lastName) {
this.lastNameProperty().set(lastName);
}
public final StringProperty emailProperty() {
return this.email;
}
public final java.lang.String getEmail() {
return this.emailProperty().get();
}
public final void setEmail(final java.lang.String email) {
this.emailProperty().set(email);
}
}
}
If you prefer not to use a third-party framework for some reason, you can use a TransformationList (which is what EasyBind does under the hood: I copied the code below from the source code there and modified it).
In the above, you would replace
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
with
ObservableList<String> namesList = new TransformationList<String, Person>(baseList) {
#Override
public int getSourceIndex(int index) {
return index ;
}
#Override
public String get(int index) {
return converter.toString(getSource().get(index));
}
#Override
public int size() {
return getSource().size();
}
#Override
protected void sourceChanged(Change<? extends Person> c) {
fireChange(new Change<String>(this) {
#Override
public boolean wasAdded() {
return c.wasAdded();
}
#Override
public boolean wasRemoved() {
return c.wasRemoved();
}
#Override
public boolean wasReplaced() {
return c.wasReplaced();
}
#Override
public boolean wasUpdated() {
return c.wasUpdated();
}
#Override
public boolean wasPermutated() {
return c.wasPermutated();
}
#Override
public int getPermutation(int i) {
return c.getPermutation(i);
}
#Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
#Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(Person removedPerson: c.getRemoved()) {
res.add(converter.toString(removedPerson));
}
return res;
}
#Override
public int getFrom() {
return c.getFrom();
}
#Override
public int getTo() {
return c.getTo();
}
#Override
public boolean next() {
return c.next();
}
#Override
public void reset() {
c.reset();
}
});
}
};
For the second question, you must use a transformation list. Here's an updated main(...) method that shows how to do this. (It works just as well with the second version of part 1.)
public static void main(String[] ags) {
ObservableList<Person> baseList = FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com")
);
StringConverter<Person> converter = new StringConverter<Person>() {
#Override
public String toString(Person person) {
return person.getFirstName() + " " + person.getLastName();
}
#Override
public Person fromString(String string) {
int indexOfDelimiter = string.indexOf(' ');
return new Person(string.substring(0, indexOfDelimiter),
string.substring(indexOfDelimiter+1),
"");
}
};
ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);
ObservableList<String> namesListWithHeader = new TransformationList<String, String>(namesList) {
#Override
public int getSourceIndex(int index) {
return index - 1 ;
}
#Override
public String get(int index) {
if (index == 0) {
return "Contacts";
} else {
return getSource().get(index - 1);
}
}
#Override
public int size() {
return getSource().size() + 1 ;
}
#Override
protected void sourceChanged(Change<? extends String> c) {
fireChange(new Change<String>(this) {
#Override
public boolean wasAdded() {
return c.wasAdded();
}
#Override
public boolean wasRemoved() {
return c.wasRemoved();
}
#Override
public boolean wasReplaced() {
return c.wasReplaced();
}
#Override
public boolean wasUpdated() {
return c.wasUpdated();
}
#Override
public boolean wasPermutated() {
return c.wasPermutated();
}
#Override
public int getPermutation(int i) {
return c.getPermutation(i - 1) + 1;
}
#Override
protected int[] getPermutation() {
// This method is only called by the superclass methods
// wasPermutated() and getPermutation(int), which are
// both overriden by this class. There is no other way
// this method can be called.
throw new AssertionError("Unreachable code");
}
#Override
public List<String> getRemoved() {
ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
for(String removed: c.getRemoved()) {
res.add(removed);
}
return res;
}
#Override
public int getFrom() {
return c.getFrom() + 1;
}
#Override
public int getTo() {
return c.getTo() + 1;
}
#Override
public boolean next() {
return c.next();
}
#Override
public void reset() {
c.reset();
}
});
}
};
namesListWithHeader.forEach(System.out::println);
namesListWithHeader.addListener((Change<? extends String> c) -> {
while (c.next()) {
if (c.wasAdded()) {
System.out.println("Added "+c.getAddedSubList());
System.out.println("From: "+c.getFrom()+", To: "+c.getTo());
}
}
});
System.out.println("\nAdding Michael to base list...\n");
baseList.add(new Person("Michael", "Brown", "michael.brown#example.com"));
namesListWithHeader.forEach(System.out::println);
}
Here's a slightly shorter version of James_D's answer using Lombok's #Delegate to avoid having to write all the delegating methods
/**
* A List that mirrors a base List by applying a converter on all items.
* #param <E> item type of the target List
* #param <F> item type of the base list
*/
public class MirroringList<E, F> extends TransformationList<E, F> implements ObservableList<E> {
/** mapping function from base list item type to target list item type */
private final Function<F, E> converter;
public MirroringList(ObservableList<? extends F> list, Function<F, E> converter) {
super(list);
this.converter = converter;
}
#Override
public int getSourceIndex(int index) {
return index;
}
#Override
public E get(int index) {
return converter.apply(getSource().get(index));
}
#Override
public int size() {
return getSource().size();
}
#Override
protected void sourceChanged(Change<? extends F> change) {
fireChange(new DelegatingChange(change, this));
}
/**
* An implementation of {#link Change} that delegates all methods to a specified change except {#link #getRemoved()}
*/
private class DelegatingChange extends Change<E> implements DelegatingChangeExcluded<E> {
#Delegate(excludes = DelegatingChangeExcluded.class)
private final Change<? extends F> change;
public DelegatingChange(Change<? extends F> change, MirroringList<E, F> list) {
super(list);
this.change = change;
}
#Override
protected int[] getPermutation() {
return new int[0];
}
#Override
public List<E> getRemoved() {
return change.getRemoved().stream()
.map(converter)
.collect(Collectors.toList());
}
}
/**
* This interface is only used to exclude some methods from delegated methods via Lombok's #{#link Delegate}
* so that the compiler doesn't complain.
*/
#SuppressWarnings("unused")
private interface DelegatingChangeExcluded<E> {
List<E> getRemoved();
ObservableList<? extends E> getList();
List<E> getAddedSubList();
}
}

Disable row selection in TableView

I have a read-only TableView in JavaFX 8, and I don't want' the users to select rows.
They should still be able to sort the columns and scroll, just not to select any rows.
How can I achieve this?
You can disable selection, by setting selectionModel to null.
table.setSelectionModel(null);
After a while I found how to solve it so posting it here for future users.
The solution is based on this answer:
JavaFX8 - Remove highlighting of selected row
After adding the following lines to your css, selected lines will look exactly as unselected lines, achieving the same effect I wanted in the same place:
.table-row-cell:filled:selected {
-fx-background: -fx-control-inner-background ;
-fx-background-color: -fx-table-cell-border-color, -fx-background ;
-fx-background-insets: 0, 0 0 1 0 ;
-fx-table-cell-border-color: derive(-fx-color, 5%);
}
.table-row-cell:odd:filled:selected {
-fx-background: -fx-control-inner-background-alt ;
}
I've just hit this issue myself. I think the best way to solve it is to provide a null implementation of TableViewSelectionModel.
Then you can simply say tableView.setSelectionModel(new NullTableViewSelectionModel(tableView));
A sample null implementation is below...
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
public class NullTableViewSelectionModel extends TableView.TableViewSelectionModel {
public NullTableViewSelectionModel(TableView tableView) {
super(tableView);
}
#Override
public ObservableList<TablePosition> getSelectedCells() {
return FXCollections.emptyObservableList();
}
#Override
public void selectLeftCell() {
}
#Override
public void selectRightCell() {
}
#Override
public void selectAboveCell() {
}
#Override
public void selectBelowCell() {
}
#Override
public void clearSelection(int i, TableColumn tableColumn) {
}
#Override
public void clearAndSelect(int i, TableColumn tableColumn) {
}
#Override
public void select(int i, TableColumn tableColumn) {
}
#Override
public boolean isSelected(int i, TableColumn tableColumn) {
return false;
}
#Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}
#Override
public ObservableList getSelectedItems() {
return FXCollections.emptyObservableList();
}
#Override
public void selectIndices(int i, int... ints) {
}
#Override
public void selectAll() {
}
#Override
public void clearAndSelect(int i) {
}
#Override
public void select(int i) {
}
#Override
public void select(Object o) {
}
#Override
public void clearSelection(int i) {
}
#Override
public void clearSelection() {
}
#Override
public boolean isSelected(int i) {
return false;
}
#Override
public boolean isEmpty() {
return false;
}
#Override
public void selectPrevious() {
}
#Override
public void selectNext() {
}
#Override
public void selectFirst() {
}
#Override
public void selectLast() {
}
}
I found here another solution for the same problem but for a ListView. The pattern is: listen the selection change event, and then clear the selection. It works also for the TableView.
Code example:
_tableView.getSelectionModel()
.selectedIndexProperty()
.addListener((observable, oldvalue, newValue) -> {
Platform.runLater(() -> {
_tableView.getSelectionModel().clearSelection();
});
});

Implementing an ObservableValue

I have this object:
public class Oggetto{
private int value;
private boolean valid;
public Oggetto(int value, boolean valid) {
this.value = value;
this.valid = valid;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
}
and I would like implement an Observable object that fires events when something inside changes
Here the observable object:
public class OggettoOsservabile implements ObservableValue<Oggetto>{
private Oggetto value;
OggettoOsservabile(int i, boolean b) {
this.value=new Oggetto(i, b);
}
#Override
public void addListener(ChangeListener<? super Oggetto> listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public void removeListener(ChangeListener<? super Oggetto> listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public Oggetto getValue() {
return value;
}
#Override
public void addListener(InvalidationListener listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public void removeListener(InvalidationListener listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
i dont know how to proceed in order to detect a change in the class "Oggetto" and send a notification to the registeres listener.
OggettoOsservabile oggetto = new OggettoOsservabile(1, false);
oggetto.addListener(new ChangeListener<Oggetto>() {
public void changed(ObservableValue<? extends Oggetto> observable, Oggetto oldValue, Oggetto newValue) {
System.out.println("changed " + oldValue + "->" + newValue);
}
});
Implement your Oggetto class using standard JavaFX Properties:
import javafx.beans.property.BooleanProperty ;
import javafx.beans.property.IntegerProperty ;
import javafx.beans.property.SimpleBooleanProperty ;
import javafx.beans.property.SimpleIntegerProperty ;
public class Oggetto {
private final IntegerProperty value = new SimpleIntegerProperty() ;
public final IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return value.get();
}
public final void setValue(int value) {
this.value.set(value);
}
private final BooleanProperty valid = new SimpleBooleanProperty();
public final BooleanProperty validProperty() {
return valid ;
}
public final boolean isValid() {
return valid.get();
}
public final void setValid(boolean valid) {
this.valid.set(valid);
}
public Oggetto(int value, boolean valid) {
setValue(value);
setValid(valid);
}
}
This may be all you need, as you can just observe the individual properties. But if you want a class that notifies invalidation listeners if either property changes, you can extend ObjectBinding:
import javafx.beans.binding.ObjectBinding ;
public class OggettoObservable extends ObjectBinding {
private final Oggetto value ;
public OggettoObservable(int value, boolean valid) {
this.value = new Oggetto(value, valid);
bind(this.value.valueProperty(), this.value.validProperty());
}
#Override
public Oggetto computeValue() {
return value ;
}
}
import javafx.beans.InvalidationListener;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
public class VerySimply implements ObservableValue<Integer> {
private int newValue;
public ChangeListener<Integer> listener = new ChangeListener<Integer>() {
#Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) {
System.out.println(" :) "+ newValue.intValue());
}
};
#Override
public void addListener(ChangeListener<? super Integer> listener) {
}
#Override
public void removeListener(ChangeListener<? super Integer> listener) {
}
#Override
public Integer getValue() {
return newValue;
}
#Override
public void addListener(InvalidationListener listener) {
}
#Override
public void removeListener(InvalidationListener listener) {
}
public void setNewValue(int newValue) {
int oldValue = this.newValue;
this.newValue = newValue;
listener.changed(this,oldValue,this.newValue);
}
}

Resources