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.
Related
i've a problem with a custom cell render in a java fx table view component. I'm able to render the split menu button, but is only rendered from second row of table.
Below i put the code created to generate that image.
SplitMenuButtonApp.java
package com.example.splimenubtn;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class SplitMenuButtonApp extends Application {
private class Contact {
private StringProperty firstName;
private StringProperty lastName;
public Contact() {}
public Contact(String fName, String lName) {
firstName = new SimpleStringProperty(fName);
lastName = new SimpleStringProperty(lName);
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public StringProperty firstName() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lName) {
lastName.set(lName);
}
public StringProperty lastName() {
return lastName;
}
}
private ObservableList<Contact> data;
protected List<MenuItemFactory<Contact>> menuItemsList;
private TableView<Contact> table;
#Override
public void start(Stage primaryStage) throws Exception {
// Init data list
data = FXCollections.observableArrayList();
data.add(new Contact("Mickey", "Mouse"));
data.add(new Contact("Donald", "Duck"));
data.add(new Contact("Fantasy", "Name"));
initMenuButton();
SplitMenuButtonFactory<Contact> sMBtn = new SplitMenuButtonFactory<>();
sMBtn.setMenuItems(menuItemsList);
SplitMenuButton actions = sMBtn.buildButton();
// Build the list
table = new TableView<>();
TableColumn<Contact, String> col = new TableColumn<>("First Name");
col.setCellValueFactory(c -> c.getValue().firstName);
table.getColumns().add(col);
col = new TableColumn<>("Last Name");
col.setCellValueFactory(c -> c.getValue().lastName);
table.getColumns().add(col);
TableColumn<Contact, SplitMenuButton> aCol = new TableColumn<>("Action");
aCol.setCellValueFactory(new PropertyValueFactory<>(""));
aCol.setCellFactory(new ButtonCellFactory<>(actions));
table.getColumns().add(aCol);
table.setItems(data);
AnchorPane root = new AnchorPane();
AnchorPane.setTopAnchor(table, 5.0);
AnchorPane.setRightAnchor(table, 5.0);
AnchorPane.setBottomAnchor(table, 5.0);
AnchorPane.setLeftAnchor(table, 5.0);
root.getChildren().add(table);
Scene s = new Scene(root, 600d, 300d);
primaryStage.setScene(s);
primaryStage.setTitle("Split menu button on table row");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initMenuButton() {
if (menuItemsList == null) {
menuItemsList = new ArrayList<>();
menuItemsList.add(new MenuItemFactory<Contact>(MenuItemActions.EDIT, "Edit", true).setDataList(table));
menuItemsList.add(new MenuItemFactory<Contact>(MenuItemActions.DELETE, "Delete", false).setDataList(table));
}
}
}
MenuItemActions.java
package com.example.splimenubtn;
public enum MenuItemActions {
/**
* Detail item
*/
DETAILS,
/**
* Edit/Update item
*/
EDIT,
/**
* Delete item
*/
DELETE;
}
MenuItemFactory.java
package com.example.splimenubtn;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableView;
public class MenuItemFactory<S> {
private MenuItemActions itemType;
private String itemLbl;
private TableView<S> table;
private boolean defaultAction;
public MenuItemFactory() {}
public MenuItemFactory(MenuItemActions itemType, String itemLabel, boolean dA) {
this.itemType = itemType;
itemLbl = itemLabel;
defaultAction = dA;
}
public MenuItemFactory<S> setDataList(TableView<S> t) {
table = t;
return this;
}
public boolean isDefault() {
return defaultAction;
}
public MenuItem buildMenuItem() {
MenuItem mI = new MenuItem();
switch (itemType) {
case DETAILS:
mI.setText(itemLbl);
mI.setOnAction(handleDetails());
break;
case EDIT:
mI.setText(itemLbl);
mI.setOnAction(handleEdit());
break;
case DELETE:
mI.setText(itemLbl);
mI.setOnAction(handleDelete());
break;
default:
break;
}
return mI;
}
private EventHandler<ActionEvent> handleDetails() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DETAIL REQUESTED ***");
}
};
}
private EventHandler<ActionEvent> handleEdit() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** EDIT REQUESTED ***");
}
};
}
private EventHandler<ActionEvent> handleDelete() {
return new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent aE) {
System.out.println("*** DELETE REQUESTED ***");
}
};
}
}
ButtonCellFactory.java
package com.example.splimenubtn;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
public class ButtonCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private SplitMenuButton btn;
public ButtonCellFactory() {}
public ButtonCellFactory(SplitMenuButton b) {
btn = b;
}
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
return new TableCell<S, T>() {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(btn);
}
}
};
}
}
SpliMenuButtonFactory.java
package com.example.splimenubtn;
import java.util.List;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
public class SplitMenuButtonFactory<T> {
private List<MenuItemFactory<T>> menuItems;
public SplitMenuButtonFactory() {}
public SplitMenuButtonFactory<T> setMenuItems(List<MenuItemFactory<T>> items) {
menuItems = items;
return this;
}
public SplitMenuButton buildButton() {
SplitMenuButton menuBtn = new SplitMenuButton();
for (MenuItemFactory<?> mIF : menuItems) {
MenuItem btn = mIF.buildMenuItem();
if (mIF.isDefault()) {
menuBtn.setText(btn.getText());
menuBtn.setOnAction(btn.getOnAction());
}
menuBtn.getItems().add(btn);
}
return menuBtn;
}
}
As you see in the image, with this code i'm able to create the spli menu button and add it to ta table, but is only rendered on last row.
I need suggestion to render the split menu button in the other row, any help is appreciated.
Cause you have use the same button in every cell, So it's set a button only last of the cell Value.
Remove this line in SplitMenuButtonApp class
SplitMenuButton actions = sMBtn.buildButton();
And replace this line
aCol.setCellFactory(new ButtonCellFactory<>(actions));
To below code
Callback<TableColumn<Contact, SplitMenuButton>, TableCell<Contact, SplitMenuButton>> actionsCol = new Callback<TableColumn<Contact, SplitMenuButton>, TableCell<Contact, SplitMenuButton>>() {
#Override
public TableCell call(final TableColumn<Contact, SplitMenuButton> param) {
final TableCell<Contact, SplitMenuButton> cell = new TableCell<Contact, SplitMenuButton>() {
SplitMenuButton actions = sMBtn.buildButton();
#Override
public void updateItem(SplitMenuButton item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(actions);
setText(null);
}
}
};
return cell;
}
};
aCol.setCellFactory(actionsCol);
I hope this code is working for you:)
I already know how I have to manipulate the table cell using a table cell factory callback. I added a currency symbol to the cell to make it look neat. (i.e. € 5,00 instead of 5,00) The thing is, when i double click on the cell i want that symbol to be removed. But for the heck of it, I'm unable to find how i am able to manipulate the textfield again to remove that currency symbol and bring it back in when the user committed the edit. Basically what I try to do is something similar when editing a cell in Excel :).
Any chance someone can help me out with a little basic example? Do I need to use the OnEditStart event?
Any time you want to configure how an item in a cell is displayed, without changing the actual data, you should use a custom TableCell. Here is an example that exhibits the behavior you want:
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.function.UnaryOperator;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.StringConverter;
public class CurrencyCell<T> extends TableCell<T, Double> {
private final TextField textField ;
private final NumberFormat format = DecimalFormat.getCurrencyInstance();
private final DecimalFormat textFieldFormat = new DecimalFormat("0.00");
public CurrencyCell() {
this.textField = new TextField();
StringConverter<Double> converter = new StringConverter<Double>() {
#Override
public String toString(Double object) {
return object == null ? "" : textFieldFormat.format(object) ;
}
#Override
public Double fromString(String string) {
try {
return string.isEmpty() ? 0.0 : textFieldFormat.parse(string).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
return 0.0 ;
}
}
};
UnaryOperator<Change> filter = (Change change) -> {
String newText = change.getControlNewText() ;
if (newText.isEmpty()) {
return change ;
}
try {
textFieldFormat.parse(newText);
return change ;
} catch (ParseException exc) {
return null ;
}
};
TextFormatter<Double> textFormatter = new TextFormatter<Double>(converter, 0.0, filter);
textField.setTextFormatter(textFormatter);
textField.setOnAction(e -> commitEdit(converter.fromString(textField.getText())));
textField.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setContentDisplay(ContentDisplay.TEXT_ONLY);
} else if (isEditing()) {
textField.setText(item.toString());
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(format.format(item));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
#Override
public void startEdit() {
super.startEdit();
textField.setText(textFieldFormat.format(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(format.format(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void commitEdit(Double newValue) {
super.commitEdit(newValue);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
And here's an example using it:
import java.util.Locale ;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class CurrencyCellTest extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
table.getColumns().add(column("Item", Item::nameProperty));
TableColumn<Item, Double> priceCol = column("Price", item -> item.priceProperty().asObject());
table.getColumns().add(priceCol);
priceCol.setCellFactory(tc -> new CurrencyCell<>());
Random rng = new Random();
for (int i = 1; i <= 100; i++) {
table.getItems().add(new Item("Item "+i, rng.nextInt(10000)/100.0));
}
primaryStage.setScene(new Scene(table, 600, 600));
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> property) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final DoubleProperty price = new SimpleDoubleProperty();
public Item(String name, double price) {
setName(name);
setPrice(price);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final DoubleProperty priceProperty() {
return this.price;
}
public final double getPrice() {
return this.priceProperty().get();
}
public final void setPrice(final double price) {
this.priceProperty().set(price);
}
}
public static void main(String[] args) {
// for testing:
Locale.setDefault(new Locale("NL", "nl"));
launch(args);
}
}
I have tried the below code to create an autocomplete combobox with ID and Value. It's working fine when i select any value using mouse event. But with Key Event it is showing error
Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to demo.Demo$Employee
at demo.Demo$1.changed(Demo.java:34)
Code: AutoCompleteComboBoxListener.java
package demo;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.ComboBox;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class AutoCompleteComboBoxListener<T> implements EventHandler<KeyEvent> {
private ComboBox comboBox;
private StringBuilder sb;
private ObservableList<T> data;
private boolean moveCaretToPos = false;
private int caretPos;
public AutoCompleteComboBoxListener(final ComboBox comboBox) {
this.comboBox = comboBox;
sb = new StringBuilder();
data = comboBox.getItems();
this.comboBox.setEditable(true);
this.comboBox.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
comboBox.hide();
}
});
this.comboBox.setOnKeyReleased(AutoCompleteComboBoxListener.this);
}
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.UP) {
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.DOWN) {
if(!comboBox.isShowing()) {
comboBox.show();
}
caretPos = -1;
moveCaret(comboBox.getEditor().getText().length());
return;
} else if(event.getCode() == KeyCode.BACK_SPACE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
} else if(event.getCode() == KeyCode.DELETE) {
moveCaretToPos = true;
caretPos = comboBox.getEditor().getCaretPosition();
}
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT
|| event.isControlDown() || event.getCode() == KeyCode.HOME
|| event.getCode() == KeyCode.END || event.getCode() == KeyCode.TAB) {
return;
}
ObservableList list = FXCollections.observableArrayList();
for (int i=0; i<data.size(); i++) {
if(data.get(i).toString().toLowerCase().startsWith(
AutoCompleteComboBoxListener.this.comboBox
.getEditor().getText().toLowerCase())) {
list.add(data.get(i));
}
}
String t = comboBox.getEditor().getText();
comboBox.setItems(list);
comboBox.getEditor().setText(t);
if(!moveCaretToPos) {
caretPos = -1;
}
moveCaret(t.length());
if(!list.isEmpty()) {
comboBox.show();
}
}
private void moveCaret(int textLength) {
if(caretPos == -1) {
comboBox.getEditor().positionCaret(textLength);
} else {
comboBox.getEditor().positionCaret(caretPos);
}
moveCaretToPos = false;
}
}
Code: Demo.java
package demo;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
*
* #author vikassingh
*/
public class Demo extends Application {
private final ObservableList<Employee> data
= FXCollections.observableArrayList(
new Employee("Azamat", 2200.15),
new Employee("Veli", 1400.0),
new Employee("Nurbek", 900.5));
#Override
public void start(Stage primaryStage) {
ComboBox<Employee> combobox = new ComboBox<>(data);
new AutoCompleteComboBoxListener<>(combobox);
combobox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Employee>() {
#Override
public void changed(ObservableValue<? extends Employee> arg0, Employee arg1, Employee arg2) {
if (arg2 != null) {
System.out.println("Selected employee: " + arg2.getName());
System.out.println("Salary: " + arg2.getSalary());
}
}
});
StackPane root = new StackPane();
root.getChildren().add(combobox);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
public static class Employee {
private String name;
private Double salary;
#Override
public String toString() {
return name;
}
public Employee(String name, Double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
}
public static void main(String[] args) {
launch(args);
}
}
Replace your start() method with the following start() method and understand the code.
public void start(Stage primaryStage) {
ComboBox<Employee> combobox = new ComboBox<>(data);
new AutoCompleteComboBoxListener<>(combobox);
combobox.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Selected employee: " + combobox.getSelectionModel().getSelectedItem());
System.out.println("Salary: " + combobox.getSelectionModel().getSelectedItem().getSalary());
}
});
combobox.setConverter(new StringConverter<Employee>() {
#Override
public String toString(Employee object) {
if (object == null) return null;
return object.toString();
}
#Override
public Employee fromString(String string) {
Employee employee = new Employee();
for(Employee emp : combobox.getItems()){
if(emp.getName().equals(string)){
employee = emp;
break;
}
}
return employee;
}
});
StackPane root = new StackPane();
root.getChildren().add(combobox);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
Since you have used editable true in comboBox you need to call combobox.setconverter() function as given in the above code.
I hope it will help you.
I've set up a multiselection enabled tableview and am trying to attach a listener a checkbox inserted into a column to the selection model of the table.
checkBoxTableColumn.setCellValueFactory(
cellData -> {
CheckBox checkBox = new CheckBox();
ObjectProperty<CheckBox> sop = new SimpleObjectProperty<CheckBox>();
sop.setValue(checkBox);
sop.getValue().setText("");
sop.getValue().selectedProperty().addListener(
(obsv, oldv, newv) -> {
ArrayList<Job> tempSelectionArray = new ArrayList<>();
if(newv.booleanValue()){
tempSelectionArray.addAll(jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
for(Job job: tempSelectionArray){
jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
else{
tempSelectionArray.addAll(this.jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
tempSelectionArray.remove(getJobTableView().getFocusModel().getFocusedItem());
for(Job job: tempSelectionArray){
this.jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
}
);
ObservableValue<CheckBox> ov = sop;
return ov;
}
But that doesn't change the table selection.
Edited as jurge stated
checkBoxTableColumn.setCellFactory(new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
#Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxCell(jobTableView);
}
});
and checkbox cell is as
class CheckBoxCell extends TableCell<Job, Boolean>{
private CheckBox checkBox;
private TableView<Job> jobTableView;
public CheckBoxCell(TableView<Job> tableView){
this.jobTableView = tableView;
}
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
checkBox = new CheckBox();
setGraphic(checkBox);
checkBox.selectedProperty().addListener(
(obsv, oldv, newv) -> {
ArrayList<Job> tempSelectionArray = new ArrayList<>();
if(newv.booleanValue()){
tempSelectionArray.addAll(jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
for(Job job: tempSelectionArray){
jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
else{
tempSelectionArray.addAll(this.jobTableView.getSelectionModel().getSelectedItems().stream().collect(Collectors.toList()));
this.jobTableView.getSelectionModel().clearSelection();
tempSelectionArray.remove(jobTableView.getFocusModel().getFocusedItem());
for(Job job: tempSelectionArray){
this.jobTableView.getSelectionModel().select(job);
}
tempSelectionArray.clear();
}
}
);
}
}
The listener wouldn't work this time.....
edit-#2
in initialize method of controller:
jobTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
checkBoxTableColumn.setEditable(true);
checkBoxTableColumn.setCellValueFactory(
new PropertyValueFactory<Job, Boolean>("isContextSelected")
);
checkBoxTableColumn.setCellFactory(
new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
#Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxTableCell<Job, Boolean>(){
{
setAlignment(Pos.CENTER);
}
#Override
public void updateItem(Boolean item, boolean empty){
if(!empty){
TableRow row = getTableRow();
if(row != null){
Integer rowNumber = row.getIndex();
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
if(item){
sm.select(rowNumber);
}
else{
sm.clearSelection(rowNumber);
}
}
}
super.updateItem(item, empty);
}
};
}
}
);
the job class:
private BooleanProperty isContextSelected;
public BooleanProperty isContextSelectedProperty() {
return isContextSelected;
}
edit--
Ignore the unnecessary parts. The whole code as requested.:
The controller:
package BillControl.view;
import BillControl.Controller.PopulateView;
import BillControl.Controller.Validator;
import BillControl.MainApp;
import BillControl.model.Article;
import BillControl.model.Job;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.TreeSet;
public class ArticleJobAssignmentController {
private Article article;
private Stage jobAssignStage;
private boolean okClicked = false;
private MainApp mainApp;
ArrayList<Job> selectedJobList = new ArrayList<>();
private ObservableList<Job> masterJobList = FXCollections.observableArrayList();
private ObservableList<Job> currentJobList = FXCollections.observableArrayList();
private ObservableList<Job> articleEngagementList = FXCollections.observableArrayList();
private TreeSet rowIndices = new TreeSet();
#FXML
private Label articleNameLabel;
#FXML
private Label noOfJobsLabel;
#FXML
private Button okButton;
#FXML
private Button cancelButton;
#FXML
private Label errorLabel;
#FXML
private TableView<Job> jobTableView;
#FXML
private TableColumn<Job, Boolean> checkBoxTableColumn;
#FXML
private TableColumn<Job, String> jobNameColumn;
#FXML
private TableColumn<Job, String> clientNameColumn;
#FXML
private TableColumn<Job, Integer> noOfArticlesColumn;
#FXML
private TableColumn<Job, String> alreadyEngagedColumn;
public ArticleJobAssignmentController(){
}
public void initialize(){
errorLabel.setVisible(false);
jobTableView.setEditable(true);
jobTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
checkBoxTableColumn.setEditable(true);
checkBoxTableColumn.setCellValueFactory(
new PropertyValueFactory<Job, Boolean>("isContextSelected")
);
checkBoxTableColumn.setCellFactory(
new Callback<TableColumn<Job, Boolean>, TableCell<Job, Boolean>>() {
#Override
public TableCell<Job, Boolean> call(TableColumn<Job, Boolean> param) {
return new CheckBoxTableCell<Job, Boolean>(){
{
setAlignment(Pos.CENTER);
}
#Override
public void updateItem(Boolean item, boolean empty){
if(!empty){
TableRow row = getTableRow();
if(row != null){
Integer rowNumber = row.getIndex();
TableView.TableViewSelectionModel sm = getTableView().getSelectionModel();
if(item){
sm.select(rowNumber);
}
else{
sm.clearSelection(rowNumber);
}
}
}
super.updateItem(item, empty);
}
};
}
}
);
jobNameColumn.setCellValueFactory(
cellData -> cellData.getValue().nameProperty()
);
noOfArticlesColumn.setCellValueFactory(
cellData -> {
SimpleIntegerProperty sip = new SimpleIntegerProperty(cellData.getValue().numberOfArticlesProperty().getValue());
ObservableValue<Integer> ov = sip.asObject();
return ov;
}
);
alreadyEngagedColumn.setCellValueFactory(
cellData -> {
Boolean engaged = false;
for(Job job: articleEngagementList){
if(job.getNumberID().equals(cellData.getValue().getNumberID())){
engaged = true;
}
}
if(engaged){
SimpleStringProperty sbp = new SimpleStringProperty("Yes");
ObservableValue<String> ov = sbp;
return ov;
}
else {
SimpleStringProperty sbp = new SimpleStringProperty("No");
ObservableValue<String> ov = sbp;
return ov;
}
}
);
jobTableView.getSelectionModel().getSelectedItems().addListener(
new ListChangeListener<Job>() {
#Override
public void onChanged(Change<? extends Job> c) {
noOfJobsLabel.setText(String.valueOf(c.getList().size()));
}
}
);
}
public void filterMasterList(){
for(Job job : masterJobList){
if(!job.getIsCompleted()){
currentJobList.add(job);
}
}
for(Job currentJob : currentJobList){
currentJob.setIsContextSelected(false);
}
}
#FXML
public void handleOkClicked(){
if(!Validator.articleJobAssignment(this)){
for(Job job : jobTableView.getSelectionModel().getSelectedItems()){
selectedJobList.add(job);
}
okClicked = true;
jobAssignStage.close();
}
else {
errorLabel.setText("Select at least one job");
errorLabel.setVisible(true);
}
}
#FXML
public void handleCancelClicked(){
jobAssignStage.close();
}
public void setArticle(Article article) {
this.article = article;
articleNameLabel.setText(article.getName());
}
public void setJobAssignStage(Stage jobAssignStage) {
this.jobAssignStage = jobAssignStage;
}
public void setOkClicked(boolean okClicked) {
this.okClicked = okClicked;
}
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
setMasterJobList(mainApp.getJobObservableList());
filterMasterList();
jobTableView.setItems(currentJobList);
if(article != null){
articleEngagementList = PopulateView.articleCurrentEngagementList(articleEngagementList, article.getId(), mainApp.getClientObservableList());
}
}
public Label getArticleNameLabel() {
return articleNameLabel;
}
public Label getNoOfJobsLabel() {
return noOfJobsLabel;
}
public Button getOkButton() {
return okButton;
}
public Button getCancelButton() {
return cancelButton;
}
public TableView<Job> getJobTableView() {
return jobTableView;
}
public TableColumn<Job, String> getJobNameColumn() {
return jobNameColumn;
}
public TableColumn<Job, String> getClientNameColumn() {
return clientNameColumn;
}
public TableColumn<Job, Integer> getNoOfArticlesColumn() {
return noOfArticlesColumn;
}
public ObservableList<Job> getMasterJobList() {
return masterJobList;
}
public void setMasterJobList(ObservableList<Job> masterJobList) {
this.masterJobList = masterJobList;
}
public boolean isOkClicked() {
return okClicked;
}
public ArrayList<Job> getSelectedJobList() {
return selectedJobList;
}
}
the job class(ignore the constructors):
package BillControl.model;
import BillControl.Controller.PopulateItems;
import BillControl.GeneralUtils.DateUtil;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Job {
private String numberID;
private StringProperty name;
private ObjectProperty<Client> client;
private StringProperty clientName;
private ObjectProperty<Date> startPeriod;
private ObjectProperty<Date> endPeriod;
private ObjectProperty<Date> startDate;
private ObjectProperty<Date> targetDate;
private BooleanProperty isDelayed;
private LongProperty remainingDays;
private LongProperty delayedDays;
private BooleanProperty isCompleted;
private ObjectProperty<Date> completionDate;
private LongProperty daysToComplete;
private StringProperty partner;
private IntegerProperty numberOfArticles;
private BooleanProperty isContextSelected;
private String clientID;
public Job(Client client){
this.numberID = null;
this.client = new SimpleObjectProperty<Client>(client);
this.clientName = new SimpleStringProperty(client.getName());
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.fillOthers(false);
// todo check fill others logic
}
public Job(ObservableList clientList, String numberID){
this.numberID = numberID;
// this.numberID = null;
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.client = new SimpleObjectProperty<Client>();
this.clientName = new SimpleStringProperty();
this.numberOfArticles = new SimpleIntegerProperty();
this.isContextSelected = new SimpleBooleanProperty(false);
PopulateItems.populateJob(this);
Client selectedClient = null;
for(Object clientObject : clientList){
Client queriedClient = (Client) clientObject;
String name = queriedClient.getName();
String queriedName = PopulateItems.clientID2NameHelper(this.getClientID());
if(name.equals(queriedName)){
selectedClient = (Client) clientObject;
break;
}
}
this.setClient(selectedClient);
this.setClientName(this.getClient().getName());
this.fillOthers(true);
}
public Job(){
this.numberID = null;
this.client = new SimpleObjectProperty<Client>();
this.clientName = new SimpleStringProperty("");
this.name = new SimpleStringProperty("");
this.partner = new SimpleStringProperty("");
this.startDate = new SimpleObjectProperty<Date>();
this.targetDate = new SimpleObjectProperty<Date>();
this.completionDate = new SimpleObjectProperty<Date>();
this.isCompleted = new SimpleBooleanProperty(false);
this.startPeriod = new SimpleObjectProperty<Date>();
this.endPeriod = new SimpleObjectProperty<Date>();
this.fillOthers(false);
}
public void fillOthers(Boolean filledJob){
if(filledJob){
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
try {
Date startDate = this.getStartDate();
Date completionDate = this.getCompletionDate();
Date currentDate = DateUtil.getCurrentDate();
Date targetDate = this.getTargetDate();
if (this.getIsCompleted()){
// completion days
this.daysToComplete = new SimpleLongProperty();
long duration = completionDate.getTime() - startDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setDaysToComplete(diffInDays);
}
else{
this.remainingDays = new SimpleLongProperty();
this.isDelayed = new SimpleBooleanProperty();
if (targetDate.after(currentDate) && !this.getIsCompleted()){
// remaining days
long duration = targetDate.getTime() - currentDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setRemainingDays(diffInDays);
this.setIsDelayed(false);
}
else if (targetDate.before(currentDate) && !this.getIsCompleted()) {
// delayed days
this.delayedDays = new SimpleLongProperty();
this.setIsDelayed(true);
long duration = currentDate.getTime() - targetDate.getTime();
long diffInDays = TimeUnit.MILLISECONDS.toDays(duration);
this.setRemainingDays(0);
this.setDelayedDays(diffInDays);
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
else {
//TODO client creation form job
}
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public Client getClient() {
return client.get();
}
public ObjectProperty<Client> clientProperty() {
return client;
}
public void setClient(Client client) {
this.client.set(client);
}
public Date getStartDate() {
return startDate.get();
}
public ObjectProperty<Date> startDateProperty() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate.set(startDate);
}
public Date getTargetDate() {
return targetDate.get();
}
public ObjectProperty<Date> targetDateProperty() {
return targetDate;
}
public void setTargetDate(Date targetDate) {
this.targetDate.set(targetDate);
}
public boolean getIsDelayed() {
return isDelayed.get();
}
public BooleanProperty isDelayedProperty() {
return isDelayed;
}
public void setIsDelayed(boolean isDelayed) {
this.isDelayed.set(isDelayed);
}
public long getRemainingDays() {
return remainingDays.get();
}
public LongProperty remainingDaysProperty() {
return remainingDays;
}
public void setRemainingDays(long remainingDays) {
this.remainingDays.set(remainingDays);
}
public long getDelayedDays() {
return delayedDays.get();
}
public LongProperty delayedDaysProperty() {
return delayedDays;
}
public void setDelayedDays(long delayedDays) {
this.delayedDays.set(delayedDays);
}
public boolean getIsCompleted() {
return isCompleted.get();
}
public BooleanProperty isCompletedProperty() {
return isCompleted;
}
public void setIsCompleted(boolean isCompleted) {
this.isCompleted.set(isCompleted);
}
public Date getCompletionDate() {
return completionDate.get();
}
public ObjectProperty<Date> completionDateProperty() {
return completionDate;
}
public void setCompletionDate(Date completionDate) {
this.completionDate.set(completionDate);
}
public long getDaysToComplete() {
return daysToComplete.get();
}
public LongProperty daysToCompleteProperty() {
return daysToComplete;
}
public void setDaysToComplete(long daysToComplete) {
this.daysToComplete.set(daysToComplete);
}
public String getPartner() {
return partner.get();
}
public StringProperty partnerProperty() {
return partner;
}
public void setPartner(String partner) {
this.partner.set(partner);
}
public Integer getNumberOfArticles() {
return numberOfArticles.get();
}
public IntegerProperty numberOfArticlesProperty() {
return numberOfArticles;
}
public void setNumberOfArticles(int numberOfArticles) {
this.numberOfArticles.set(numberOfArticles);
}
public String getNumberID() {
return numberID;
}
public String getClientName() {
return clientName.get();
}
public StringProperty clientNameProperty() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName.set(clientName);
}
public Date getStartPeriod() {
return startPeriod.get();
}
public ObjectProperty<Date> startPeriodProperty() {
return startPeriod;
}
public void setStartPeriod(Date startPeriod) {
this.startPeriod.set(startPeriod);
}
public Date getEndPeriod() {
return endPeriod.get();
}
public ObjectProperty<Date> endPeriodProperty() {
return endPeriod;
}
public void setEndPeriod(Date endPeriod) {
this.endPeriod.set(endPeriod);
}
public String getClientID() {
return clientID;
}
public void setClientID(String clientID) {
this.clientID = clientID;
}
public boolean getIsContextSelected() {
return isContextSelected.get();
}
public BooleanProperty isContextSelectedProperty() {
return isContextSelected;
}
public void setIsContextSelected(boolean isContextSelected) {
this.isContextSelected.set(isContextSelected);
}
}
Ok, so this one way to do it. You'll need a BooleanProperty in your backing model to hold the value of the check box so that the table will 'remember' if that rows check box should be selected or not if the row scrolls out of view and then back again.
TableColumn<Job,Boolean> checkCol = new TableColumn<>("Check");
checkCol.setCellValueFactory( new PropertyValueFactory<Job,Boolean>( "checkBoxValue" ) );
checkCol.setCellFactory( new Callback<TableColumn<Job,Boolean>, TableCell<Job,Boolean>>()
{
#Override
public TableCell<Job,Boolean> call( TableColumn<Job,Boolean> param )
{
return new CheckBoxTableCell<Job,Boolean>()
{
{
setAlignment( Pos.CENTER );
}
#Override
public void updateItem( Boolean item, boolean empty )
{
if ( ! empty )
{
TableRow row = getTableRow();
if ( row != null )
{
int rowNo = row.getIndex();
TableViewSelectionModel sm = getTableView().getSelectionModel();
if ( item ) sm.select( rowNo );
else sm.clearSelection( rowNo );
}
}
super.updateItem( item, empty );
}
};
}
} );
checkCol.setEditable( true );
checkCol.setMaxWidth( 50 );
checkCol.setMinWidth( 50 );
You are using checkBoxTableColumn.setCellValueFactory incorrectly.
Your TableView has data items of type T, and the setCellValueFactory method on a column is there to tell the column what value it must extract out of an object of type T to display.
You however are returning an observable value containing a GUI component (CheckBox), whereas you should be returning an observable Boolean value extracted from cellData.
See here for an Oracle tutorial on TableView: http://docs.oracle.com/javafx/2/ui_controls/table-view.htm#CJAGAAEE
Adding a checkbox column to a table where changes to the table checkbox are propogated back to the model object is quite simple:
TableColumn<Job,Boolean> checkCol = new TableColumn<>("Check");
checkCol.setCellValueFactory( new PropertyValueFactory<Job,Boolean>( "checkBoxValue" ) );
checkCol.setCellFactory( CheckBoxTableCell.forTableColumn( checkCol ) );
Note that "checkBoxValue" is the partial name of a property method in Job called checkBoxValueProperty() that returns a BooleanProperty. (It doesn't have to be called checkBoxValue, you can give it a different name, but it must end with Property.)
In my JavaFX-program I use a TableCell where you can edit a value. Like shown at the examples on the JavaFX-page "Example", I use this function to save the changes (function is set on TextField in edit Cell)
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent t) {
if (combo.match(t)) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
When using ENTER to leave the cell, the value is changed / saved, but how can I change / save the value, when leaving the cell by clicking in another cell? Actually the value is resetted.
Thanks
Basti
Listening to a change in focus on the TextField is one way.. I added a listener to the focusedProperty of the textField. The example from Oracle didn't include this.
[edit - here is a link to another question that has a different approach UITableView - Better Editing through Binding? ]
private void createTextField() {
textField = new TextField(getItem());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
// Detect a change in focus on the text field.. If we lose the focus we take appropriate action
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(!newValue.booleanValue())
commitEdit(textField.getText());
}
} );
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;
class CellGenerator {
/***************************************************************************
* * Private fields * *
**************************************************************************/
private final static StringConverter defaultStringConverter = new StringConverter<Object>() {
#Override
public String toString(Object t) {
return t == null ? null : t.toString();
}
#Override
public Object fromString(String string) {
return (Object) string;
}
};
static <T> StringConverter<T> defaultStringConverter() {
return (StringConverter<T>) defaultStringConverter;
}
private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) {
return converter == null ? cell.getItem() == null ? "" : cell.getItem().toString()
: converter.toString(cell.getItem());
}
/***************************************************************************
* * TextField convenience * *
**************************************************************************/
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final TextField textField) {
updateItem(cell, converter, null, null, textField);
}
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox,
final Node graphic, final TextField textField) {
if (cell.isEmpty()) {
cell.setText(null);
cell.setGraphic(null);
} else {
if (cell.isEditing()) {
if (textField != null) {
textField.setText(getItemText(cell, converter));
}
cell.setText(null);
if (graphic != null) {
hbox.getChildren().setAll(graphic, textField);
cell.setGraphic(hbox);
} else {
cell.setGraphic(textField);
}
} else {
cell.setText(getItemText(cell, converter));
cell.setGraphic(graphic);
}
}
}
static <T> void startEdit(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox,
final Node graphic, final TextField textField) {
if (textField != null) {
textField.setText(getItemText(cell, converter));
}
cell.setText(null);
if (graphic != null) {
hbox.getChildren().setAll(graphic, textField);
cell.setGraphic(hbox);
} else {
cell.setGraphic(textField);
}
textField.selectAll();
// requesting focus so that key input can immediately go into the
// TextField (see RT-28132)
textField.requestFocus();
}
static <T> void cancelEdit(Cell<T> cell, final StringConverter<T> converter, Node graphic) {
cell.setText(getItemText(cell, converter));
cell.setGraphic(graphic);
}
static <T> TextField createTextField(final Cell<T> cell, final StringConverter<T> converter) {
final TextField textField = new TextField(getItemText(cell, converter));
EdittingCell cellEdit=(EdittingCell)cell;
textField.setOnMouseExited(event -> {
if (converter == null) {
throw new IllegalStateException("Attempting to convert text input into Object, but provided "
+ "StringConverter is null. Be sure to set a StringConverter "
+ "in your cell factory.");
}
cell.commitEdit(converter.fromString(textField.getText()));
});
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
cell.cancelEdit();
event.consume();
} else if (event.getCode() == KeyCode.RIGHT) {
cellEdit.getTableView().getSelectionModel().selectRightCell();
event.consume();
} else if (event.getCode() == KeyCode.LEFT) {
cellEdit.getTableView().getSelectionModel().selectLeftCell();
event.consume();
} else if (event.getCode() == KeyCode.UP) {
cellEdit.getTableView().getSelectionModel().selectAboveCell();
event.consume();
} else if (event.getCode() == KeyCode.DOWN) {
cellEdit.getTableView().getSelectionModel().selectBelowCell();
event.consume();
} else if (event.getCode() == KeyCode.ENTER) {
if (converter == null) {
throw new IllegalStateException("Attempting to convert text input into Object, but provided "
+ "StringConverter is null. Be sure to set a StringConverter "
+ "in your cell factory.");
}
cell.commitEdit(converter.fromString(textField.getText()));
event.consume();
}
else if (event.getCode() == KeyCode.TAB) {
cell.commitEdit(converter.fromString(textField.getText()));
cellEdit.setNextColumn(event);
event.consume();
}
});
return textField;
}}
//the table cell
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
public class EdittingCell<S, T> extends TableCell<S, T> {
public static <S> Callback<TableColumn<S, String>, TableCell<S, String>>
forTableColumn() {
return forTableColumn(new DefaultStringConverter());
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>>
forTableColumn(
final StringConverter<T> converter) {
return new Callback<TableColumn<S, T>, TableCell<S, T>>() {
#Override
public TableCell<S, T> call(TableColumn<S, T> list) {
return new EdittingCell<S, T>(converter);
}
};
}
public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>>
forTableColumn(final StringConverter<T> converter,
final boolean isFieldEditable) {
return new Callback<TableColumn<S, T>, TableCell<S, T>>() {
#Override
public TableCell<S, T> call(TableColumn<S, T> list) {
return new EdittingCell<S, T>(converter, isFieldEditable);
}
};
}
/***************************************************************************
* * Fields * *
**************************************************************************/
public TextField textField;
private static int currentRow = -1;
private static int control = 0;
public EdittingCell() {
this(null);
textField = CellGenerator.createTextField(this, getConverter());
}
public EdittingCell(StringConverter<T> converter) {
this.getStyleClass().add("text-field-table-cell");
setConverter(converter);
textField = CellGenerator.createTextField(this, getConverter());
// textField.setEditable(false);
}
public EdittingCell(StringConverter<T> converter, boolean isFieldEditable) {
this.getStyleClass().add("text-field-table-cell");
setConverter(converter);
textField = CellGenerator.createTextField(this, getConverter());
textField.setEditable(isFieldEditable);
}
/***************************************************************************
* * Properties * *
**************************************************************************/
// --- converter
private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<StringConverter<T>>(this,
"converter");
public final ObjectProperty<StringConverter<T>> converterProperty() {
return converter;
}
public TextField getTextFiedCell() {
return textField;
}
public final void setConverter(StringConverter<T> value) {
converterProperty().set(value);
}
public final StringConverter<T> getConverter() {
return converterProperty().get();
}
#Override
public void startEdit() {
if (!isEditable() || !getTableView().isEditable() || !getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
CellGenerator.startEdit(this, getConverter(), null, null, textField);
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
CellGenerator.cancelEdit(this, getConverter(), null);
}
/** {#inheritDoc} */
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
CellGenerator.updateItem(this, getConverter(), null, null, textField);
// System.out.println("Silas");
}
public TableView<S> getContextTableView() {
return getTableView();
}
public void setNextColumn(KeyEvent event) {
TableColumn nextColumn = getNextColumn(!event.isShiftDown());
if (nextColumn != null) {
// Get Selected index to reset current editable row
int selectedRow = getTableRow().getIndex();
// Set row that serves as a control for tapping through
if (currentRow == -1) {
currentRow = getTableRow().getIndex();
}
// Reset editing upon selection change row
if (currentRow != selectedRow) {
currentRow = selectedRow;
}
int colSize = getTableView().getColumns().size();
int colindex = getTableView().getColumns().indexOf(nextColumn);
if (colindex == colSize - 1) {
control++;
}
if (control > 0 && colindex == 0) {
currentRow++;
}
if (getTableView().getItems().size() > currentRow) {
getTableView().edit(currentRow, nextColumn);
// getTableView().getSelectionModel().select(currentRow,
// nextColumn);
} else {
currentRow = 0;
// getTableView().getSelectionModel().select(currentRow,
// nextColumn);
getTableView().edit(currentRow, nextColumn);
}
}
}
private TableColumn<S, ?> getNextColumn(boolean forward) {
List<TableColumn<S, ?>> columns = new ArrayList<>();
for (TableColumn<S, ?> column : getTableView().getColumns()) {
columns.addAll(getLeaves(column));
}
// There is no other column that supports editing.
if (columns.size() < 2) {
return null;
}
int currentIndex = columns.indexOf(getTableColumn());
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return columns.get(nextIndex);
}
private ObservableList<TableColumn<S, ?>> getLeaves(TableColumn<S, ?> column2) {
ObservableList<TableColumn<S, ?>> columns = FXCollections.observableArrayList();
if (column2.getColumns().isEmpty()) {
// We only want the leaves that are editable.
if (column2.isEditable()) {
columns.addAll(column2);
}
return columns;
} else {
for (TableColumn<S, ?> column : column2.getColumns()) {
columns.addAll(getLeaves(column));
}
return columns;
}
}
}
//How to use this
TableColumn<NewInvoice, BigDecimal> quantityCol = new
TableColumn<NewInvoice, BigDecimal>("Quantity");
quantityCol.setCellValueFactory(cellData ->
cellData.getValue().quantityProperty());
quantityCol.setCellFactory(EdittingCell.forTableColumn( new
BigDecimalStringConverter()));
quantityCol.setStyle("-fx-alignment:CENTER-RIGHT;");
quantityCol.setOnEditCommit(new EventHandler<CellEditEvent<NewInvoice,
BigDecimal>>() {
#Override
public void handle(CellEditEvent<NewInvoice, BigDecimal> t) {
t.getTableView().getItems().get(t.getTablePosition().getRow()
).setQuantity(t.getNewValue());
}
});