JavaFX8如何在使用TAB或其他键遍历时在表格视图中请求关注图形节点?

时间:2018-08-12 06:15:47

标签: javafx combobox tableview javafx-8 tablecell

我有一个TableView,其中包含文本和图形内容(组合框,复选框等)。

当我使用键盘遍历单元格并到达包含图形元素的单元格时,我希望选择图形,以便例如可以按F4键并下拉组合列表或单击空格键并具有切换按钮的更改状态。

但是,此刻,当我TAB(或其他键)到一个单元格时,选择了包含图形的单元格,并且我被迫使用鼠标来操纵图形。

我该如何选择图形元素本身,而不是包含图形元素的单元格?

IE。现在,当我将TAB插入非文本单元格时,它就是这样做的:

enter image description here

我如何才能做到这一点?

enter image description here

我尝试了几种获取单元格图形的方法,但它始终为空。

更新

我已经完成了更多工作,现在可以进入单元格图形了。这是Java新手错误。抱歉!

但是,尽管我现在可以获取图形,但仍然无法选择或关注图形。谁能告诉我该怎么做?非常感谢!

以下是我的更新代码摘录,其中以组合框和TABbing为例。

键事件被困在TableView级别的通用setOnKeyPressed处理程序中。这是TAB的代码。我已经指出了我被卡住的地方。

    } else if ( event.getCode() == KeyCode.TAB ) {

        tv.getSelectionModel().selectRightCell();
        endOfRowCheck(tv, event, pos, firstCol, maxCols);

        event.consume();

//==> IS IT BETTER TO USE THE FOCUS MODEL OR THE SELECTION MODEL?  BOTH GIVE THE CELL GRAPHIC.
//==> IS THERE A BETTER WAY OF GETTING THE CELL GRAPHIC?
        TablePosition<S, ?> focussedPos = tv.getFocusModel().getFocusedCell();
        TableColumn tableColumn = (TableColumn<S, ?>) focussedPos.getTableColumn();
        TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn);
        Node cellGraphic = cell.getGraphic();
        System.out.println(cellGraphic);
        //Output:  ComboBox@44cf20e7[styleClass=combo-box-base combo-box]

//==> HOW DO I NOW FOCUS ON (OR SELECT?) THE GRAPHIC?
//I tried Platform.runLater() on the requestFocus but that didn't work either.
        cellGraphic.requestFocus();

    } else if ... 

为完整起见,这是所谓的endOfRowCheck方法:

private void endOfRowCheck(TableView tv, KeyEvent event, TablePosition pos, TableColumn col, int maxCols) {
    if ( pos.getColumn() == maxCols ) {
        //We're at the end of a row so position to the start of the next row
        tv.getSelectionModel().select(pos.getRow()+1, col);
        event.consume();
    }
}

我按如下所示创建组合框列。

在FXML控制器中:

TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumnTEST(colComboBoxField_HEADING, TestModel::comboBoxFieldProperty, arlMasterAssetClasses);

在DAOGenUtil类中:

public <S> TableColumn<S, DBComboChoice> createComboBoxColumnTEST(String title, 
        Function<S, StringProperty> methodGetComboFieldProperty, 
        ObservableList<DBComboChoice> comboData) {

    TableColumn<S, DBComboChoice> col = new TableColumn<>(title);

    col.setCellValueFactory(cellData -> {
        String masterCode =  methodGetComboFieldProperty.apply(cellData.getValue()).get();
        DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
        return new SimpleObjectProperty<>(choice);
    });

    col.setCellFactory(column -> ComboBoxCell.createComboBoxCell(comboData));

    return col;

}

ComboBoxCell类,用于将不可编辑的组合显示为组合而不是标签。

public class ComboBoxCell<S, T> extends TableCell<S, T> {

    private final ComboBox<DBComboChoice> combo = new ComboBox<>();

    public ComboBoxCell(ObservableList<DBComboChoice> comboData) {

        combo.getItems().addAll(comboData);
        combo.setEditable(false);

        setGraphic(combo);
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        combo.setOnAction((ActionEvent event) -> {
            try {
                String masterCode =  combo.getSelectionModel().getSelectedItem().getMasterCode();
                S datamodel = getTableView().getItems().get(getIndex());
                try {
                    Method mSetComboBoxField = datamodel.getClass().getMethod("setComboBoxField", (Class) String.class);
                    mSetComboBoxField.invoke(datamodel, masterCode);
                } catch (NoSuchMethodException | SecurityException | IllegalAccessException
                        | IllegalArgumentException | InvocationTargetException ex) {
                    System.err.println(ex);
                    DAOGenUtil.logError(ex.getClass().toString(), ex.getMessage(), "Call to 'setComboBoxField' failed in ComboBoxCell.setOnAction for master code '" + masterCode + "'");
                }
            } catch (NullPointerException ex) {
                //temporary workaround for bad test data
                System.out.println("caught NPE in combo.setOnAction");
            }
        });
    }

