2015-04-12 47 views
2

问题类型编辑在TableView中

我想切换到我的TableView只要我输入编辑模式。我不想双击或按第一个单元格输入,这很烦人。

我想出了下面这段代码。问题在于它是或多或少的副作用编程,我怀疑是麻烦。当您使用KEY_RELEASED将表切换到编辑模式时,第一次按键会丢失。

所以你必须使用KEY_PRESSED。这一切现在似乎都运行良好,但偶尔会遇到竞争条件,并且TextField单元格编辑器中的插入符号位于输入文本之前而不是之后。但是当你继续打字时,文字会在现有文字后正确地追加。

看起来没问题,但从发展的角度来看,它似乎像一个混乱的竞争条件。

问题

没有人有做一个“类型,以编辑”功能的有道?

代码

这里是到目前为止,我已经得到了代码:

public class InlineEditingTableView extends Application { 

    private final ObservableList<Data> data = 
     FXCollections.observableArrayList(
       new Data(1.,5.), 
       new Data(2.,6.), 
       new Data(3.,7.), 
       new Data(4.,8.) 
     ); 

    private TableView<Data> table; 

    @Override 
    public void start(Stage stage) { 

     // create edtiable table 
     table = new TableView<Data>(); 
     table.setEditable(true); 

     // column 1 contains numbers 
     TableColumn<Data, Number> number1Col = new TableColumn<>("Number 1"); 
     number1Col.setMinWidth(100); 
     number1Col.setCellValueFactory(cellData -> cellData.getValue().number1Property()); 
     number1Col.setCellFactory(createNumberCellFactory()); 
     number1Col.setOnEditCommit(new EventHandler<CellEditEvent<Data, Number>>() { 
      @Override 
      public void handle(CellEditEvent<Data, Number> t) { 
       System.out.println(t); 
//    ((Person) t.getTableView().getItems().get(t.getTablePosition().getRow())).setFirstName(t.getNewValue()); 
      } 
     }); 

     // column 2 contains numbers 
     TableColumn<Data, Number> number2Col = new TableColumn<>("Number 2"); 
     number2Col.setMinWidth(100); 
     number2Col.setCellValueFactory(cellData -> cellData.getValue().number2Property()); 
     number2Col.setCellFactory(createNumberCellFactory()); 

     // add columns & data to table 
     table.setItems(data); 
     table.getColumns().addAll(number1Col, number2Col); 




     // switch to edit mode on keypress 
     // this must be KeyEvent.KEY_PRESSED so that the key gets forwarded to the editing cell; it wouldn't be forwarded on KEY_RELEASED 
     table.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() { 
      @Override 
      public void handle(KeyEvent event) { 

       if(event.getCode() == KeyCode.ENTER) { 
//     event.consume(); // don't consume the event or else the values won't be updated; 
        return; 
       } 

       // switch to edit mode on keypress, but only if we aren't already in edit mode 
       if(table.getEditingCell() == null) { 
        if(event.getCode().isLetterKey() || event.getCode().isDigitKey()) { 

         TablePosition focusedCellPosition = table.getFocusModel().getFocusedCell(); 
         table.edit(focusedCellPosition.getRow(), focusedCellPosition.getTableColumn()); 

        } 
       } 

      } 
     }); 

     table.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() { 
      @Override 
      public void handle(KeyEvent event) { 

       if(event.getCode() == KeyCode.ENTER) { 
        table.getSelectionModel().selectBelowCell(); 
       } 
      } 
     });  

     // single cell selection mode 
     table.getSelectionModel().setCellSelectionEnabled(true); 
     table.getSelectionModel().selectFirst(); 




     // add nodes to stage 
     BorderPane root = new BorderPane(); 
     root.setCenter(table); 

     Scene scene = new Scene(root, 800,600); 
     stage.setScene(scene); 
     stage.show(); 
    } 

    /** 
    * Number cell factory which converts strings to numbers and vice versa. 
    * @return 
    */ 
    private Callback<TableColumn<Data, Number>, TableCell<Data, Number>> createNumberCellFactory() { 

     Callback<TableColumn<Data, Number>, TableCell<Data, Number>> factory = TextFieldTableCell.forTableColumn(new StringConverter<Number>() { 

      @Override 
      public Number fromString(String string) { 
       return Double.parseDouble(string); 
      } 

      @Override 
      public String toString(Number object) { 
       return object.toString(); 
      } 
     }); 

     return factory; 
    } 

    /** 
    * Table data container 
    */ 
    public static class Data { 

     private final SimpleDoubleProperty number1; 
     private final SimpleDoubleProperty number2; 

     private Data(Double number1, Double number2) { 
      this.number1 = new SimpleDoubleProperty(number1); 
      this.number2 = new SimpleDoubleProperty(number2); 
     } 

     public final DoubleProperty number1Property() { 
      return this.number1; 
     } 

     public final double getNumber1() { 
      return this.number1Property().get(); 
     } 

     public final void setNumber1(final double number1) { 
      this.number1Property().set(number1); 
     } 

     public final DoubleProperty number2Property() { 
      return this.number2; 
     } 

     public final double getNumber2() { 
      return this.number2Property().get(); 
     } 

     public final void setNumber2(final double number2) { 
      this.number2Property().set(number2); 
     } 


    } 

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


} 

