JavaFX 表 - 如何添加组件?

Posted

技术标签:

【中文标题】JavaFX 表 - 如何添加组件?【英文标题】:JavaFX table- how to add components? 【发布时间】:2013-04-27 22:34:15 【问题描述】:

我有一个 Swing 项目,它使用许多 JTable 来显示各种内容,从文本到面板再到按钮和复选框的混合。我可以通过覆盖表格单元格渲染器以返回通用 JComponents 来做到这一点。我的问题是可以使用 JavaFx 制作类似的表格吗?

我想更新项目中的所有表以使用 JavaFx 来主要支持手势。似乎 TableView 是要使用的 JavaFx 组件,我尝试向它添加按钮,但是在显示时它显示的是按钮的字符串值,而不是按钮本身。看起来我必须覆盖行工厂或单元工厂才能做我想做的事,但例子并不多。这是我用作将按钮显示为字符串的示例的代码。

import javax.swing.JButton;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class GestureEvents extends Application 

    private TableView<Person> table = new TableView<Person>();
    private final ObservableList<Person> data =
        FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com","The Button"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com","The Button"),
            new Person("Ethan", "Williams", "ethan.williams@example.com","The Button"),
            new Person("Emma", "Jones", "emma.jones@example.com","The Button"),
            new Person("Michael", "Brown", "michael.brown@example.com","The Button")

        );

    public static void main(String[] args) 
        launch(args);
    

    @Override
    public void start(Stage stage) 
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(500);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        table.setEditable(true);

        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));

        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));

        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));

        TableColumn btnCol = new TableColumn("Buttons");
        btnCol.setMinWidth(100);
        btnCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("btn"));

        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, btnCol);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    

    public static class Person 

        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
        private final JButton btn;

        private Person(String fName, String lName, String email, String btn) 
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
            this.btn = new JButton(btn);
        

        public String getFirstName() 
            return firstName.get();
        

        public void setFirstName(String fName) 
            firstName.set(fName);
        

        public String getLastName() 
            return lastName.get();
        

        public void setLastName(String fName) 
            lastName.set(fName);
        

        public String getEmail() 
            return email.get();
        

        public void setEmail(String fName) 
            email.set(fName);
        

        public JButton getBtn()
            return btn;
        

        public void setBtn(String btn)

        
    

    public static class ButtonPerson
        private final JButton btn;
        private ButtonPerson()
            btn = new JButton("The Button");
        
        public JButton getButton()
            return btn;
        
    

编辑:在进一步调查后,我发现使用预定义单元格类型(如文本和检查)覆盖单元格图形的示例。尚不清楚是否可以将任何通用 jfx 组件放置在像 JFXPanel 这样的单元格中。这与 JTable 不同,因为使用 JTable,只要我正确设置了渲染类,我就可以放置从 JComponent 继承的任何东西。如果有人知道如何(或者甚至可能)将 JFXPanel 放置在单元格或其他通用 JFx 组件(如 Button)中,那将非常有帮助。

【问题讨论】:

【参考方案1】:

您的实施问题

您可以在 Swing 应用程序中嵌入 JavaFX 组件(通过将 JavaFX 组件放在 JFXPanel 中)。但是您不能在 JavaFX 中嵌入 Swing 组件(除非您使用的是 JavaFX 8+)。

JavaFX 有它自己的按钮实现,因此没有理由在 JavaFX 场景中嵌入 javax.swing.JButton,即使您使用的是 Java8 并且它可以工作。

但这并不能解决您的所有问题。您正在为按钮列提供单元格值工厂以提供按钮,但不提供自定义单元格渲染工厂。默认的表格单元格渲染工厂在相应的单元格值上渲染toString 输出,这就是为什么您只在表格实现中看到按钮的 to 字符串表示形式。

您正在将按钮放入您的 Person 对象中。不要那样做——他们不属于那里。相反,在单元格渲染工厂中动态生成一个按钮。这使您可以利用表的虚拟流技术,从而只为您在屏幕上看到的内容创建可视节点,而不是为表的支持数据存储中的每个元素创建可视节点。例如,如果屏幕上有 10 行可见,表格中有 10000 个元素,则只会创建 10 个按钮,而不是 10000 个。

如何解决

    使用 JavaFX Button 代替 javax.swing.JButton。 为您的按钮提供单元格渲染工厂。 在表格单元格中而不是在 Person 中生成按钮。 要在表格单元格中设置按钮(或任意JavaFX 节点),请使用单元格的setGraphic 方法。

正确的示例代码

此代码包含建议的修复和一些改进。

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

