JavaFX TableView自定义单元格渲染分割菜单按钮

问题描述:

我在java fx表视图组件中定制单元格渲染出了问题。我能够渲染分割菜单按钮,但只能从第二行的表格中渲染。JavaFX TableView自定义单元格渲染分割菜单按钮

Custom cell render error

下面我把创建生成图像的代码。

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 包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; 
} 
} 

正如你在图上看到的,与此代码我能够创建spli菜单按钮,并把它添加到TA表,但仅呈现在最后一排。

我需要建议,以使其他行的拆分菜单按钮,任何帮助表示赞赏。

+0

您正在尝试为每个单元使用相同的按钮,而这是您无法做到的,因为任何节点只能在场景图中出现一次。不要让按钮成为工厂的一个属性:使它成为单元格的属性(并为每个单元格实例化一个新的属性)。 –

+0

@James_D,ineed把按钮构造函数作为单元格的值? – PatrickT

原因是您在每个单元格中都使用相同的按钮,所以它仅设置了单元格值的最后一个按钮。

删除在SplitMenuButtonApp类

SplitMenuButton actions = sMBtn.buildButton(); 

此行,并替换此行

aCol.setCellFactory(new ButtonCellFactory<>(actions));

为了下面的代码

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

我希望这个代码是为你工作:)

+0

感谢您回应Keyur Bhanderi,我已经选择了一个类似的解决方案,使用外部工厂,并通过菜单项列表吧。如果相同的解决方案不会忘记接受答案,那么在'updateItem'内移动构建按钮将覆盖 – PatrickT

+0

@PatrickT! –