回答

0

我想你可以实现自定义文本字段TableCell的,在那里你可以放在最后插入符避免在进入编辑模式时手动输入项目文本。

另一种方法是焦点进入编辑模式:

table.getFocusModel().focusedCellProperty().addListener(
     (ObservableValue<? extends TablePosition> observable, TablePosition oldValue, TablePosition newValue) -> 
     { 
      if (newValue != null) 
      { 
       Platform.runLater(() -> 
         { 
          table.edit(newValue.getRow(), newValue.getTableColumn()); 
       }); 
      } 
     } 
); 
1

要立即编辑上单击单元格,它更有意义,我有,而不是过渡到TextField小号永久显示在表中,一个特殊的“编辑模式”,并从Label切换到TextField。 (我认为这是因为所有单元格总是处于“编辑模式”,我认为这对你想要的行为有意义)。

如果这种UI可以满足你的要求,你可以在该单元格并将文本字段的textProperty双向绑定到模型中的相应属性。这里棘手的部分是获取该属性:您必须从单元格转到表格行,然后转到表格行的项目,然后转到您需要的属性。在任何时候,其中一个可能会改变(可能到null),所以你必须处理这些可能性。

给这个普通的一个例子:

public class Person { 

    // ... 

    public StringProperty firstNameProperty() { ... } 

    // etc... 
} 

你可以做

TableView<Person> table = new TableView<>(); 
    TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name"); 
    firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty()); 
    firstNameCol.setCellFactory(col -> { 
     TableCell<Person, String> cell = new TableCell<>(); 
     TextField textField = new TextField(); 

     cell.graphicProperty().bind(Bindings.when(cell.emptyProperty()) 
       .then((Node)null) 
       .otherwise(textField)); 

     ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> { 
      if (oldPerson != null) { 
       textField.textProperty().unbindBidirectional(((Person) oldPerson).firstNameProperty()); 
      } 
      if (newPerson != null) { 
       textField.textProperty().bindBidirectional(((Person) newPerson).firstNameProperty()); 
      } 
     }; 
     cell.tableRowProperty().addListener((obs, oldRow, newRow) -> { 
      if (oldRow != null) { 
       oldRow.itemProperty().removeListener(rowItemListener); 
       if (oldRow.getItem() != null) { 
        textField.textProperty().unbindBidirectional(((Person) oldRow.getItem()).firstNameProperty()); 
       } 
      } 
      if (newRow != null) { 
       newRow.itemProperty().addListener(rowItemListener); 
       if (newRow.getItem() != null) { 
        textField.textProperty().bindBidirectional(((Person) newRow.getItem()).firstNameProperty()); 
       } 
      } 
     }); 

     return cell ; 
    }); 

可以大大利用EasyBind框架,它提供(除其他事项外)的方式到达这里减少代码的复杂性“性能属性”适当处理null

TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name"); 
    firstNameCol.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty()); 
    firstNameCol.setCellFactory(col -> { 
     TableCell<Person, String> cell = new TableCell<>(); 
     TextField textField = new TextField(); 

     cell.graphicProperty().bind(Bindings.when(cell.emptyProperty()) 
       .then((Node)null) 
       .otherwise(textField)); 

     textField.textProperty().bindBidirectional(
       EasyBind.monadic(cell.tableRowProperty()) 
       .selectProperty(TableRow::itemProperty) 
       .selectProperty(p -> ((Person)p).firstNameProperty())); 

     return cell ; 
    }); 

这里是一个完整的示例,其中,I分解上面的细胞工厂代码到一个更一般的方法:

import java.util.function.Function; 

import javafx.application.Application; 
import javafx.beans.property.Property; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 
import javafx.beans.value.ChangeListener; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.control.TableCell; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableRow; 
import javafx.scene.control.TableView; 
import javafx.scene.control.TextField; 
import javafx.scene.layout.BorderPane; 
import javafx.stage.Stage; 

import org.fxmisc.easybind.EasyBind; 

public class LiveTableViewCell extends Application { 

    @Override 
    public void start(Stage primaryStage) { 
     TableView<Person> table = new TableView<>(); 
     table.getItems().addAll(   
      new Person("Jacob", "Smith", "[email protected]"), 
      new Person("Isabella", "Johnson", "[email protected]"), 
      new Person("Ethan", "Williams", "[email protected]"), 
      new Person("Emma", "Jones", "[email protected]"), 
      new Person("Michael", "Brown", "[email protected]") 
     ); 

     table.getColumns().addAll(
      createColumn("First Name", Person::firstNameProperty), 
      createColumn("Last Name", Person::lastNameProperty), 
      createColumn("Email", Person::emailProperty) 
     ); 

     Button button = new Button("Debug"); 
     button.setOnAction(e -> table.getItems().stream().map(p -> String.format("%s %s %s", p.getFirstName(), p.getLastName(), p.getEmail())).forEach(System.out::println)); 

     primaryStage.setScene(new Scene(new BorderPane(table, null, null, button, null), 600, 120)); 
     primaryStage.show(); 
    } 