    public static <S> ComboBoxCell<S, DBComboChoice> createComboBoxCell(ObservableList<DBComboChoice> comboData) {
        return new ComboBoxCell<S, DBComboChoice>(comboData);
    }

    @Override
    protected void updateItem(T comboChoice, boolean empty) {
        super.updateItem(comboChoice, empty);
        if (empty) {
            setGraphic(null);
        } else {
            combo.setValue((DBComboChoice) comboChoice);
            setGraphic(combo);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        }
    }

}

我正在使用JavaFX8,NetBeans 8.2和Scene Builder 8.3。

再次更新:

这是一个按要求提供的完整测试用例,可以在NetBeans中重现。如果没有采用预期的格式,我表示歉意...我对Java还是比较陌生,不知道如何把它变成可以独立运行的东西。

如果您单击文本字段列,然后单击TAB到组合框列,则包含组合框的单元格将获得焦点,而不是组合框本身。

对于我的应用,组合框必须不可编辑,并且始终呈现为组合。当用户到达表行的末尾并单击TAB(或向右箭头)时,焦点需要移至下一行的开始。

这是测试用例代码。

应用程序:

package test;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Test extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));

        Scene scene = new Scene(root);

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

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

}

FXML控制器:

package test;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

public class FXMLDocumentController implements Initializable {

    private DAOGenUtil DAOGenUtil = new DAOGenUtil();

    public ObservableList<TestModel> olTestModel = FXCollections.observableArrayList(testmodel ->
        new Observable[] {
                testmodel.textFieldProperty(),
                testmodel.comboBoxFieldProperty()
        });
    ObservableList<DBComboChoice> comboChoices = FXCollections.observableArrayList();

    TableColumn<TestModel, String> colTextField = new TableColumn("text col");
    TableColumn<TestModel, DBComboChoice> colComboBoxField = DAOGenUtil.createComboBoxColumn("combo col", TestModel::comboBoxFieldProperty, comboChoices);

    @FXML
    private TableView<TestModel> tv;

    @Override
    public void initialize(URL url, ResourceBundle rb) {

        comboChoices.add(new DBComboChoice("F", "Female"));
        comboChoices.add(new DBComboChoice("M", "Male"));

        olTestModel.add(new TestModel("test row 1", "M"));
        olTestModel.add(new TestModel("test row 2", "F"));
        olTestModel.add(new TestModel("test row 3", "F"));
        olTestModel.add(new TestModel("test row 4", "M"));
        olTestModel.add(new TestModel("test row 5", "F"));

        colTextField.setCellValueFactory(new PropertyValueFactory<>("textField"));    

        tv.getSelectionModel().setCellSelectionEnabled(true);
        tv.setEditable(true);
        tv.getColumns().addAll(colTextField, colComboBoxField);
        tv.setItems(olTestModel);

        tv.setOnKeyPressed(event -> {

            TableColumn firstCol = colTextField;
            TableColumn lastCol = colComboBoxField;
            int firstRow = 0;
            int lastRow = tv.getItems().size()-1;
            int maxCols = 1;

            DAOGenUtil.handleTableViewSpecialKeys(tv, event, firstCol, lastCol, firstRow, lastRow, maxCols);

        });

    }    

}

ComboBoxCell类:

package test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;

public class ComboBoxCell<S, T> extends TableCell<S, T> {

    private final ComboBox<DBComboChoice> combo = new ComboBox<>();

    private final DAOGenUtil DAOGenUtil;

    public ComboBoxCell(ObservableList<DBComboChoice> comboData) {

        this.DAOGenUtil = new DAOGenUtil();

        combo.getItems().addAll(comboData);
        combo.setEditable(false);

        setGraphic(combo);
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        combo.setOnAction((ActionEvent event) -> {

            String masterCode =  combo.getSelectionModel().getSelectedItem().getMasterCode();

            S datamodel = getTableView().getItems().get(getIndex());

            try {

                Method mSetComboBoxField = datamodel.getClass().getMethod("setComboBoxField", (Class) String.class);
                mSetComboBoxField.invoke(datamodel, masterCode);

            } catch (NoSuchMethodException | SecurityException | IllegalAccessException
                    | IllegalArgumentException | InvocationTargetException ex) {
                System.err.println(ex);
            }

        });

    }

