JavaFX TableView custom cell rendering split menu

2020-05-04 08:53发布

问题:

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.

回答1:

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