JavaFX TableView 单击编辑并自动插入行?
JavaFX TableView Edit with single click and auto insert row?
我在我的程序中使用 tableview 来显示几行它很好。
我的程序用于会计,如你所知,其中有很多数据插入,我使用了 table 视图,但我遇到了很多问题:.
问题 1:当用户想要修改一个值时,他将双击以编辑单元格我希望我的用户 select 任何行并开始输入。
问题 2:在我的程序中,用户可能会在一天的工作中插入 100 行,我需要一种自动添加行的策略,例如,当他插入行时,它应该在当前行下添加一个空行以完成他的数据轻松入门。
请帮助我解决这些问题,因为我的用户每天都会输入大量数据。
谢谢。
这是一个例子
- 在焦点单元格上输入后立即进入编辑模式
- 当您在最后一行按回车键时创建一个新行(仅作为示例)。或者,您可以单击按钮添加新行。如果您希望在最后一行使用制表符来创建新行,则必须相应地更改代码。
我建议不要使用空行,因为它们会出现在您的模型中。
InlineEditingWithDynamicRowAdding.java
import javafx.application.Application;
import javafx.scene.control.TableView;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableCell;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.DoubleProperty;
import javafx.collections.FXCollections;
import javafx.scene.input.KeyEvent;
import javafx.event.EventHandler;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.KeyCode;
import javafx.scene.control.TablePosition;
import javafx.scene.layout.FlowPane;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.cell.TextFieldTableCell;
public class InlineEditingWithDynamicRowAdding 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());
// 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) {
// move focus & selection
// we need to clear the current selection first or else the selection would be added to the current selection since we are in multi selection mode
TablePosition pos = table.getFocusModel().getFocusedCell();
if (pos.getRow() == -1) {
table.getSelectionModel().select(0);
}
// add new row when we are at the last row
else if (pos.getRow() == table.getItems().size() -1) {
addRow();
}
// select next row, but same column as the current selection
else if (pos.getRow() < table.getItems().size() -1) {
table.getSelectionModel().clearAndSelect( pos.getRow() + 1, pos.getTableColumn());
}
}
}
});
// single cell selection mode
table.getSelectionModel().setCellSelectionEnabled(true);
// add row index column as 1st column
// -------------------------------------
TableColumn<Data, Data> indexCol = new TableColumn<Data, Data>("#");
indexCol.setCellFactory(new Callback<TableColumn<Data, Data>, TableCell<Data, Data>>() {
@Override public TableCell<Data, Data> call(TableColumn<Data, Data> param) {
return new TableCell<Data, Data>() {
@Override protected void updateItem(Data item, boolean empty) {
super.updateItem(item, empty);
if (this.getTableRow() != null) {
int index = this.getTableRow().getIndex();
if( index < table.getItems().size()) {
int rowNum = index + 1;
setText( String.valueOf(rowNum));
} else {
setText("");
}
} else {
setText("");
}
}
};
}
});
table.getColumns().add( 0, indexCol); // number column is at index 0
// allow multi selection
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// buttons
// --------------------------------------------
FlowPane buttonBar = new FlowPane();
// add new row button
Button addButton = new Button( "Add");
addButton.setOnAction(e -> {
addRow();
});
addButton.setFocusTraversable(false);// don't let it get the focus or else the table would lose it when we click the button and we's have to request the focus on the table in the event handler
// remove selected rows button
Button removeButton = new Button( "Remove");
removeButton.setOnAction(e -> {
removeSelectedRows();
});
removeButton.setFocusTraversable(false);// don't let it get the focus or else the table would lose it when we click the button and we's have to request the focus on the table in the event handler
buttonBar.getChildren().addAll( addButton, removeButton);
// add nodes to stage
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(buttonBar);
Scene scene = new Scene( root, 800,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.show();
// select first cell
// TODO: why isn't this selecting the 1st cell in the index column?
table.getSelectionModel().selectFirst();
}
/**
* Insert a new default row to the table, select a cell of it and scroll to it.
*/
public void addRow() {
// get current position
TablePosition pos = table.getFocusModel().getFocusedCell();
// clear current selection
table.getSelectionModel().clearSelection();
// create new record and add it to the model
Data data = new Data(0d,0d);
table.getItems().add( data);
// get last row
int row = table.getItems().size() - 1;
table.getSelectionModel().select( row, pos.getTableColumn());
// scroll to new row
table.scrollTo( data);
}
/**
* Remove all selected rows.
*/
public void removeSelectedRows() {
table.getItems().removeAll(table.getSelectionModel().getSelectedItems());
// table selects by index, so we have to clear the selection or else items with that index would be selected
table.getSelectionModel().clearSelection();
}
/**
* 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);
}
}
application.css
/* edit cell appearance */
/* If the padding is 0, then there's a grey area. If you set it to 2, then there won't be a great area.
* However if you use a border color, then the border requires its size, so the padding is set to 2 - 1 = 1.
* You have to toy around with the border. If necessary give it a width of 2 and make it the same color as the background color.
*/
.text-field-table-cell .text-field {
-fx-padding: 1;
-fx-border-color:red;
-fx-border-width:1;
-fx-background-color:yellow;
}
.table-cell:focused {
-fx-padding: 0;
}
/* right-align the cell content in view mode */
.table-cell {
-fx-alignment: CENTER-RIGHT;
}
/* right-align the cell content in edit mode */
.text-field {
-fx-alignment: CENTER-RIGHT;
}
/* colorize background only of rows which have data */
.table-row-cell:empty {
-fx-background-color: white;
}
.table-row-cell:empty .table-cell {
-fx-border-width: 0px;
}
我在我的程序中使用 tableview 来显示几行它很好。 我的程序用于会计,如你所知,其中有很多数据插入,我使用了 table 视图,但我遇到了很多问题:.
问题 1:当用户想要修改一个值时,他将双击以编辑单元格我希望我的用户 select 任何行并开始输入。
问题 2:在我的程序中,用户可能会在一天的工作中插入 100 行,我需要一种自动添加行的策略,例如,当他插入行时,它应该在当前行下添加一个空行以完成他的数据轻松入门。
请帮助我解决这些问题,因为我的用户每天都会输入大量数据。 谢谢。
这是一个例子
- 在焦点单元格上输入后立即进入编辑模式
- 当您在最后一行按回车键时创建一个新行(仅作为示例)。或者,您可以单击按钮添加新行。如果您希望在最后一行使用制表符来创建新行,则必须相应地更改代码。
我建议不要使用空行,因为它们会出现在您的模型中。
InlineEditingWithDynamicRowAdding.java
import javafx.application.Application;
import javafx.scene.control.TableView;
import javafx.collections.ObservableList;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableCell;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.DoubleProperty;
import javafx.collections.FXCollections;
import javafx.scene.input.KeyEvent;
import javafx.event.EventHandler;
import javafx.scene.control.SelectionMode;
import javafx.scene.input.KeyCode;
import javafx.scene.control.TablePosition;
import javafx.scene.layout.FlowPane;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.util.StringConverter;
import javafx.scene.Scene;
import javafx.scene.control.cell.TextFieldTableCell;
public class InlineEditingWithDynamicRowAdding 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());
// 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) {
// move focus & selection
// we need to clear the current selection first or else the selection would be added to the current selection since we are in multi selection mode
TablePosition pos = table.getFocusModel().getFocusedCell();
if (pos.getRow() == -1) {
table.getSelectionModel().select(0);
}
// add new row when we are at the last row
else if (pos.getRow() == table.getItems().size() -1) {
addRow();
}
// select next row, but same column as the current selection
else if (pos.getRow() < table.getItems().size() -1) {
table.getSelectionModel().clearAndSelect( pos.getRow() + 1, pos.getTableColumn());
}
}
}
});
// single cell selection mode
table.getSelectionModel().setCellSelectionEnabled(true);
// add row index column as 1st column
// -------------------------------------
TableColumn<Data, Data> indexCol = new TableColumn<Data, Data>("#");
indexCol.setCellFactory(new Callback<TableColumn<Data, Data>, TableCell<Data, Data>>() {
@Override public TableCell<Data, Data> call(TableColumn<Data, Data> param) {
return new TableCell<Data, Data>() {
@Override protected void updateItem(Data item, boolean empty) {
super.updateItem(item, empty);
if (this.getTableRow() != null) {
int index = this.getTableRow().getIndex();
if( index < table.getItems().size()) {
int rowNum = index + 1;
setText( String.valueOf(rowNum));
} else {
setText("");
}
} else {
setText("");
}
}
};
}
});
table.getColumns().add( 0, indexCol); // number column is at index 0
// allow multi selection
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// buttons
// --------------------------------------------
FlowPane buttonBar = new FlowPane();
// add new row button
Button addButton = new Button( "Add");
addButton.setOnAction(e -> {
addRow();
});
addButton.setFocusTraversable(false);// don't let it get the focus or else the table would lose it when we click the button and we's have to request the focus on the table in the event handler
// remove selected rows button
Button removeButton = new Button( "Remove");
removeButton.setOnAction(e -> {
removeSelectedRows();
});
removeButton.setFocusTraversable(false);// don't let it get the focus or else the table would lose it when we click the button and we's have to request the focus on the table in the event handler
buttonBar.getChildren().addAll( addButton, removeButton);
// add nodes to stage
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(buttonBar);
Scene scene = new Scene( root, 800,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
stage.setScene(scene);
stage.show();
// select first cell
// TODO: why isn't this selecting the 1st cell in the index column?
table.getSelectionModel().selectFirst();
}
/**
* Insert a new default row to the table, select a cell of it and scroll to it.
*/
public void addRow() {
// get current position
TablePosition pos = table.getFocusModel().getFocusedCell();
// clear current selection
table.getSelectionModel().clearSelection();
// create new record and add it to the model
Data data = new Data(0d,0d);
table.getItems().add( data);
// get last row
int row = table.getItems().size() - 1;
table.getSelectionModel().select( row, pos.getTableColumn());
// scroll to new row
table.scrollTo( data);
}
/**
* Remove all selected rows.
*/
public void removeSelectedRows() {
table.getItems().removeAll(table.getSelectionModel().getSelectedItems());
// table selects by index, so we have to clear the selection or else items with that index would be selected
table.getSelectionModel().clearSelection();
}
/**
* 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);
}
}
application.css
/* edit cell appearance */
/* If the padding is 0, then there's a grey area. If you set it to 2, then there won't be a great area.
* However if you use a border color, then the border requires its size, so the padding is set to 2 - 1 = 1.
* You have to toy around with the border. If necessary give it a width of 2 and make it the same color as the background color.
*/
.text-field-table-cell .text-field {
-fx-padding: 1;
-fx-border-color:red;
-fx-border-width:1;
-fx-background-color:yellow;
}
.table-cell:focused {
-fx-padding: 0;
}
/* right-align the cell content in view mode */
.table-cell {
-fx-alignment: CENTER-RIGHT;
}
/* right-align the cell content in edit mode */
.text-field {
-fx-alignment: CENTER-RIGHT;
}
/* colorize background only of rows which have data */
.table-row-cell:empty {
-fx-background-color: white;
}
.table-row-cell:empty .table-cell {
-fx-border-width: 0px;
}