2015-04-12 72 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