【问题标题】:How do you prevent a TextFieldTableCell from going into Edit mode when pressing the ENTER key?当按下 ENTER 键时,如何防止 TextFieldTableCell 进入编辑模式?
【发布时间】:2021-08-04 10:35:42
【问题描述】:

默认情况下,在TextFieldTableCell 上按 ENTER 会使单元格进入编辑模式并在单元格中绘制一个文本字段。我该如何防止这种情况?如果按下数字键,我只希望单元格进入编辑模式。如果按下 ENTER,那么它应该只是选择下面的单元格。

我尝试过的:

tableView.setOnKeyReleased(new EventHandler<KeyEvent>() {
    @Override
    public void handle(KeyEvent event) {
        TablePosition tp;
        if(event.getCode().isDigitKey()) {
            lastKey = event.getText();
            tp = tableView.getFocusModel().getFocusedCell();
            tableView.edit(tp.getRow() , tp.getTableColumn());
        }
        if(tableView.getEditingCell() == null && event.getCode() == KeyCode.ENTER) {
            event.consume();
            tableView.getSelectionModel().selectBelowCell();
        }
    }
});

单元格仍进入编辑模式。

【问题讨论】:

  • 解决使用不同于 enter 的键的部分:实现您自己的 .. 行为是硬编码在 CellUtils.createTextField 中的。其余的更复杂(每个问题一个问题,请:)
  • oops .. 误读了 enter 键部分(最后一条评论是关于使用 enter 提交;) - 要不开始编辑,您必须从表的行为中删除 enter 绑定:没有干净的方法可以做到这一点,但可以绕过(反射性地从皮肤访问行为,然后根据需要修改其 inputMap)

标签: java javafx tablecell


【解决方案1】:

在表格上手动安装过滤器/处理程序的另一种方法是调整表格的 InputMap。注意:这是私有 api,需要访问内部包(输入映射和行为)并从皮肤反射抓取行为。希望这些软件包能尽快进入公共范围。

基本方法是

  • 从皮肤中获取行为及其 inputMap
  • 删除要替换的键映射
  • 根据需要添加新的键映射

在代码中:

TableViewSkin<?> skin = (TableViewSkin<?>) table.getSkin();
// use your favorite utility method to reflectively access the private field
TableViewBehavior<?> listBehavior = (TableViewBehavior<?>) FXUtils.invokeGetFieldValue(
        TableViewSkin.class, skin, "behavior");
InputMap<?> map = listBehavior.getInputMap();
// remove old mapping for enter
Optional<Mapping<?>> mapping = map.lookupMapping(new KeyBinding(ENTER));
map.getMappings().remove(mapping.get());
// add new mapping for enter
map.getMappings().add(new KeyMapping(KeyCode.ENTER, e -> table.getSelectionModel().selectBelowCell()));
// add mappings for digits
EventHandler<KeyEvent> handler = e -> {
    TablePosition<T, ?> pos = table.getFocusModel().getFocusedCell() ;
    if (pos != null ) {
        table.edit(pos.getRow(), pos.getTableColumn());
    }
};
List<KeyMapping> digitMappings = Arrays.stream(KeyCode.values())
        .filter(c -> c.isDigitKey())
        .map(c -> new KeyMapping(c, handler))
        .collect(Collectors.toList());
                
map.getMappings().addAll(digitMappings);

显然,这要求表格的皮肤可用并且它的类型为 TableViewSkin。要使用它,可以在表的皮肤属性的侦听器中调用它:

table.skinProperty().addListener(c -> {
    tweakInputMap(table);
});

应该在 fx 版本 >= fx9(我的上下文是 fx17/18 dev)中工作,并且需要在编译时添加导出输入映射/行为包,并在运行时添加打开这些包。

