0

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.

Custom cell render error

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.

2
  • You're attempting to use the same button for every cell, which you can't do as any node can only appear once in the scene graph. Don't make the button a property of the factory: make it a property of the cell (and instantiate a new one for each cell). Commented Jun 15, 2017 at 23:13
  • @James_D, ineed to put, the button constructor as the cell value? Commented Jun 16, 2017 at 5:33

1 Answer 1

0

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:)

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your response Keyur Bhanderi, i've opted for a similar solution, using external factory, and passing the menu items list to it. Moving build button call inside updateItem override
@PatrickT if the same solution then don't forget to accept the answer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.