    @Override
    protected void updateItem(T comboChoice, boolean empty) {
        super.updateItem(comboChoice, empty);
        if (empty) {
            setGraphic(null);
        } else {
            combo.setValue((DBComboChoice) comboChoice);
            setGraphic(combo);
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        }
    }

}

TableView数据模型:

package test;

import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;

public class TestModel {

    private StringProperty textField;
    private StringProperty comboBoxField;

    public TestModel() {
        this(null, null);
    }

    public TestModel(
        String textField,
        String comboBoxField
    ) {
        this.textField = new SimpleStringProperty(textField);
        this.comboBoxField = new SimpleStringProperty(comboBoxField);
    }

    public String getTextField() {
        return textField.get().trim();
    }

    public void setTextField(String textField) {
        this.textField.set(textField);
    }

    public StringProperty textFieldProperty() {
        return textField;
    }

    public String getComboBoxField() {
        return comboBoxField.get().trim();
    }

    public void setComboBoxField(String comboBoxField) {
        this.comboBoxField.set(comboBoxField);
    }

    public StringProperty comboBoxFieldProperty() {
        return comboBoxField;
    }

}

DBComboChoice数据模型:

package test;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;

public class DBComboChoice {

        private StringProperty masterCode;
        private StringProperty masterDescription;

    public DBComboChoice(
        String masterCode, 
        String masterDescription
    ) {
        this.masterCode = new SimpleStringProperty(masterCode);
        this.masterDescription = new SimpleStringProperty(masterDescription);
    }

    public String getMasterCode() {
        return masterCode.get();
    }

    public StringProperty masterCodeProperty() {
        return masterCode;
    }

    public String getMasterDescription() {
        return masterDescription.get();
    }

    public StringProperty masterDescriptionProperty() {
        return masterDescription;
    }

    public static DBComboChoice getDescriptionByMasterCode(String inMasterCode, ObservableList<DBComboChoice> comboData) {
        for ( int i=0; i<comboData.size(); i++ ) {
            if ( comboData.get(i).getMasterCode().equals(inMasterCode) ) {
                return comboData.get(i);
            }
        }
        return null;
    }

    @Override
    public String toString() {
       return this.masterDescription.get();
   }

}

DAOGenUtil类:

package test;

import java.util.function.Function;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

public class DAOGenUtil {

    public <S> TableColumn<S, DBComboChoice> createComboBoxColumn(String title, 
            Function<S, StringProperty> methodGetComboFieldProperty, 
            ObservableList<DBComboChoice> comboData) {

        TableColumn<S, DBComboChoice> col = new TableColumn<>(title);

        col.setCellValueFactory(cellData -> {
            String masterCode =  methodGetComboFieldProperty.apply(cellData.getValue()).get();
            DBComboChoice choice = DBComboChoice.getDescriptionByMasterCode(masterCode, comboData);
            return new SimpleObjectProperty<>(choice);
        });

        col.setCellFactory((TableColumn<S, DBComboChoice> param) -> new ComboBoxCell<>(comboData));

        return col;

    }

    public <S> void handleTableViewSpecialKeys(TableView tv, KeyEvent event,
                                                TableColumn firstCol, TableColumn lastCol,
                                                int firstRow, int lastRow,
                                                int maxCols) {

        //NB:  pos, at this point, is the cell position that the cursor is about to leave
        TablePosition<S, ?> pos = tv.getFocusModel().getFocusedCell();

        if (pos != null ) {

            if ( event.getCode() == KeyCode.TAB ) {

                tv.getSelectionModel().selectRightCell();
                endOfRowCheck(tv, event, pos, firstCol, maxCols);

                event.consume();

                TablePosition<S, ?> focussedPos = tv.getFocusModel().getFocusedCell();
                TableColumn tableColumn = (TableColumn<S, ?>) focussedPos.getTableColumn();
                TableCell cell = (TableCell) tableColumn.getCellFactory().call(tableColumn);
                Node cellGraphic = cell.getGraphic();
                System.out.println("node cellGraphic is " + cellGraphic);

                if ( cellGraphic instanceof ComboBox<?> ) {
                    System.out.println("got a combo");
                    //nbg cellGraphic.requestFocus();
                    Platform.runLater(() -> {
                        ((ComboBox<?>) cellGraphic).requestFocus();
                    });
                }

            } else if ( ! event.isShiftDown() && ! event.isControlDown() ){
                //edit the cell
                    tv.edit(pos.getRow(), pos.getTableColumn());
            }

        }

    }