【讨论】:

    【解决方案2】:

    也许您可以使用Event Filter 来防止在按下回车键时进入编辑模式(并选择下面的单元格):

    tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
        if(keyEvent.getCode() == KeyCode.ENTER) {
            tableView.getSelectionModel().selectBelowCell();
            keyEvent.consume();
        }
    });
    

    按下数字键时开始编辑模式:

    tableView.setOnKeyPressed(event -> {
        TablePosition<Person, ?> pos = tableView.getFocusModel().getFocusedCell() ;
        if (pos != null && event.getCode().isDigitKey()) {
            tableView.edit(pos.getRow(), pos.getTableColumn());
        }
    });
    
    

    编辑:包含的示例

    package org.example;
    
    import javafx.application.Application;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.event.Event;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;
    import javafx.stage.Stage;
    
    import java.util.stream.IntStream;
    
    public class TableViewTestApp extends Application {
    
        @Override
        public void start(Stage stage) {
            stage.setScene(new Scene(createTableView()));
            stage.show();
        }
    
        private TableView<Item> createTableView() {
            TableView<Item> tableView = new TableView<>();
            tableView.setEditable(true);
            tableView.getSelectionModel().setCellSelectionEnabled(true);
    
            TableColumn<Item, String> nameCol = new TableColumn<>("name");
            tableView.getColumns().add(nameCol);
    
            nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
    
            nameCol.setCellFactory(c -> new CustomTableCell());
    
            // Enter edit mode on digit pressed only
            tableView.setOnKeyPressed(event -> {
                TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
                if (pos != null && event.getCode().isDigitKey()) {
                    tableView.edit(pos.getRow(), pos.getTableColumn());
                }
            });
    
            tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
                if (keyEvent.getCode() == KeyCode.ENTER) {
                    tableView.getSelectionModel().selectBelowCell(); //***When last cell of column move to the first cell of next column maybe
                    keyEvent.consume();
                }
            });
    
            IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item("Item " + i)));
    
            return tableView;
        }
    
        private class CustomTableCell extends TableCell<Item, String> {
    
            private TextField textField;
    
            private void createTextField() {
                textField = new TextField(getItem());
    
                // Commit value on focus lost:
                textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
                    if (wasFocused) {
                        commitEdit(textField.getText());
                    }
                });
    
                // This is needed to commit last cell of the column on enter released:
                // (***Alternative: Move to first cell of next column)
                textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
                    if (keyEvent.getCode() == KeyCode.ENTER) {
                        commitEdit(getItem());
                        keyEvent.consume();
                    }
                });
            }
    
            @Override
            public void startEdit() {
                super.startEdit();
                setText(null);
                createTextField();
                setGraphic(textField);
                textField.requestFocus();
                textField.selectAll();
            }
    
            @Override
            public void cancelEdit() {
                super.cancelEdit();
                setText(getItem());
                setGraphic(null);
            }
    
            // See: https://gist.github.com/james-d/be5bbd6255a4640a5357
            @Override
            public void commitEdit(String item) {
                // This block is necessary to support commit on losing focus, because the baked-in mechanism
                // sets our editing state to false before we can intercept the loss of focus.
                // The default commitEdit(...) method simply bails if we are not editing...
                if (!isEditing() && !item.equals(getItem())) {
                    TableView<Item> table = getTableView();
                    if (table != null) {
                        TableColumn<Item, String> column = getTableColumn();
                        TableColumn.CellEditEvent<Item, String> event = new TableColumn.CellEditEvent<>(table,
                                new TablePosition<>(table, getIndex(), column),
                                TableColumn.editCommitEvent(), item);
                        Event.fireEvent(column, event);
                    }
                }
                super.commitEdit(item);
                setText(item);
                setGraphic(null);
                textField = null;
            }
    
            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty) {
                    setText(null);
                } else {
                    setText(item);
                }
            }
        }
    
        private class Item {
    
            private final StringProperty name;
    
            public Item(String name) {
                this.name = new SimpleStringProperty(name);
            }
    
            public String getName() {
                return name.get();
            }
    
            public StringProperty nameProperty() {
                return name;
            }
    
            public void setName(String name) {
                this.name.set(name);
            }
        }
    
        public static void main(String[] args) {
            launch();
        }
    }
    

    编辑 2:为整数添加自定义表格单元格(带微调器)

    package org.example;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.beans.value.WritableValue;
    import javafx.event.Event;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import javafx.util.converter.IntegerStringConverter;
    
    import java.text.NumberFormat;
    import java.text.ParsePosition;
    import java.util.function.UnaryOperator;
    import java.util.stream.IntStream;
    
    public class TableViewTestApp extends Application {
    
        @Override
        public void start(Stage stage) {
            TableView<Item> tableView = createTableView();
            Button btn = new Button("print items to console");
            btn.setOnAction(actionEvent -> tableView.getItems().forEach(System.out::println));
            stage.setScene(new Scene(new VBox(tableView, btn)));
            stage.show();
        }
    
        private TableView<Item> createTableView() {
            TableView<Item> tableView = new TableView<>();
            tableView.setEditable(true);
            tableView.getSelectionModel().setCellSelectionEnabled(true);
    
            TableColumn<Item, String> nameCol = new TableColumn<>("name");
            tableView.getColumns().add(nameCol);
    
            nameCol.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
    
            nameCol.setCellFactory(c -> new StringTextFieldTableCell());
    
            // Enter edit mode on digit pressed only
            tableView.setOnKeyPressed(event -> {
                TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
                if (pos != null && event.getCode().isDigitKey()) {
                    tableView.edit(pos.getRow(), pos.getTableColumn());
                }
            });
    
            tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
                if (keyEvent.getCode() == KeyCode.ENTER) {
                    tableView.getSelectionModel().selectBelowCell(); //***When last cell of column move to the first cell of next column maybe
                    keyEvent.consume();
                }
            });
    
            IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item("Item " + i)));
    
            TableColumn<Item, Integer> quantityCol = new TableColumn<>("quantity");
            quantityCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
            quantityCol.setCellFactory(c -> new IntegerSpinnerTableCell<>());
            quantityCol.setMinWidth(150);
            tableView.getColumns().add(quantityCol);
    
            return tableView;
        }
    
        private class StringTextFieldTableCell extends TableCell<Item, String> {
    
            private TextField textField;
    
            private void createTextField() {
                textField = new TextField(getItem());
    
                // Commit value on focus lost:
                textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
                    if (wasFocused) {
                        commitEdit(textField.getText());
                    }
                });
    
                // This is needed to commit last cell of the column on enter released:
                // (***Alternative: Move to first cell of next column)
                textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
                    if (keyEvent.getCode() == KeyCode.ENTER) {
                        commitEdit(getItem());
                        keyEvent.consume();
                    }
                });
            }
    
            // set the text of the text field and display the graphic
            @Override
            public void startEdit() {
                super.startEdit();
                setText(null);
                createTextField();
                setGraphic(textField);
                textField.requestFocus();
                textField.selectAll();
            }
    
            // revert to text display
            @Override
            public void cancelEdit() {
                super.cancelEdit();
                setText(getItem());
                setGraphic(null);
            }
    
            // See: https://gist.github.com/james-d/be5bbd6255a4640a5357
            @Override
            public void commitEdit(String item) {
                // This block is necessary to support commit on losing focus, because the baked-in mechanism
                // sets our editing state to false before we can intercept the loss of focus.
                // The default commitEdit(...) method simply bails if we are not editing...
                if (!isEditing() && !item.equals(getItem())) {
                    TableView<Item> table = getTableView();
                    if (table != null) {
                        TableColumn<Item, String> column = getTableColumn();
                        TableColumn.CellEditEvent<Item, String> event = new TableColumn.CellEditEvent<>(table,
                                new TablePosition<>(table, getIndex(), column),
                                TableColumn.editCommitEvent(), item);
                        Event.fireEvent(column, event);
                    }
                }
                super.commitEdit(item);
                setText(item);
                setGraphic(null);
                textField = null;
            }
    
            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty)
                    setText(null);
                else
                    setText(item);
            }
        }
    
        public class IntegerSpinnerTableCell<T> extends TableCell<T, Integer> {
    
            private final Spinner<Integer> spinner;
            private final SpinnerValueFactory.IntegerSpinnerValueFactory valueFactory;
            private boolean ignoreUpdate;
    
            public IntegerSpinnerTableCell() {
                spinner = new Spinner<>();
                valueFactory = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, 1, 1);
                spinner.setValueFactory(valueFactory);
                spinner.setEditable(true);
                NumberFormat format = NumberFormat.getIntegerInstance();
                UnaryOperator<TextFormatter.Change> filter = c -> {
                    if (c.isContentChange()) {
                        ParsePosition parsePosition = new ParsePosition(0);
                        format.parse(c.getControlNewText(), parsePosition);
                        if (parsePosition.getIndex() == 0 || parsePosition.getIndex() < c.getControlNewText().length()) {
                            return null;
                        }
                    }
                    return c;
                };
    
                TextFormatter<Integer> intFormatter = new TextFormatter<>(new IntegerStringConverter(), 1, filter);
                spinner.getEditor().setTextFormatter(intFormatter);
    
                spinner.valueProperty().addListener((o, oldValue, newValue) -> {
                    if (!ignoreUpdate) {
                        ignoreUpdate = true;
                        WritableValue<Number> property = (WritableValue<Number>) getTableColumn()
                                .getCellObservableValue(getTableRow().getItem());
                        property.setValue(newValue);
                        ignoreUpdate = false;
                    }
                });
    
                spinner.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
                    if (keyEvent.getCode() == KeyCode.ENTER) {
                        commitEdit(getItem());
                        keyEvent.consume();
                    }
                });
            }
    
            @Override
            protected void updateItem(Integer item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty) {
                    setText(null);
                    setGraphic(null);
                } else {
                    if (isEditing()) {
                        ignoreUpdate = true;
                        setText(null);
                        valueFactory.setValue(item);
                        setGraphic(spinner);
                        ignoreUpdate = false;
                    } else {
                        ignoreUpdate = true;
                        setText(String.valueOf(item));
                        setGraphic(null);
                        ignoreUpdate = false;
                    }
                }
            }
    
            @Override
            public void startEdit() {
                if (!isEmpty()) {
                    super.startEdit();
                    ignoreUpdate = true;
                    setText(null);
                    setGraphic(spinner);
                    valueFactory.setValue(getItem());
                    requestEditorAndSelectText(Integer.MAX_VALUE);
                    ignoreUpdate = false;
                }
            }
    
            @Override
            public void cancelEdit() {
                super.cancelEdit();
                setText(String.valueOf(getItem()));
                setGraphic(null);
            }
    
            @Override
            public void commitEdit(Integer newValue) {
                if (!isEditing()) {
                    TableView<T> table = getTableView();
                    if (table != null) {
                        TableColumn<T, Integer> column = getTableColumn();
                        TableColumn.CellEditEvent<T, Integer> event = new TableColumn.CellEditEvent<>(table,
                                new TablePosition<>(table, getIndex(), column),
                                TableColumn.editCommitEvent(), spinner.getValue());
                        Event.fireEvent(column, event);
                    }
                }
                super.commitEdit(newValue);
                setText(String.valueOf(newValue.intValue()));
                setGraphic(null);
            }
    
            private void requestEditorAndSelectText(int maxTries) {
                if (maxTries > 0) {
                    Platform.runLater(() -> {
                                if (!spinner.getEditor().isFocused()) {
                                    spinner.getEditor().requestFocus();
                                    spinner.getEditor().selectAll();
                                    requestEditorAndSelectText(maxTries - 1);
                                }
                            }
                    );
                }
            }
        }
    
        private class Item {
    
            private final StringProperty name;
            private final IntegerProperty quantity;
    
            public Item(String name) {
                this.name = new SimpleStringProperty(name);
                this.quantity = new SimpleIntegerProperty(1);
            }
    
            @Override
            public String toString() {
                return getName() + " | " + getQuantity();
            }
    
            public String getName() {
                return name.get();
            }
    
            public StringProperty nameProperty() {
                return name;
            }
    
            public void setName(String name) {
                this.name.set(name);
            }
    
            public int getQuantity() {
                return quantity.get();
            }
    
            public IntegerProperty quantityProperty() {
                return quantity;
            }
    
            public void setQuantity(int quantity) {
                this.quantity.set(quantity);
            }
        }
    
    
        public static void main(String[] args) {
            launch();
        }
    }
    

    编辑 3:为 IntegerTextFieldTableCell 添加了示例(没有 Spinner,仅 TextField):

    package org.example;
    
    import javafx.application.Application;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.event.Event;
    import javafx.scene.Scene;
    import javafx.scene.control.*;
    import javafx.scene.input.KeyCode;
    import javafx.scene.input.KeyEvent;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import javafx.util.converter.IntegerStringConverter;
    
    import java.text.NumberFormat;
    import java.text.ParsePosition;
    import java.util.function.UnaryOperator;
    import java.util.stream.IntStream;
    
    public class TableWithPlainTextFieldTableCellForIntTestApp extends Application {
    
        @Override
        public void start(Stage stage) {
    
            TableView<Item> tableView = new TableView<>();
            tableView.setEditable(true);
            tableView.getSelectionModel().setCellSelectionEnabled(true);
    
            TableColumn<Item, Integer> quantityCol = new TableColumn<>("quantity");
            tableView.getColumns().add(quantityCol);
    
            quantityCol.setCellValueFactory(cellData -> cellData.getValue().quantityProperty().asObject());
            quantityCol.setCellFactory(c -> new IntegerTextFieldTableCell());
            // Enter edit mode on digit pressed only
    
            tableView.setOnKeyPressed(event -> {
                TablePosition<Item, ?> pos = tableView.getFocusModel().getFocusedCell();
                if (pos != null && event.getCode().isDigitKey()) {
                    tableView.edit(pos.getRow(), pos.getTableColumn());
                }
            });
    
            tableView.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
                if (keyEvent.getCode() == KeyCode.ENTER) {
                    tableView.getSelectionModel().selectBelowCell();
                    keyEvent.consume();
                }
            });
    
            IntStream.range(1, 5).forEach(i -> tableView.getItems().add(new Item()));
    
            quantityCol.setMinWidth(150);
    
            Button printBtn = new Button("print items");
            printBtn.setOnAction(actionEvent -> tableView.getItems().forEach(System.out::println));
    
            stage.setScene(new Scene(new VBox(tableView, printBtn)));
            stage.show();
        }
    
        class IntegerTextFieldTableCell extends TableCell<Item, Integer> {
    
            private TextField textField;
    
            private void createTextField() {
                textField = new TextField(getItem().toString());
    
                // Commit value on focus lost:
                textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
                    if (wasFocused) {
                        try {
                            commitEdit(Integer.parseInt(textField.getText()));
                        } catch (NumberFormatException ignored) {
                            commitEdit(1);
                        }
                    }
                });
    
                // This is needed to commit last cell of the column on enter released:
                // (***Alternative: Move to first cell of next column)
                textField.addEventFilter(KeyEvent.KEY_RELEASED, keyEvent -> {
                    if (keyEvent.getCode() == KeyCode.ENTER) {
                        commitEdit(getItem());
                        keyEvent.consume();
                    }
                });
    
                NumberFormat format = NumberFormat.getIntegerInstance();
                UnaryOperator<TextFormatter.Change> filter = c -> {
                    if (c.isContentChange()) {
                        ParsePosition parsePosition = new ParsePosition(0);
                        format.parse(c.getControlNewText(), parsePosition);
                        if (parsePosition.getIndex() == 0 || parsePosition.getIndex() < c.getControlNewText().length()) {
                            return null;
                        }
                    }
                    return c;
                };
                TextFormatter<Integer> intFormatter = new TextFormatter<>(new IntegerStringConverter(), 1, filter);
                textField.setTextFormatter(intFormatter);
            }
    
            // set the text of the text field and display the graphic
            @Override
            public void startEdit() {
                super.startEdit();
                setText(null);
                createTextField();
                setGraphic(textField);
                textField.requestFocus();
                textField.selectAll();
            }
    
            // revert to text display
            @Override
            public void cancelEdit() {
                super.cancelEdit();
                setText(getItem().toString());
                setGraphic(null);
            }
    
            // See: https://gist.github.com/james-d/be5bbd6255a4640a5357
            @Override
            public void commitEdit(Integer item) {
                // This block is necessary to support commit on losing focus, because the baked-in mechanism
                // sets our editing state to false before we can intercept the loss of focus.
                // The default commitEdit(...) method simply bails if we are not editing...
                if (!isEditing() && !item.equals(getItem())) {
                    TableView<Item> table = getTableView();
                    if (table != null) {
                        TableColumn<Item, Integer> column = getTableColumn();
                        TableColumn.CellEditEvent<Item, Integer> event = new TableColumn.CellEditEvent<>(table,
                                new TablePosition<>(table, getIndex(), column),
                                TableColumn.editCommitEvent(), item);
                        Event.fireEvent(column, event);
                    }
                }
                super.commitEdit(item);
                setText(item.toString());
                setGraphic(null);
                textField = null;
            }
    
            @Override
            protected void updateItem(Integer item, boolean empty) {
                super.updateItem(item, empty);
                if (item == null || empty)
                    setText(null);
                else
                    setText(item.toString());
            }
        }
    
        class Item {
    
            private final IntegerProperty quantity = new SimpleIntegerProperty(1);
    
            @Override
            public String toString() {
                return quantity.get() + "";
            }
    
            public int getQuantity() {
                return quantity.get();
            }
    
            public IntegerProperty quantityProperty() {
                return quantity;
            }
    
            public void setQuantity(int quantity) {
                this.quantity.set(quantity);
            }
        }
    
        public static void main(String[] args) {
            launch();
        }
    }
    

    【讨论】:

    • 好主意 - 有个问题:在过滤器中使用 enter 会在编辑后禁用提交
    • 不幸的是,添加事件过滤器会产生与发布的原始代码相同的结果。单元格仍会进入编辑模式。
    • @kleopatra 编辑后的提交在自定义 TextFieldTableCell 中处理。所以这不是问题。
    • hmm .. 我尝试了代码,它 一个问题 - 这是预期的,因为过滤器在到达文本字段之前消耗了输入。您可以通过编写带有可编辑数字列的minimal reproducible example 快速非常快速地添加此答案中的代码,在数字单元格中开始编辑,更改值,按回车键:单元格值恢复为编辑前的值,选择是向下移动一行。
    • 啊...我明白了:您有一个用于 commit-on-focusLost 的单元格黑客攻击 - 使用纯 TextFieldTableCell,它的行为正如我在上一条评论中所描述的那样。您可以考虑将其添加到您的答案中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-12
    • 1970-01-01
    • 2022-01-06
    • 2015-10-03
    • 1970-01-01
    • 1970-01-01
    • 2013-03-11
    相关资源
    最近更新 更多