TableView是個十分有用的控件,適應性和靈活性非常強,可以對它進行任意的修改,比如界面樣式、功能。本文將從一步步提問的方式講解TableView
歡迎加入我的javafx探討群:518914410
本文版權原創 by cmlanche.com, 文章鏈接:http://cmlanche.com/2017/06/08/JavaFx-TableView詳解/
-
創建已知列的TableView
已知列的表格的創建,需要把TableView的TableColumn關聯到模型的屬性,TableView是個模板類,其實是TableView
,這個T就是模型,例如下代碼: // MyModel.java public class MyModel{ private String name; private String url; // getters, setters ... } // init your tableView TableColumn<MyModel, String> t1 = new TableColumn(); // 關聯MyModel中的name屬性 t1.setCellValueFactory(new PropertyValueFactory<>("name")); t1.setCellFactory(p->{ // 創建此列的Cell的時候的回調,允許讓你自己去創建 });
特別要說明的是,
setCellValueFactory
和setCellFactory
不是沖突的,我用的時候一直以為是沖突,就是只能用其中一個,另外一個就失效了,其實不是,setCellFactory它的意圖是在創建這列的時候要做的事情,你可以改變TableCell的任何內容,包括UI和Value,而setCellValueFactory呢,它的重點是關聯屬性,從你傳遞給它的Model中通過對應屬性的getter來獲取值。在setCellFactory中的TableCell有個回調,叫updateItem
,它可以獲取到你設置到此Cell的值,這個值是跟setCellValueFactory所關聯的屬性有關。 -
創建動態列的TableView
參考:https://community.oracle.com/thread/2474328
因為列是不定的,模型是沒有屬性對應的,創建列的時候你根本不知道列是什么,看如下實現代碼:
column.setCellValueFactory(param -> { ObservableList<VarCell> values = param.getValue(); if (columnIndex >= values.size()) { return new SimpleObjectProperty<>(null); } else { return new SimpleObjectProperty<>(param.getValue().get(columnIndex)); } });
創建動態列的tableview,它的模型是一個
ObservableList<T>
,你的setCellValueFactory
不能使用PropertyValueFactory
,而是如上代碼所示,通過列的索引來獲取此列的值 -
[列拖動] 如何捕獲列拖動事件?
列拖動是tablview一個默認的自帶的效果,但是並沒有專門的事件給你去監聽它,而是監聽列的變化,方法:給tableview的columns添加Listener,判斷變動列的狀態是否是replaced的狀態,例如:
tableView.getColumns().addListener(new ListChangeListener<TableColumn<ObservableList<VarCell>, ?>>() { @Override public void onChanged(Change<? extends TableColumn<ObservableList<VarCell>, ?>> change) { change.next(); if (change.wasReplaced()) { // 表示當前拖動過了 } } });
-
[列拖動] 如何防止第一列被拖動?
在上一問的基礎上,實現第一列不允許被拖動的功能。
參考:https://stackoverflow.com/questions/30645606/javafx-restrict-column-rearrangement-on-drag-and-drop
tableView.getColumns().addListener(new ListChangeListener<TableColumn<ObservableList<String>, ?>>() { private boolean suspended; @Override public void onChanged(Change<? extends TableColumn<ObservableList<String>, ?>> change) { change.next(); if (change.wasReplaced() && !suspended) { List<TableColumn<ObservableList<String>, ?>> oldList = new ArrayList<>(change.getRemoved()); List<TableColumn<ObservableList<String>, ?>> newList = new ArrayList<>(tableView.getColumns()); // first column changed => revert to original list if (oldList.get(0) != newList.get(0)) { this.suspended = true; tableView.getColumns().setAll(oldList); this.suspended = false; } } } });
上面的代碼中有三個關鍵的地方,是tableview原本提供的api,一個是
change.wasReplaced
表示當前的變動是否被替換了,第二個是change.getRemoved
,表示獲取要移除掉的列,進一步的意思就是原來的列,也就是此前的tablecolumns,第三個是tableview.getColumns
這個是獲取現在列,有了這些信息,就可以判斷,oldList.get(0)!=newList(0)
,表示如果新老列的第一列不相同,表示是第一列是變動的,但是我們不允許變動,因此,調用tableview.getColumns().setAll(oldList)
用來恢復原來的列。這樣就禁止拖動第一列了。 -
[列拖動] 如何禁用列拖動效果?
給列設置一個屬性:
column.impl_setReorderable(false);
impl_setReorderable
前面帶impl_前綴,表示它是一個將來可能會被刪除的方法,但是為了解決目前無法解決的問題,暫時把impl的私有方法改為了public方法,參考我的博客中的如何自定義Taborder的文章,是一樣的道理。 -
[行拖動] 如何拖動行,進行換行?
參考:https://stackoverflow.com/questions/28603224/sort-tableview-with-drag-and-drop-rows
已經測試過的代碼:
tableView.setRowFactory(tv -> { TableRow<ObservableList<String>> row = new TableRow<>(); row.setOnDragDetected(event -> { log.info("row drag detected"); if (!row.isEmpty()) { Integer index = row.getIndex(); Dragboard db = row.startDragAndDrop(TransferMode.MOVE); db.setDragView(row.snapshot(null, null)); ClipboardContent cc = new ClipboardContent(); cc.put(SERIALIZED_MIME_TYPE, index); db.setContent(cc); event.consume(); } }); row.setOnDragOver(event -> { log.info("row drag over"); Dragboard db = event.getDragboard(); if (db.hasContent(SERIALIZED_MIME_TYPE)) { if (row.getIndex() != ((Integer) db.getContent(SERIALIZED_MIME_TYPE)).intValue()) { event.acceptTransferModes(TransferMode.COPY_OR_MOVE); event.consume(); } } }); row.setOnDragDropped(event -> { log.info("row drag dropped"); Dragboard db = event.getDragboard(); if (db.hasContent(SERIALIZED_MIME_TYPE)) { int draggedIndex = (Integer) db.getContent(SERIALIZED_MIME_TYPE); ObservableList<String> draggedPerson = tableView.getItems().remove(draggedIndex); int dropIndex; if (row.isEmpty()) { dropIndex = tableView.getItems().size(); } else { dropIndex = row.getIndex(); } tableView.getItems().add(dropIndex, draggedPerson); event.setDropCompleted(true); tableView.getSelectionModel().select(dropIndex); event.consume(); } }); return row; });
-
修改TableView樣式
使用css,參考如下我的測試代碼
.table-view { -fx-border-width: 1px; -fx-border-color: #CACACA; -fx-background-color: transparent; } .table-view:focused { -fx-background-color: transparent; } .table-view .table-cell { -fx-font-size: 12px; } .table-view .filler { -fx-background-color: #BDE8FF; } .table-view .text { -fx-text-fill: red; } .table-view .column-header { -fx-background-color: #BDE8FF; -fx-pref-height: 37px; -fx-border-width: 1px; -fx-border-color: #D9D9D9; -fx-border-insets: -2px -2px 0px -2px; } .table-view .column-header-background .label { -fx-text-fill: #363739; -fx-font-weight: normal; -fx-font-size: 12px; } .table-row-cell { /*行高*/ -fx-cell-size: 35px; } .table-row-cell .cell { -fx-alignment: center; -fx-text-fill: #333333; } .table-view .table-cell:selected { -fx-text-fill: white; } .table-view .table-column .column-header { -fx-background-color: #363739; } .cell { /*-fx-border-width: 0px 1px 0 0;*/ /*-fx-border-color: #CACACA;*/ } .table-view .scroll-bar { -fx-background-color: transparent; } .viewport { -fx-background-color: white; }
-
如何自定義列頭,比如自己設置一個可編輯的列頭呢?
答案是給你的TableColumn設置
setGraphic
,可編輯的列頭的話,你讓你的graphic中有編輯框,雙擊顯示編輯框,按enter鍵確認編輯,如下是一個我的實現:package com.itestin.ui.datamgt.table; import com.itestin.ui.recordNreplay.logic.CommonLogic; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; import javafx.scene.layout.StackPane; /** * Created by cmlanche on 2017/6/1. */ public class EditColumn extends StackPane { private EditColumnCallback callback; private Label label; private TextField textField; private StringProperty title; private BooleanProperty editing; private BooleanProperty editable; private BooleanProperty textFieldFocus; private TableColumn tableColumn; private String oldTitle; public EditColumn(TableColumn tableColumn) { super(); this.tableColumn = tableColumn; this.init(); } private void init() { this.setMaxWidth(120); this.setAlignment(Pos.CENTER); this.setStyle("-fx-background-color: #D9D9D9;"); label = new Label(); label.setStyle("-fx-font-size: 12px; -fx-text-fill: #333333;"); textField = new TextField(); textField.getStyleClass().add("tablefx-header-editor"); label.textProperty().bindBidirectional(titleProperty()); textField.textProperty().bindBidirectional(titleProperty()); label.setTooltip(new Tooltip()); label.getTooltip().textProperty().bindBidirectional(label.textProperty()); this.getChildren().addAll(label, textField); editingProperty().addListener((observable, oldValue, newValue) -> { if (isEditable()) { if (newValue) { label.setVisible(false); textField.setVisible(true); textField.setFocusTraversable(true); textField.requestFocus(); } else { label.setVisible(true); textField.setVisible(false); } } }); textField.textProperty().addListener((observable, oldValue, newValue) -> { textField.setStyle("-fx-border-color: #25B3FA;"); }); this.setStyle("-fx-background-color: transparent;"); textField.setOnKeyPressed(event -> { if (isEditing()) { if (event.getCode() == KeyCode.ENTER) { event.consume(); // 放置對tabview的其他列產生影響,不讓消息透傳 oldTitle = textField.getText(); setEditing(false); // 提交編輯 if (callback != null) { callback.editCommit(tableColumn, textField.getText()); } } else if (event.getCode() == KeyCode.ESCAPE) { setTitle(oldTitle); setEditing(false); // 取消編輯 if (callback != null) { callback.cancelEdit(); } } else if (event.getCode() == KeyCode.TAB) { setEditing(false); } } }); textFieldFocusProperty().addListener((observable, oldValue, newValue) -> { if (newValue) { textField.setFocusTraversable(true); textField.requestFocus(); } }); setEditing(false); } public String getTitle() { return titleProperty().get(); } public StringProperty titleProperty() { if (title == null) { title = new SimpleStringProperty(); } return title; } public void setTitle(String title) { this.oldTitle = title; this.titleProperty().set(title); } public boolean isEditing() { return editingProperty().get(); } public BooleanProperty editingProperty() { if (editing == null) { editing = new SimpleBooleanProperty(true); } return editing; } public void setEditing(boolean editing) { this.editingProperty().set(editing); } public void setEditCallback(EditColumnCallback callback) { this.callback = callback; } public boolean isEditable() { return editableProperty().get(); } public BooleanProperty editableProperty() { if (editable == null) { editable = new SimpleBooleanProperty(true); } return editable; } public void setEditable(boolean editable) { this.editableProperty().set(editable); } public boolean isTextFieldFocus() { return textFieldFocusProperty().get(); } public BooleanProperty textFieldFocusProperty() { if (textFieldFocus == null) { textFieldFocus = new SimpleBooleanProperty(); } return textFieldFocus; } public void setTextFieldFocus(boolean textFieldFocus) { this.textFieldFocusProperty().set(textFieldFocus); } }