    private void endOfRowCheck(TableView tv, KeyEvent event, TablePosition pos, TableColumn col, int maxCols) {

        if ( pos.getColumn() == maxCols ) {
            //We're at the end of a row so position to the start of the next row
            tv.getSelectionModel().select(pos.getRow()+1, col);
            event.consume();
        }

    }

}

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="test.FXMLDocumentController">
   <center>
      <TableView fx:id="tv" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

1 个答案:

答案 0 :(得分:1)

您的问题太广泛了,您正在尝试一次解决太多问题,例如:

  • 更改导航顺序,即endOfRow处理
  • 使用标签导航
  • 实现自定义单元格
  • 将值的视觉外观映射到对用户有意义的东西
  • 开始编辑(又称“焦点”)到达单元格

代码中最重要的(IMO)误解是最后一个要点:更改基础数据时,您一定不要绕过编辑机制。因此,将思维方式从“聚焦图形”更改为“开始编辑”。

下面是一个独立的示例,该示例演示了如何从核心支持开始并进行修改以使其更接近实际需求。它

  • 使用(粗体的)StringConverter配置核心ComboBoxTableCell来映射masterCode-> masterDescription
  • 扩展该核心单元以在startEdit中请求焦点
  • 在表(实际上是其focusModel的)的focusCell属性上注册一个侦听器,该属性开始编辑新单元格

从此处继续的选项:

  • 要始终显示可编辑控件,请查看ComboBoxTableCell的代码并进行修改以始终显示组合
  • 重新应用制表符处理(对我来说很好)
  • 根据需要更改导航顺序

代码:

public class TableCellFocusApp extends Application {

    private Parent createContent() {

        ObservableList<TestModel> olTestModel = FXCollections
                .observableArrayList(testmodel -> new Observable[] {
                        testmodel.textFieldProperty(),
                        testmodel.comboBoxFieldProperty() });

        TableView<TestModel> table = new TableView<>();

        olTestModel.add(new TestModel("test row 1", "M"));
        olTestModel.add(new TestModel("test row 2", "F"));
        olTestModel.add(new TestModel("test row 3", "F"));
        olTestModel.add(new TestModel("test row 4", "M"));
        olTestModel.add(new TestModel("test row 5", "F"));

        TableColumn<TestModel, String> colTextField = new TableColumn<>("text col");
        colTextField
                .setCellValueFactory(cb -> cb.getValue().textFieldProperty());

        TableColumn<TestModel, String> gender= new TableColumn<>("Gender");
        gender.setMinWidth(100);
        gender.setCellValueFactory(cb -> cb.getValue().comboBoxFieldProperty());
        StringConverter<String> converter = new StringConverter<>() {

            @Override
            public String toString(String object) {
                return "F".equals(object) ? "Female" : "Male";
            }

            @Override
            public String fromString(String string) {
                return "Female".equals(string) ? "F" : "M";
            }

        };
        gender.setCellFactory(cb -> new ComboBoxTableCell<>(converter, "F", "M") {

            @Override
            public void startEdit() {
                super.startEdit();
                if (getGraphic() != null) {
                    getGraphic().requestFocus();
                }
            }

        });

        // just to see that the data is updated correctly - add a readonly column
        TableColumn<TestModel, String> plainGender = new TableColumn<>("readonly");
        plainGender.setCellValueFactory(cb -> cb.getValue().comboBoxFieldProperty());
        plainGender.setEditable(false);

        table.getFocusModel().focusedCellProperty().addListener((src, ov, nv) -> {
            if (nv != null && nv.getTableColumn() == gender) {
                table.edit(nv.getRow(), gender);
            }
        });

        table.getSelectionModel().setCellSelectionEnabled(true);
        table.setEditable(true);

        table.getColumns().addAll(colTextField,gender, plainGender ); //, colComboBoxField );
        table.setItems(olTestModel);

        BorderPane content = new BorderPane(table);
        return content;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.setTitle(FXUtils.version());
        stage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(TableCellFocusApp.class.getName());

}