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'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.
I am learning JavaFX and implementing a TableView class. I'd like to make a cell editable without first pressing Enter or double clicking on it. I wonder if it's possible to start entering a new value without first hitting Enter? Thank you.
Looks like I've found a solution to the problem of missing first entered symbols. Data can be entered into a cell as soon as the cell is in focus. There is no necessity to press Enter first or double click on a cell before data input.
Class CellField
//Text box cell
public class CellField {
private static StringBuffer text = new StringBuffer("");
public static String getText() {
return text.toString();
}
public static void setText(String text) {
CellField.text = new StringBuffer(text);
}
//true, if the length of more than one character
public static boolean isLessOrEqualOneSym(){
return CellField.text.length() <= 1;
}
//add character to the end of line
public static void addSymbol(String symbol){
text.append(symbol);
}
public static void clearText() {
setText("");
}
}
Class NewOrderCtrl(part of the code)
class public class NewOrderCtrl extends HBox implements Initializable {
#FXML private TableView<OrderItem> catalogTable;
#FXML private TableColumn<OrderItem, String> numCatalogColumn;
public void initialize(URL url, ResourceBundle resourceBundle) {
numCatalogColumn.setCellFactory(new Callback<TableColumn<OrderItem, String>, TableCell<OrderItem, String>>() {
#Override
public TableCell<OrderItem, String> call(TableColumn<OrderItem, String> orderItemStringTableColumn) {
return new EditingCell();
}
});
catalogTable.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
KeyCode keyCode = keyEvent.getCode();
if (keyCode == KeyCode.ENTER || keyCode == KeyCode.ESCAPE){
CellField.clearText();
}
if (keyCode.isDigitKey()) {
int row = catalogTable.getSelectionModel().getSelectedIndex();
catalogTable.edit(row, numCatalogColumn);
}
}
});
}
#FXML
private void onEditStart() {
CellField.clearText();
}
}
Class EditingCell
public class EditingCell extends TableCell<OrderItem, String> {
private TextField textField;
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(String.valueOf(getItem()));
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
} else {
setText(getString());
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
EditingCell.this.getTableView().requestFocus();//why does it lose focus??
EditingCell.this.getTableView().getSelectionModel().selectBelowCell();
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode().isDigitKey()) {
if (CellField.isLessOrEqualOneSym()) {
CellField.addSymbol(t.getText());
} else {
CellField.setText(textField.getText());
}
textField.setText(CellField.getText());
textField.deselect();
textField.end();
textField.positionCaret(textField.getLength() + 2);//works sometimes
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem();
}
}
I've finally got everything working how I like it. I've added some formatting stuff since I needed to test that. Users will have to enter some data and the closer it is to excel the easier it will be for most people to use.
Make a new javaFX project called TableTest in package easyedit and paste these files in the right class names.
TableTest.java
package easyedit;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableTest extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<LineItem> items = FXCollections.observableArrayList();
items.addAll(new LineItem("hello",123.45,6),
new LineItem("world",0.01,11));
TableView table = new EasyEditTable().makeTable(items);
Button focusableNode = new Button("Nada");
VBox root = new VBox();
root.getChildren().addAll(table, focusableNode);
Scene scene = new Scene(root, 300, 250);
//css to remove empty lines in table
scene.getStylesheets().add(this.getClass().getResource("css.css").toExternalForm());
primaryStage.setTitle("Easy edit table test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
LineItem.java
package easyedit;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class LineItem {
private final StringProperty desc = new SimpleStringProperty();
private final DoubleProperty amount = new SimpleDoubleProperty();
private final IntegerProperty sort = new SimpleIntegerProperty();
public StringProperty descProperty() {return desc;}
public DoubleProperty amountProperty() {return amount;}
public IntegerProperty sortProperty() {return sort;}
public LineItem(String dsc, double amt, int srt) {
desc.set(dsc); amount.set(amt); sort.set(srt);
}
}
EasyEditTable.java
package easyedit;
import java.text.NumberFormat;
import java.util.Stack;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
public class EasyEditTable{
private String lastKey = null;
public TableView makeTable(ObservableList<LineItem> items) {
TableView tv = new TableView(items);
tv.setEditable(true);
Stack<LineItem> deletedLines = new Stack<>();
tv.setUserData(deletedLines);
Callback<TableColumn<LineItem,String>, TableCell<LineItem,String>> txtCellFactory =
(TableColumn<LineItem,String> p) -> {return new EditingCell();};
TableColumn<LineItem,String> descCol = new TableColumn<>("desc");
descCol.setCellValueFactory(new PropertyValueFactory<>("desc"));
descCol.setCellFactory(txtCellFactory);
descCol.setOnEditCommit((TableColumn.CellEditEvent<LineItem, String> evt) -> {
evt.getTableView().getItems().get(evt.getTablePosition().getRow())
.descProperty().setValue(evt.getNewValue());
});
final NumberFormat currFmt = NumberFormat.getCurrencyInstance();
TableColumn<LineItem, String> amountCol = new TableColumn<>("amount");
amountCol.setCellValueFactory((TableColumn.CellDataFeatures<LineItem, String> p) -> {
return new SimpleStringProperty(currFmt.format(p.getValue().amountProperty().get()));
});
amountCol.setCellFactory(txtCellFactory);
amountCol.setOnEditCommit((TableColumn.CellEditEvent<LineItem, String> evt) -> {
try {
evt.getTableView().getItems().get(evt.getTablePosition().getRow())
.amountProperty().setValue(Double.parseDouble(evt.getNewValue().replace("$","")));
} catch (NumberFormatException nfe) {
//handle error properly somehow
}
});
amountCol.setComparator((String o1, String o2) -> {
try {//only works in $ countries, use currFmt.parse() instead
return Double.compare(Double.parseDouble(o1.replace("$", "")),
Double.parseDouble(o2.replace("$", "")));
} catch (NumberFormatException numberFormatException) {
return 0;
}
});
TableColumn<LineItem,String> sortCol = new TableColumn<>("sort");
sortCol.setCellValueFactory(new PropertyValueFactory("sort"));
sortCol.setCellFactory(txtCellFactory);
sortCol.setOnEditCommit((TableColumn.CellEditEvent<LineItem, String> evt) -> {
evt.getTableView().getItems().get(evt.getTablePosition().getRow())
.sortProperty().setValue(Integer.parseInt(evt.getNewValue()));//throws nfe
});
tv.getColumns().setAll(descCol, amountCol, sortCol);
tv.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tv.getSelectionModel().setCellSelectionEnabled(true);
tv.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
tv.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent t) -> {
if (tv.getEditingCell() == null && t.getCode() == KeyCode.ENTER) {
if (t.isShiftDown()) {
tv.getSelectionModel().selectAboveCell();
} else {
tv.getSelectionModel().selectBelowCell();
}
t.consume();
}
//I decided not to override the default tab behavior
//using ctrl tab for cell traversal, but arrow keys are better
if (t.isControlDown() && t.getCode() == KeyCode.TAB) {
if (t.isShiftDown()) {
tv.getSelectionModel().selectLeftCell();
} else {
tv.getSelectionModel().selectRightCell();
}
t.consume();
}
});
tv.setOnKeyPressed((KeyEvent t) -> {
TablePosition tp;
if (!t.isControlDown() &&
(t.getCode().isLetterKey() || t.getCode().isDigitKey())) {
lastKey = t.getText();
tp = tv.getFocusModel().getFocusedCell();
tv.edit(tp.getRow(),tp.getTableColumn());
lastKey = null;
}
});
tv.setOnKeyReleased((KeyEvent t) -> {
TablePosition tp;
switch (t.getCode()) {
case INSERT:
items.add(new LineItem("",0d,0));//maybe try adding at position
break;
case DELETE:
tp = tv.getFocusModel().getFocusedCell();
if (tp.getTableColumn() == descCol) {
deletedLines.push(items.remove(tp.getRow()));
} else { //maybe delete cell value
}
break;
case Z:
if (t.isControlDown()) {
if (!deletedLines.isEmpty()) {
items.add(deletedLines.pop());
}
}
}
});
return tv;
}
private class EditingCell extends TableCell{
private TextField textField;
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
//setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
Platform.runLater(() -> {//without this space erases text, f2 doesn't
textField.requestFocus();//also selects
});
if (lastKey != null) {
textField.setText(lastKey);
Platform.runLater(() -> {
textField.deselect();
textField.end();
});
}
}
}
public void commit(){
commitEdit(textField.getText());
}
#Override
public void cancelEdit() {
super.cancelEdit();
try {
setText(getItem().toString());
} catch (Exception e) {}
setGraphic(null);
}
#Override
public void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
if (getTableColumn().getText().equals("amount"))
setAlignment(Pos.CENTER_RIGHT);
}
}
private void createTextField() {
textField = new TextField(getString());
//doesn't work if clicking a different cell, only focusing out of table
textField.focusedProperty().addListener(
(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) -> {
if (!arg2) commitEdit(textField.getText());
});
textField.setOnKeyReleased((KeyEvent t) -> {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
EditingCell.this.getTableView().getSelectionModel().selectBelowCell();
}
if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
textField.addEventFilter(KeyEvent.KEY_RELEASED, (KeyEvent t) -> {
if (t.getCode() == KeyCode.DELETE) {
t.consume();//stop from deleting line in table keyevent
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
}
The css.css file if you want to use it. It goes in the same package.
.table-row-cell:empty {
-fx-background-color: ivory;
}
.table-row-cell:empty .table-cell {
-fx-border-width: 0px;
}
I don't have any problems with characters not showing up or blank cells. Just the blinking cursor is sometimes in the wrong place. Using 8-b127 on XP sp3. I don't like how the textField focusListener doesn't work very well, but it's a small issue.
For the TableView (named tv here) I do this
tv.setOnKeyReleased((KeyEvent t) -> {
TablePosition tp;
switch (t.getCode()) {
//other code cut out here
case Z:
if (t.isControlDown()) {
if (!deletedLines.isEmpty()) {
items.add(deletedLines.pop());
}
break; //don't break for regular Z
}
default:
if (t.getCode().isLetterKey() || t.getCode().isDigitKey()) {
lastKey = t.getText();
tp = tv.getFocusModel().getFocusedCell();
tv.edit(tp.getRow(), tp.getTableColumn());
lastKey = null;
}
}
});
And then when I make the TextField editing cell
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
Platform.runLater(() -> {//without this space erases text, f2 doesn't
textField.requestFocus();//also selects
});
if (lastKey != null) {
textField.setText(lastKey);
Platform.runLater(() -> {
textField.deselect();
textField.end();
textField.positionCaret(textField.getLength()+2);//works sometimes
});
}
}
}
Sometimes the blinking cursor shows up at the front of lastKey but when I keep typing the characters go at the end and the cursor moves to the correct position. If you type really fast, the second character gets ignored.
If you can make it better let me know. I'd like it to work more like excel. I also add this to the standard textField code.
textField.setOnKeyReleased((KeyEvent t) -> {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
EditingCell.this.getTableView().requestFocus();//why does it lose focus??
EditingCell.this.getTableView().getSelectionModel().selectBelowCell();
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
I have used the sample Tree User interface on the JAVA FX 2 site and changed the type from String to PayString and all seems to work well. My next step is in the setCellFactory 'TextFieldTreeCellImpl' I need to assign a different context menu to the cells depending upon the value of PayString.level an integer.
How do I reference the value of the data fields associated with the current cell.
Below are the two source files code in packages treeviewsample. The position I need the data is marked with +++++++++++++++++++++++++ for ease of finding.
`/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package treeviewsample;
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.VBox;
public class TreeViewSample extends Application {
private final Node rootIcon;
private final Image depIcon;
List<Employee> employees = Arrays.<Employee>asList(
new Employee(1, "Ethan Williams", "Sales Department"),
new Employee(2, "Emma Jones", "Sales Department"),
new Employee(3, "Michael Brown", "Sales Department"),
new Employee(4, "Anna Black", "Sales Department"),
new Employee(5, "Rodger York", "Sales Department"),
new Employee(6, "Susan Collins", "Sales Department"),
new Employee(7, "Mike Graham", "IT Support"),
new Employee(8, "Judy Mayer", "IT Support"),
new Employee(9, "Gregory Smith", "IT Support"),
new Employee(10, "Jacob Smith", "Accounts Department"),
new Employee(11, "Isabella Johnson", "Accounts Department"));
TreeItem<PayString> rootNode;
Integer next;
public static void main(String[] args) {
Application.launch(args);
}
public TreeViewSample() {
this.next = 12;
this.depIcon = new Image(getClass().getResourceAsStream("department.png"));
this.rootIcon = new ImageView(new Image(getClass().getResourceAsStream("root.png")));
this.rootNode = new TreeItem<>(new PayString ("MyCompany Human Resources", 0,0), rootIcon);
}
#Override
public void start(Stage stage) {
rootNode.setExpanded(true);
for (Employee employee : employees) {
TreeItem<PayString> empLeaf = new TreeItem<>(new PayString(employee.getName(),2,employee.getId()));
boolean found = false;
for (TreeItem<PayString> depNode : rootNode.getChildren()) {
if (depNode.getValue().toString().contentEquals(employee.getDepartment())){
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found) {
TreeItem depNode = new TreeItem<>(new PayString(employee.getDepartment(),1,employee.getId()),
new ImageView(depIcon)
);
rootNode.getChildren().add(depNode);
depNode.getChildren().add(empLeaf);
}
}
stage.setTitle("Tree View Sample");
VBox box = new VBox();
final Scene scene = new Scene(box, 400, 300);
scene.setFill(Color.LIGHTGRAY);
TreeView<PayString> treeView = new TreeView<>(rootNode);
treeView.setEditable(true);
treeView.setCellFactory(new Callback<TreeView<PayString>,TreeCell<PayString>>(){
#Override
public TreeCell<PayString> call(TreeView<PayString> p) {
return new TextFieldTreeCellImpl();
}
});
box.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
private final class TextFieldTreeCellImpl extends TreeCell<PayString> {
private TextField textField;
private ContextMenu addMenu = new ContextMenu();
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* This is where I need to be able to extract the values in the current TreeCell<PayString>
* to be able to create the appropriate context menu.
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
private String curr = getString();
public TextFieldTreeCellImpl() {
TreeItem<PayString> paystring = treeItemProperty().getValue();
MenuItem addMenuItem = new MenuItem("Add Employee");
MenuItem addMenuItem2 = new MenuItem("Add Address");
MenuItem addMenuItem3 = new MenuItem(curr);
addMenu.getItems().add(addMenuItem2);
addMenu.getItems().add(addMenuItem3);
addMenuItem.setOnAction(new EventHandler() {
#Override
public void handle(Event t) {
TreeItem newEmployee =
new TreeItem<>(new PayString ("New Employee",3,next));
next ++;
getTreeItem().getChildren().add(newEmployee);
}
});
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getString());
setGraphic(getTreeItem().getGraphic());
}
#Override
public void updateItem(PayString item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getTreeItem().getGraphic());
if (
!getTreeItem().isLeaf()&&getTreeItem().getParent()!= null
){
setContextMenu(addMenu);
}
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(new PayString (textField.getText(),3,next));
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
public static class Employee {
private final SimpleStringProperty name;
private final SimpleStringProperty department;
private final Integer id;
private Employee(Integer id, String name, String department) {
this.name = new SimpleStringProperty(name);
this.department = new SimpleStringProperty(department);
this.id = new Integer(id);
}
public Integer getId() {
return id;
}
public String getName() {
return name.get();
}
public void setName(String fName) {
name.set(fName);
}
public String getDepartment() {
return department.get();
}
public void setDepartment(String fName) {
department.set(fName);
}
}
}
package treeviewsample;
import java.util.Objects;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author Len
*/
public class PayString {
private final String description;
private final Integer level;
private final Integer id;
public PayString(String description, Integer level, Integer id) {
this.id = id;
this.level = level;
this.description = description;
}
public int getId() {
return id;
}
public int getLevel() {
return level;
}
public String getDescription() {
return description;
}
#Override
public int hashCode() {
int hash = 7;
return hash;
}
public boolean contentEquals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PayString other = (PayString) obj;
if (!Objects.equals(this.description, other.description)) {
return false;
}
return true;
}
#Override
public String toString() {
return description;
}
}
`
You can always access the current data associated with your cell by calling
PayString value = getTreeItem().getValue()
However there is no 1:1 relationship between a cell and its value. JavaFX creates as many TreeCells it needs to display. It then dynamically assigns them to the underlying data items by calling the method
public void updateItem(PayString item, boolean empty) { ... }
This is where you can set a context menu that depends on the current data item. And there you get the data item passed in as parameter anyway.
At the code position you marked current data item will be null.
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());
}
});