8

我有一个摇摆项目,它使用许多 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)中,那将非常有帮助。

4

1 回答 1

27

您的实施问题

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

JavaFX 有它自己的按钮实现,因此没有理由将 a 嵌入到javax.swing.JButtonJavaFX 场景中,即使您使用的是 Java8 并且它会工作。

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

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

如何修复它

  1. 使用 JavaFX按钮代替javax.swing.JButton.
  2. 为您的按钮提供一个单元格渲染工厂。
  3. 在表格单元格中而不是在 Person 中生成一个按钮。
  4. 要在表格单元格中设置按钮(或任意 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 组件,因此尝试将 aJFXPanel放在 JavaFX 中绝对没有意义TableView。这就是为什么你找不到任何人尝试这种事情的例子。

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

JavaFX在这方面TableView类似于 Swing JTable。代替 a JComponentNode是 JavaFX 的基本构建块——它们是相似的,虽然不同。只要您为其提供适当的单元工厂, 您就可以Node在 JavaFX中呈现任何内容。TableView

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

使用 a 的代码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。

于 2013-05-03T19:59:45.747 回答