public class GestureEvents extends Application 
    private TableView<Person> table = new TableView<Person>();
    private final ObservableList<Person> data =
        FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com","Coffee"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com","Fruit"),
            new Person("Ethan", "Williams", "ethan.williams@example.com","Fruit"),
            new Person("Emma", "Jones", "emma.jones@example.com","Coffee"),
            new Person("Michael", "Brown", "michael.brown@example.com","Fruit")

        );

    public static void main(String[] args) 
        launch(args);
    

    @Override
    public void start(Stage stage) 
        stage.setTitle("Table View Sample");

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        final Label actionTaken = new Label();

        table.setEditable(true);

        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));

        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));

        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));

        TableColumn<Person, Person> btnCol = new TableColumn<>("Gifts");
        btnCol.setMinWidth(150);
        btnCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Person>, ObservableValue<Person>>() 
          @Override public ObservableValue<Person> call(CellDataFeatures<Person, Person> features) 
              return new ReadOnlyObjectWrapper(features.getValue());
          
        );
        btnCol.setComparator(new Comparator<Person>() 
          @Override public int compare(Person p1, Person p2) 
            return p1.getLikes().compareTo(p2.getLikes());
          
        );
        btnCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() 
          @Override public TableCell<Person, Person> call(TableColumn<Person, Person> btnCol) 
            return new TableCell<Person, Person>() 
              final ImageView buttonGraphic = new ImageView();
              final Button button = new Button(); 
                button.setGraphic(buttonGraphic);
                button.setMinWidth(130);
              
              @Override public void updateItem(final Person person, boolean empty) 
                super.updateItem(person, empty);
                if (person != null) 
                  switch (person.getLikes().toLowerCase()) 
                    case "fruit": 
                      button.setText("Buy fruit");
                      buttonGraphic.setImage(fruitImage);
                      break;

                    default:
                      button.setText("Buy coffee");
                      buttonGraphic.setImage(coffeeImage);
                      break;
                  

                  setGraphic(button);
                  button.setOnAction(new EventHandler<ActionEvent>() 
                    @Override public void handle(ActionEvent event) 
                      actionTaken.setText("Bought " + person.getLikes().toLowerCase() + " for: " + person.getFirstName() + " " + person.getLastName());
                    
                  );
                 else 
                  setGraphic(null);
                
              
            ;
          
        );

        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol, btnCol);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 10, 10, 10));
        vbox.getChildren().addAll(label, table, actionTaken);
        VBox.setVgrow(table, Priority.ALWAYS);

        stage.setScene(new Scene(vbox));
        stage.show();
    

    public static class Person 

        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
        private final SimpleStringProperty likes;

        private Person(String fName, String lName, String email, String likes) 
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
            this.likes = new SimpleStringProperty(likes);
        

        public String getFirstName() 
            return firstName.get();
        

        public void setFirstName(String fName) 
            firstName.set(fName);
        

        public String getLastName() 
            return lastName.get();
        

        public void setLastName(String fName) 
            lastName.set(fName);
        

        public String getEmail() 
            return email.get();
        

        public void setEmail(String fName) 
            email.set(fName);
        

        public String getLikes() 
            return likes.get();
        

        public void setLikes(String likes) 
            this.likes.set(likes);
        
    

    // icons for non-commercial use with attribution from: http://www.iconarchive.com/show/veggies-icons-by-iconicon/bananas-icon.html and http://www.iconarchive.com/show/collection-icons-by-archigraphs.html
    private final Image coffeeImage = new Image(
      "http://icons.iconarchive.com/icons/archigraphs/collection/48/Coffee-icon.png"
    );
    private final Image fruitImage = new Image(
      "http://icons.iconarchive.com/icons/iconicon/veggies/48/bananas-icon.png"
    );

关于在 JavaFX 中使用 Swing 组件

在进一步调查后,我发现使用预定义单元格类型(如文本和检查)覆盖单元格图形的示例。尚不清楚是否可以将任何通用 jfx 组件放置在像 JFXPanel 这样的单元格中。

JFXPanel 用于在 Swing 中嵌入 JavaFX 组件,而不是在 JavaFX 中嵌入 Swing 组件,因此尝试在 JavaFX TableView 中放置 JFXPanel 绝对没有意义。这就是为什么你找不到任何人尝试这种事情的例子。

这与 JTable 不同,因为使用 JTable,只要我正确设置了渲染类,我就可以放置从 JComponent 继承的任何东西。

JavaFX TableView 在这方面类似于 Swing JTable。代替JComponent,Node 是JavaFX 的基本构建块——它们是相似的,但不同。您可以在 JavaFX TableView 中渲染任何 Node,只要您为其提供适当的单元工厂。

在Java 8 中,有一个SwingNode 可以包含一个Swing JComponentSwingNode 允许您在 JavaFX TableView 中呈现任何 Swing 组件。

使用SwingNode 的代码非常简单:

import javafx.application.Application;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import javax.swing.*;

public class SwingFx extends Application 
  @Override public void start(Stage stage) 
    SwingNode swingNode = new SwingNode();
    SwingUtilities.invokeLater(() -> swingNode.setContent(new JButton("Click me!")));

    stage.setScene(new Scene(new StackPane(swingNode), 100, 50));
    stage.show();
  

  public static void main(String[] args)  launch(SwingFx.class); 

替代实施

基本上我要做的是向我的 java 项目添加手势滚动支持,以便用户可以“轻弹”表格和选项卡

虽然 Java 8 应该允许您完全实现您想要的,但如果您更喜欢使用较旧的 Java 版本,您可以使用基于 Swing 的 Multitouch for Java (MT4J) 系统而不是 JavaFX。

【讨论】:

非常感谢您提供的示例。但就像你说的,我不能在 javafx 中嵌入 Swing 组件。因此,我当前放置在 jtables 上的所有组件都必须重新编写。听起来对吗?在那种情况下,我认为 javafx 表视图不适合我。基本上我想要做的是向我的java项目添加手势滚动支持,以便用户可以“轻弹”表格和选项卡,我认为由于javafx本身就有这种支持,因此将项目升级到javafx是个好主意.但如果不重写许多组件,这似乎是不可能的。 您的假设并不完全正确。我更新了我的答案,以帮助澄清我对此的看法。 谢谢你的食谱 @jewelsea 你真是个天才! @jewelsea:我想使用您的代码在一个 tableView 单元格中添加两个按钮?有可能吗?

以上是关于JavaFX 表 - 如何添加组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JavaFX 中从 SceneBuilder 访问 UI 组件

JavaFX 如何隐藏Pagination的按钮?

JavaFX 入门

有没有办法在 JAVAFX 上实现像“渲染”这样的属性?

有没有办法在 JAVAFX 上实现像“渲染”这样的属性?

组件构造函数参数 fxml javafx