Row edit in TableView

2019-07-27 13:54发布

问题:

I have a TableView in JavaFX (2.2/Java 7 to be specific), where each column is sortable (real-time sorted) and all rows/columns are editable. As an example, let's assume the entire table represents an "order". Each row in the table, represents "order lines", and the columns represents properties of the "order line", like "product", "quantity", "price" etc. The user can add, remove and edit rows ("order lines") as he wish.

To make the UI less confusing for the user, I would like to use row selection, and also make sure that only a single row is editable at a time (because sorting would otherwise make the currently edited value "jump" around in the table). I also have validation per "order line", which is easier to visualize for a row, rather than individual columns (as an example, assume the combination or product X and the quantity 7 is illegal, as product X can only be ordered in units of 5, I want to highlight both X and the value 7).

Row selection can easily be enabled. But is there a good way to implement row editing? The default/built in editing mode in a TableView is more like that of Excel where each column may be edited, committed and validated individually, and this is not what I want. I want something like the file list in a file explorer. It is important that all edits for the edited row are committed to the backing model (items property of the TableView) at once (ie. no onEditCommit for individual columns) because of the sorting and validation.

I've implemented something similar before in Swing, and then ended up using what I named a ColumnList, which is really a List, but with column headers and columns, to make it appear just like a table. Would this be a viable choice in JavaFX too? The TableColum class seems very bound to a TableView, but I might be missing something.

I also am aware of the "Address book" example in the JavaFX tutorials, which has row selection and editor fields below the table. This is what I have currently implemented in my application, but I want the editing to appear in-line with the currently selected row. So, making the editors appear "on top" or "inside" the currently selected row would also be an alternative, if anyone can come up with a way to achieve that.

回答1:

You could always make your own editor box and pop it up over the table as needed. This isn't a great solution but with some tweaks it might be nice. Dbl click to edit a row.

import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TableEdit extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Order> tv = new TableView<>();
        ObservableList<Order> items = FXCollections.observableArrayList(
                new Order("a-12", 100), new Order("b-23", 200), new Order("c-34", 300));
        tv.setItems(items);

        TableColumn<Order, String> tcNum = new TableColumn("Order#");
        tcNum.setPrefWidth(100);
        TableColumn<Order, Number> tcQty = new TableColumn("Qty");
        tcQty.setPrefWidth(100);
        tcNum.setCellValueFactory(new PropertyValueFactory<>("orderNum"));
        tcQty.setCellValueFactory(new PropertyValueFactory<>("qty"));
        tv.getColumns().addAll(tcNum, tcQty);

        HBox hbox = new HBox();
        TextField orderNum = new TextField();
        orderNum.setPrefWidth(100);
        TextField qty = new TextField();
        qty.setPrefWidth(100);

        tv.getSelectionModel().selectedItemProperty().addListener((obs, ov, nv) -> {
            if (nv != null) {
                orderNum.setText(nv.orderNum.get());
                qty.setText(String.valueOf(nv.qty.get()));
            }
        });

        Button commit = new Button("Commit");
        commit.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent evt) {
                Order item = tv.getSelectionModel().getSelectedItem();
                item.orderNum.set(orderNum.getText());
                item.qty.set(Integer.valueOf(qty.getText()));
                tv.toFront();
            }
        });

        hbox.getChildren().addAll(orderNum, qty, commit);
        StackPane root = new StackPane(hbox, tv);

        tv.setOnMouseClicked(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent evt) {
                if (evt.getClickCount()==2){
                    StackPane.setMargin(hbox, new Insets(evt.getSceneY(), 0, 0, 0));
                    hbox.toFront();
                }
            }
        });

        Scene scene = new Scene(root, 300, 250);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

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

    public class Order {

        private final SimpleStringProperty orderNum = new SimpleStringProperty();
        private final SimpleIntegerProperty qty = new SimpleIntegerProperty();

        public SimpleStringProperty orderNumProperty() {return orderNum;}
        public SimpleIntegerProperty qtyProperty() {return qty;}

        public Order(String orderNum, int qty) {
            this.orderNum.set(orderNum);
            this.qty.set(qty);
        }
    }
}