    private TableColumn<Person, String> createColumn(String title, Function<Person, Property<String>> property) { 
     TableColumn<Person, String> col = new TableColumn<>(title); 
     col.setCellValueFactory(cellData -> property.apply(cellData.getValue())); 

     col.setCellFactory(column -> { 
      TableCell<Person, String> cell = new TableCell<>(); 
      TextField textField = new TextField(); 

     // Example of maintaining selection behavior when text field gains 
     // focus. You can also call getSelectedCells().add(...) on the selection 
     // model if you want to maintain multiple selected cells, etc. 

     textField.focusedProperty().addListener((obs, wasFocused, isFocused) -> { 
      if (isFocused) { 
       cell.getTableView().getSelectionModel().select(cell.getIndex(), cell.getTableColumn()); 
      } 
     }); 

     cell.graphicProperty().bind(Bindings.when(cell.emptyProperty()) 
       .then((Node)null) 
       .otherwise(textField)); 

      // If not using EasyBind, you need the following commented-out code in place of the next statement: 

//   ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> { 
//    if (oldPerson != null) { 
//     textField.textProperty().unbindBidirectional(property.apply((Person)oldPerson)); 
//    } 
//    if (newPerson != null) { 
//     textField.textProperty().bindBidirectional(property.apply((Person)newPerson)); 
//    } 
//   }; 
//   cell.tableRowProperty().addListener((obs, oldRow, newRow) -> { 
//    if (oldRow != null) { 
//     oldRow.itemProperty().removeListener(rowItemListener); 
//     if (oldRow.getItem() != null) { 
//      textField.textProperty().unbindBidirectional(property.apply((Person)oldRow.getItem())); 
//     } 
//    } 
//    if (newRow != null) { 
//     newRow.itemProperty().addListener(rowItemListener); 
//     if (newRow.getItem() != null) { 
//      textField.textProperty().bindBidirectional(property.apply((Person)newRow.getItem())); 
//     } 
//    } 
//   }); 

      textField.textProperty().bindBidirectional(EasyBind.monadic(cell.tableRowProperty()) 
        .selectProperty(TableRow::itemProperty) 
        .selectProperty(p -> (property.apply((Person)p)))); 

      return cell ; 
     }); 
     return col ; 
    } 

    public static class Person { 
     private final StringProperty firstName = new SimpleStringProperty(); 
     private final StringProperty lastName = new SimpleStringProperty(); 
     private final StringProperty email = new SimpleStringProperty(); 

     public Person(String firstName, String lastName, String email) { 
      setFirstName(firstName); 
      setLastName(lastName); 
      setEmail(email); 
     } 

     public final StringProperty firstNameProperty() { 
      return this.firstName; 
     } 

     public final java.lang.String getFirstName() { 
      return this.firstNameProperty().get(); 
     } 

     public final void setFirstName(final java.lang.String firstName) { 
      this.firstNameProperty().set(firstName); 
     } 

     public final StringProperty lastNameProperty() { 
      return this.lastName; 
     } 

     public final java.lang.String getLastName() { 
      return this.lastNameProperty().get(); 
     } 

     public final void setLastName(final java.lang.String lastName) { 
      this.lastNameProperty().set(lastName); 
     } 

     public final StringProperty emailProperty() { 
      return this.email; 
     } 

     public final java.lang.String getEmail() { 
      return this.emailProperty().get(); 
     } 

     public final void setEmail(final java.lang.String email) { 
      this.emailProperty().set(email); 
     } 


    } 

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

(这里的恼人的向下转换是因为TableCell<S,T>.getTableRow()返回代替TableRow<S>原始TableRow对象,的原因,我从来没有明白。)

+0

非常感谢你,我非常感谢你的努力。但是如果我选择你的解决方案,那么我会失去选择和e的选项。 G。复制/粘贴表格单元格。 – Roland

+0

其实,你仍然可以选择单元格,虽然它确实很难:你需要单击单元格但在文本字段外(默认情况下,有少量空间)。但是,您也可以将焦点侦听器添加到文本字段,并在获得焦点时将单元格设置为选中状态。请参阅完整示例中的更新。 –

+0

谢谢,但它不直观。我们正试图用JavaFX替换Swing。我们有沉重的桌子使用。到目前为止,我所看到的JavaFX缺乏人们对桌面的期望。我想这就是为什么它调用tableVIEW而不是TableEditor。基本上我们的用户需要他们与Excel一起使用的东西。我仍然想知道为什么Oracle不以这种方式实施这个表。我确实尝试了SpreadsheetView。但是,当我甚至无法在ControlsFX采样器jar中显示该示例时,这已经失败。必须是一些Java版本的东西,因为它在几个月前运行。您的意见始终很感谢,谢谢! – Roland