如何使用 CSS 设置两个相邻 TableView 的边框样式,使它们在 JavaFX 11 (OpenJFX 11) 中看起来像是一个 TableView
How to use CSS to style the borders of two adjacent TableViews so that they appear to be a single TableView in JavaFX 11 (OpenJFX 11)
一个CSS新手问题。
我在两个相邻的 TableView
中显示大量数据,并双向绑定了它们的 ScrollBar
、FocusModel
和 SelectionModel
让他们保持同步。
我现在正在尝试让两个 TableView
看起来像一个,并且想要:
- 当 either
TableView
有焦点时 both TableView
s 周围的默认蓝色边框。
-
TableView
周围的默认灰色边框,当 都没有焦点时。
TableView
相交处没有边界。
我该怎么做?
像这样的东西会很棒:
到目前为止,我已经能够通过这样做删除 "meet" 边框:
tvLeft.getStyleClass().add("my-table-view-left");
tvRight.getStyleClass().add("my-table-view-right");
与 CSS 像这样:
.my-table-view-left:focused {
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
}
.my-table-view-right:focused {
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
}
当其中一行被选中时,这也正确地设置了单个 TableView
的边框。
但是,当 either 具有焦点时,我不知道如何在 both TableView
s 周围设置边框.
这是一个 MVCE。很抱歉它的长度,但我需要包含同步代码才能有一个测试用例。
我在 Windows 7 上的 Netbeans 10.0 中使用 11.0.2 版本的 OpenJDK 和 OpenJFX,运行。
MyTableViewCSS.css
/************************************************************************************************************
Trying to set the borders of the synchronised tableviews
*/
.my-table-view-left:focused {
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
-fx-focus-color: red; /* for testing only */
}
.my-table-view-right:focused {
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
-fx-focus-color: red; /* for testing only */
}
/************************************************************************************************************
The following section hides the horizontal and vertical tableview scrollbars.
They are replaced by scrollbars manually added to the form.
Source:
*/
.my-table-view *.scroll-bar:horizontal *.increment-button,
.my-table-view *.scroll-bar:horizontal *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
}
.my-table-view *.scroll-bar:horizontal *.increment-arrow,
.my-table-view *.scroll-bar:horizontal *.decrement-arrow {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
.my-table-view *.scroll-bar:vertical *.increment-button,
.my-table-view *.scroll-bar:vertical *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
}
.my-table-view *.scroll-bar:vertical *.increment-arrow,
.my-table-view *.scroll-bar:vertical *.decrement-arrow {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
Test014.java
package test014;
import java.util.Arrays;
import java.util.function.Function;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;
public class Test014 extends Application {
private final ObservableList<DataModel> ol = FXCollections.observableArrayList();
private final TableView<DataModel> tvLeft = new TableView();
private final TableView<DataModel> tvRight = new TableView();
//Show a tableview that should continue to use the default Modena style. That way I'll know
//if I've messed anything up!
private final ObservableList<DataModel> olDefaultStyle = FXCollections.observableArrayList();
private final TableView<DataModel> tvDefaultStyle = new TableView();
private final ScrollBar vScroll = new ScrollBar();
private final ScrollBar hScroll = new ScrollBar();
private Parent createContent() {
loadDummyData();
createTableColumns();
tvLeft.setItems(ol);
tvRight.setItems(ol);
tvLeft.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tvRight.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
//Bi-directionally bind the selection and focus models of the two tables.
tvLeft.selectionModelProperty().bindBidirectional(tvRight.selectionModelProperty());
tvLeft.focusModelProperty().bindBidirectional(tvRight.focusModelProperty());
tvLeft.getSelectionModel().selectFirst();
vScroll.setOrientation(Orientation.VERTICAL);
hScroll.setOrientation(Orientation.HORIZONTAL);
tvLeft.getStyleClass().add("my-table-view");
tvRight.getStyleClass().add("my-table-view");
tvLeft.getStyleClass().add("my-table-view-left");
tvRight.getStyleClass().add("my-table-view-right");
Platform.runLater(() -> {
Scene scene = tvLeft.getScene();
String appStyleSheet = "MyTableViewCSS.css";
scene.getStylesheets().add(this.getClass().getResource(appStyleSheet).toString());
//Do the bindings necesary to synchronise the tableviews
synchroniseTheTableViews();
});
//Load the tableviews in a gridpane so I can control the width of the left-hand tableview
GridPane gp = new GridPane();
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setPrefWidth(130D);
cc1.setMaxWidth(130D);
cc1.setMinWidth(130D);
gp.getColumnConstraints().addAll(Arrays.asList(cc1));
gp.add(tvLeft, 0, 0);
gp.add(tvRight, 1, 0);
GridPane.setValignment(tvLeft, VPos.TOP);
GridPane.setVgrow(tvRight, Priority.ALWAYS);
//Put the gridpane in a borderpane so I can then add vScroll and hScroll
BorderPane content = new BorderPane();
gp.prefHeightProperty().bind(content.heightProperty());
gp.prefWidthProperty().bind(content.widthProperty());
content.setCenter(gp);
content.setRight(vScroll);
content.setBottom(hScroll);
//Add buttons to show and hide the tableview that should continue to have the default Modena style
Button btnShowTvDefaultStyle = new Button("Show TV with default style");
btnShowTvDefaultStyle.setOnAction(event -> {
content.setLeft(tvDefaultStyle);
});
Button btnHideTvDefaultStyle = new Button("Hide TV with default style");
btnHideTvDefaultStyle.setOnAction(event -> {
content.setLeft(null);
});
HBox hb = new HBox();
hb.setSpacing(20D);
hb.setPadding(new Insets(20D));
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(Arrays.asList(btnShowTvDefaultStyle, btnHideTvDefaultStyle));
content.setTop(hb);
return content;
}
private void synchroniseTheTableViews() {
//Bind the first table's header row height to that of the second
Pane header1 = (Pane) tvLeft.lookup("TableHeaderRow");
Pane header2 = (Pane) tvRight.lookup("TableHeaderRow");
header1.prefHeightProperty().bind(header2.heightProperty());
//Now synchronise the scrollbars
ScrollBar scrollBarLeftTv;
ScrollBar scrollBarRightTv;
for ( Node node1: tvLeft.lookupAll(".scroll-bar") ) {
if ( node1 instanceof ScrollBar && ((ScrollBar) node1).getOrientation() == Orientation.VERTICAL ) {
scrollBarLeftTv = (ScrollBar) node1;
for ( Node node2: tvRight.lookupAll(".scroll-bar") ) {
if ( node2 instanceof ScrollBar && ((ScrollBar) node2).getOrientation() == Orientation.VERTICAL ) {
scrollBarRightTv = (ScrollBar) node2;
scrollBarRightTv.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
scrollBarRightTv.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
scrollBarRightTv.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
scrollBarRightTv.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
scrollBarRightTv.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
vScroll.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
vScroll.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
vScroll.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
vScroll.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
vScroll.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
}
}
}
}
for ( Node node: tvRight.lookupAll(".scroll-bar") ) {
if ( node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.HORIZONTAL ) {
ScrollBar scrollBar = (ScrollBar) node;
hScroll.valueProperty().bindBidirectional(scrollBar.valueProperty());
hScroll.maxProperty().bindBidirectional(scrollBar.maxProperty());
hScroll.minProperty().bindBidirectional(scrollBar.minProperty());
hScroll.unitIncrementProperty().bindBidirectional(scrollBar.unitIncrementProperty());
hScroll.visibleAmountProperty().bindBidirectional(scrollBar.visibleAmountProperty());
}
}
}
private void createTableColumns() {
for ( int i=0; i<2; i++ ) {
TableColumn<DataModel, String> col = createColumn(i, DataModel::field1Property);
tvLeft.getColumns().add(col);
}
for ( int i=0; i<12; i++ ) {
TableColumn<DataModel, String> col = createColumn(i, DataModel::field2Property);
tvRight.getColumns().add(col);
}
for ( int i=0; i<3; i++ ) {
TableColumn<DataModel, String> col = createColumn(i, DataModel::field1Property);
tvDefaultStyle.getColumns().add(col);
}
tvDefaultStyle.setItems(olDefaultStyle);
tvDefaultStyle.setPrefWidth(100D);
tvDefaultStyle.setMaxWidth(100D);
tvDefaultStyle.setMinWidth(100D);
}
private TableColumn<DataModel, String> createColumn (int colNum, Function<DataModel, StringProperty> property) {
TableColumn<DataModel,String> col = new TableColumn<>("field" + colNum);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
return col;
}
private void loadDummyData() {
for ( int i=0; i<20; i++ ) {
ol.add(new DataModel(Integer.toString(i), "a"));
}
for ( int i=0; i<30; i++ ) {
olDefaultStyle.add(new DataModel(Integer.toString(i), "a"));
}
}
private class DataModel {
private final StringProperty field1;
private final StringProperty field2;
public DataModel(
String field1,
String field2
) {
this.field1 = new SimpleStringProperty(field1);
this.field2 = new SimpleStringProperty(field2);
}
public String getField1() {return field1.get().trim();}
public void setField1(String field1) {this.field1.set(field1);}
public StringProperty field1Property() {return field1;}
public String getField2() {return field2.get().trim();}
public void setField2(String field2) {this.field2.set(field2);}
public StringProperty field2Property() {return field2;}
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("OpenJFX11 - Synchronise two TableViews");
stage.setWidth(700D);
stage.setHeight(400D);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
根据用户 kleopatra 的建议,我使用 PseudoClass
es 弄清楚了如何做到这一点。
对于每个同步的 TableView
,我将 CSS 定义为:
在除 TableView
s "met".
以外的所有边框上设置摩德纳、蓝色、聚焦样式
在 TableView
s "met".
的边框上设置摩德纳灰色非聚焦样式
然后我为每个边框样式声明了一个 PseudoClass
。
最后,我在每个 TableView
的 focusedProperty()
中添加了一个 ChangeListener
来激活两个 TableView
的 PseudoClass
当其中一个得到焦点并在失去焦点时停用 PseudoClass
es。
最终结果如下所示:
正如 kleopatra 正确指出的那样,这使用了不受支持的功能,但它对我有用。我也没有尝试使用可编辑的 TableView
s,也没有尝试设置单元格(而不是行)选择。无论如何,我发布了代码片段和一个包含所有同步代码的 SSCE,以防它们帮助其他人。
CSS:
/* For the LEFT-hand tableview */
.my-table-view:left_focussed {
/* Apply the standard Modena focussed style to all bar the right-hand border (which is the
border that abuts the right-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
-fx-background-radius: 2, 0, 0;
/* Set the abutting right-hand border to grey. */
-fx-border-width: 0 0.7 0 0;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
/* For the RIGHT-hand tableview */
.my-table-view:right_focussed {
/* Apply the standard Modena focussed style to all bar the left-hand border (which is the
border that abuts the left-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
-fx-background-radius: 2, 0, 0;
/* Set the abutting left-hand border to grey. */
-fx-border-width: 0 0 0 0.7;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
PseudoClass
声明:
private final PseudoClass leftFocussed = PseudoClass.getPseudoClass("left_focussed");
private final PseudoClass rightFocussed = PseudoClass.getPseudoClass("right_focussed");
ChangeListener
s:
tvLeft.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
tvRight.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
和togglePseudoClassStates()
方法:
private void togglePseudoClassStates(boolean requiredState) {
//Set the tableview border style
tvLeft.pseudoClassStateChanged(leftFocussed, requiredState);
tvRight.pseudoClassStateChanged(rightFocussed, requiredState);
}
这是 SSCE。我在 Windows 7 上的 Netbeans 10.0 中使用 11.0.2 版本的 OpenJDK 和 OpenJFX 运行。
MyTableViewCSS.css
/************************************************************************************************************
This section defines the border style to be applied to the synchronised tableviews.
a) For the LEFT-hand tableview that represents the "frozen" columns:
i) Set all bar the RIGHT border to the default Modena focussed style.
ii) Set the RIGHT border to be the default Modena unfocussed style.
b) For the RIGHT-hand tableview that represents the "scrollable" columns:
i) Set all bar the LEFT border to the default Modena focussed style.
ii) Set the LEFT border to be the default Modena unfocussed style.
*/
/* For the LEFT-hand tableview */
.my-table-view:left_focussed {
/* Apply the standard Modena focussed style to all bar the right-hand border (which is the
border that abuts the right-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
-fx-background-radius: 2, 0, 0;
/* Set the abutting right-hand border to grey. */
-fx-border-width: 0 0.7 0 0;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
/* For the RIGHT-hand tableview */
.my-table-view:right_focussed {
/* Apply the standard Modena focussed style to all bar the left-hand border (which is the
border that abuts the left-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
-fx-background-radius: 2, 0, 0;
/* Set the abutting left-hand border to grey. */
-fx-border-width: 0 0 0 0.7;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
/************************************************************************************************************
This section defines the style of the selection bars for selected rows in the synchronised tableviews.
When a row in one tableview is selected or gets focus, the bi-directional bindings select and focus the
corresponding row in other tableview. To make it seem like the two tableviews are one, the default Modena
focussed style has to be set on the selection bars for both:
a) the actual selected/focussed row; and
b) the row in the other tableview that's automatically selected/focussed by the bi-directional bindings.
*/
.my-table-view:selectionbar_focussed > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected,
.my-table-view:selectionbar_focussed > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected
{
-fx-background: -fx-selection-bar;
-fx-table-cell-border-color: derive(-fx-selection-bar, 20%);
}
/************************************************************************************************************
This section section hides the default horizontal and vertical scrollbars in the synchronised tableviews.
*/
.my-table-view:hidden_scrollbars *.scroll-bar *.increment-arrow,
.my-table-view:hidden_scrollbars *.scroll-bar *.decrement-arrow,
.my-table-view:hidden_scrollbars *.scroll-bar *.increment-button,
.my-table-view:hidden_scrollbars *.scroll-bar *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
Test014.java
/*
An example of using two, synchronised TableViews to display a wide set of data where:
a) The left-hand TableView contains the dataset's fixed (or "frozen") columns.
b) The right-hand TableView contains the dataset's scrollable columns.
The TableViews are synchronised by bi-directionally binding their ScrollBars and selection and focus models.
The in-built ScrollBars on both TableViews are hidden with CSS. Simultaneous scrolling of both TableViews is
achieved by manually adding vertical and horizontal ScrollBars and bi-directionally binding them to the
now-hidden TableView ScrollBars.
To then make all the components look like a single TableView:
a) The two TableViews are added to a GridPane, which in turn is added as the centre node of a BorderPane.
b) The manually created vertical ScrollBar is added as the right node of the BorderPane.
c) The manually created horizontal ScrollBar is added as the bottom node of the BorderPane.
CSS is then used to:
a) Set the borders around both TableViews (except for the borders that abut each other) to the default Modena
blue style when either TableView has focus.
b) Set the borders around both TableViews (except for the abutting borders) to the default Modena grey style
when both TableViews lose focus.
c) Set the abutting borders permanently to grey.
d) Set the selection bars on both TableViews to the default Modena blue style when a row in either TableView
is selected or receives focus.
e) Set the selection bars on both TableViews to the default Modena grey style when both TableViews lose focus.
CAVEAT: This has not been tested with editable TableViews nor with cell (rather than row) selection enabled.
*/
package test014;
import java.util.Arrays;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;
public class Test014 extends Application {
private final ObservableList<DataModel> ol = FXCollections.observableArrayList();
private final TableView<DataModel> tvLeft = new TableView();
private final TableView<DataModel> tvRight = new TableView();
//Show a tableview that should continue to use the default Modena style. That way I'll know
//if I've messed anything up!
private final ObservableList<DataModel> olDefaultStyle = FXCollections.observableArrayList();
private final TableView<DataModel> tvDefaultStyle = new TableView();
//Declare scrollbars that will be used to scroll the synchronised tableviews.
private final ScrollBar vScroll = new ScrollBar();
private final ScrollBar hScroll = new ScrollBar();
//Declare the pseudoclasses
private final PseudoClass leftFocussed = PseudoClass.getPseudoClass("left_focussed");
private final PseudoClass rightFocussed = PseudoClass.getPseudoClass("right_focussed");
private final PseudoClass selectionbarFocussed = PseudoClass.getPseudoClass("selectionbar_focussed");
private final PseudoClass hiddenScrollBars = PseudoClass.getPseudoClass("hidden_scrollbars");
private Parent createContent() {
loadDummyData();
createTableColumns();
createTvWithDefaultStyle();
tvLeft.setItems(ol);
tvRight.setItems(ol);
tvLeft.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tvRight.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
//Bi-directionally bind the selection and focus models of the two tables.
tvLeft.selectionModelProperty().bindBidirectional(tvRight.selectionModelProperty());
tvLeft.focusModelProperty().bindBidirectional(tvRight.focusModelProperty());
tvLeft.getSelectionModel().selectFirst();
//Set the orientation of the scrollbars
vScroll.setOrientation(Orientation.VERTICAL);
hScroll.setOrientation(Orientation.HORIZONTAL);
//Add the "my-table-view" styleclass to both tableviews. This allows me to access the
//custom pseudoclasses needed to style the tableviews.
tvLeft.getStyleClass().add("my-table-view");
tvRight.getStyleClass().add("my-table-view");
//Activate the pseudoclass to hide the default scrollbars of the synchronised tableviews
tvLeft.pseudoClassStateChanged(hiddenScrollBars, true);
tvRight.pseudoClassStateChanged(hiddenScrollBars, true);
//Add listeners to detect and act on focus changes
addListeners();
Platform.runLater(() -> {
Scene scene = tvLeft.getScene();
String appStyleSheet = "MyTableViewCSS.css";
scene.getStylesheets().add(this.getClass().getResource(appStyleSheet).toString());
//Do the bindings necesary to synchronise the tableviews
synchroniseTheTableViews();
});
//Load the tableviews in a gridpane so I can control the width of the left-hand tableview
GridPane gp = new GridPane();
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setPrefWidth(130D);
cc1.setMaxWidth(130D);
cc1.setMinWidth(130D);
gp.getColumnConstraints().addAll(Arrays.asList(cc1));
gp.add(tvLeft, 0, 0);
gp.add(tvRight, 1, 0);
GridPane.setValignment(tvLeft, VPos.TOP);
GridPane.setVgrow(tvRight, Priority.ALWAYS);
//Put the gridpane in a borderpane so I can then add vScroll and hScroll
BorderPane content = new BorderPane();
gp.prefHeightProperty().bind(content.heightProperty());
gp.prefWidthProperty().bind(content.widthProperty());
content.setCenter(gp);
content.setRight(vScroll);
content.setBottom(hScroll);
//Add buttons to show and hide the tableview that should continue to have the default Modena style
Button btnShowTvDefaultStyle = new Button("Show TV with default style");
btnShowTvDefaultStyle.setOnAction(event -> {
content.setLeft(tvDefaultStyle);
tvDefaultStyle.requestFocus();
});
Button btnHideTvDefaultStyle = new Button("Hide TV with default style");
btnHideTvDefaultStyle.setOnAction(event -> {
content.setLeft(null);
});
Button btnRequestFocus = new Button("request focus");
btnRequestFocus.setOnAction(event -> {
tvRight.requestFocus();
});
HBox hb = new HBox();
hb.setSpacing(20D);
hb.setPadding(new Insets(20D));
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(Arrays.asList(btnShowTvDefaultStyle, btnHideTvDefaultStyle, btnRequestFocus));
content.setTop(hb);
return content;
}
private void addListeners() {
//Add a focus change listener to each of the tableviews to add or remove the border and
//selection bar styles when focus is gained or lost.
tvLeft.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if ( isNowFocused ) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
tvRight.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if ( isNowFocused ) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
//Add a value change listener to the manually added scrollbars to request focus on one of the
//synchronised tableviews.
hScroll.valueProperty().addListener((obs, oldValue, newValue) -> {
tvLeft.requestFocus();
});
vScroll.valueProperty().addListener((obs, oldValue, newValue) -> {
tvLeft.requestFocus();
});
//Likewise, add a handler to detect any mouse action on the scrollbars and request focus
//on one of the tableviews.
hScroll.setOnMouseReleased(event -> {
tvLeft.requestFocus();
});
vScroll.setOnMouseReleased(event -> {
tvLeft.requestFocus();
});
}
private void togglePseudoClassStates(boolean requiredState) {
//Set the tableview border style
tvLeft.pseudoClassStateChanged(leftFocussed, requiredState);
tvRight.pseudoClassStateChanged(rightFocussed, requiredState);
//Set the selection bar style
tvLeft.pseudoClassStateChanged(selectionbarFocussed, requiredState);
tvRight.pseudoClassStateChanged(selectionbarFocussed, requiredState);
}
private void synchroniseTheTableViews() {
//Bind the first table's header row height to that of the second
Pane header1 = (Pane) tvLeft.lookup("TableHeaderRow");
Pane header2 = (Pane) tvRight.lookup("TableHeaderRow");
header1.prefHeightProperty().bind(header2.heightProperty());
//Now synchronise the scrollbars
ScrollBar scrollBarLeftTv;
ScrollBar scrollBarRightTv;
for ( Node node1: tvLeft.lookupAll(".scroll-bar") ) {
if ( node1 instanceof ScrollBar && ((ScrollBar) node1).getOrientation() == Orientation.VERTICAL ) {
scrollBarLeftTv = (ScrollBar) node1;
for ( Node node2: tvRight.lookupAll(".scroll-bar") ) {
if ( node2 instanceof ScrollBar && ((ScrollBar) node2).getOrientation() == Orientation.VERTICAL ) {
scrollBarRightTv = (ScrollBar) node2;
scrollBarRightTv.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
scrollBarRightTv.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
scrollBarRightTv.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
scrollBarRightTv.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
scrollBarRightTv.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
vScroll.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
vScroll.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
vScroll.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
vScroll.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
vScroll.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
}
}
}
}
for ( Node node: tvRight.lookupAll(".scroll-bar") ) {
if ( node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.HORIZONTAL ) {
ScrollBar scrollBar = (ScrollBar) node;
hScroll.valueProperty().bindBidirectional(scrollBar.valueProperty());
hScroll.maxProperty().bindBidirectional(scrollBar.maxProperty());
hScroll.minProperty().bindBidirectional(scrollBar.minProperty());
hScroll.unitIncrementProperty().bindBidirectional(scrollBar.unitIncrementProperty());
hScroll.visibleAmountProperty().bindBidirectional(scrollBar.visibleAmountProperty());
}
}
}
private void createTableColumns() {
TableColumn<DataModel,String> col1 = new TableColumn<>("field1");
TableColumn<DataModel,String> col2 = new TableColumn<>("field2");
TableColumn<DataModel,String> col3 = new TableColumn<>("field3");
TableColumn<DataModel,String> col4 = new TableColumn<>("field4");
TableColumn<DataModel,String> col5 = new TableColumn<>("field5");
TableColumn<DataModel,String> col6 = new TableColumn<>("field6");
TableColumn<DataModel,String> col7 = new TableColumn<>("field7");
TableColumn<DataModel,String> col8 = new TableColumn<>("field8");
TableColumn<DataModel,String> col9 = new TableColumn<>("field9");
TableColumn<DataModel,String> col10 = new TableColumn<>("field10");
TableColumn<DataModel,String> col11 = new TableColumn<>("field11");
TableColumn<DataModel,String> col12 = new TableColumn<>("field12");
TableColumn<DataModel,?> colsGroup = new TableColumn<>("group of cols");
col1.setCellValueFactory(cellData -> cellData.getValue().field1Property());
col1.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col2.setCellValueFactory(cellData -> cellData.getValue().field2Property());
col2.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col3.setCellValueFactory(cellData -> cellData.getValue().field3Property());
col3.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col4.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col4.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col5.setCellValueFactory(cellData -> cellData.getValue().field5Property());
col5.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col6.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col6.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col7.setCellValueFactory(cellData -> cellData.getValue().field5Property());
col7.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col8.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col8.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col9.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col9.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col10.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col10.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col11.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col11.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col12.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col12.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
colsGroup.getColumns().addAll(Arrays.asList(col4, col5, col6, col7, col8));
tvLeft.getColumns().addAll(Arrays.asList(col1, col2));
tvRight.getColumns().addAll(Arrays.asList(col3, colsGroup, col9, col10, col11, col12));
}
private void createTvWithDefaultStyle() {
TableColumn<DataModel,String> colDefaultStyle1 = new TableColumn<>("f1");
TableColumn<DataModel,String> colDefaultStyle2 = new TableColumn<>("f2");
TableColumn<DataModel,String> colDefaultStyle3 = new TableColumn<>("f3");
colDefaultStyle1.setCellValueFactory(cellData -> cellData.getValue().field1Property());
colDefaultStyle1.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
colDefaultStyle2.setCellValueFactory(cellData -> cellData.getValue().field2Property());
colDefaultStyle2.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
colDefaultStyle3.setCellValueFactory(cellData -> cellData.getValue().field3Property());
colDefaultStyle3.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
tvDefaultStyle.getColumns().addAll(Arrays.asList(colDefaultStyle1, colDefaultStyle2, colDefaultStyle3));
tvDefaultStyle.setItems(ol);
tvDefaultStyle.setPrefWidth(80D);
tvDefaultStyle.setMaxWidth(80D);
tvDefaultStyle.setMinWidth(80D);
}
private void loadDummyData() {
ol.add(new DataModel("1", "a", "1", "2", "z"));
ol.add(new DataModel("2", "a", "2", "3", "z"));
ol.add(new DataModel("3", "a", "3", "5", "z"));
ol.add(new DataModel("4", "a", "4", "9", "z"));
ol.add(new DataModel("5", "a", "5", "3", "z"));
ol.add(new DataModel("6", "a", "6", "4", "z"));
ol.add(new DataModel("7", "a", "7", "8", "z"));
ol.add(new DataModel("8", "a", "8", "7", "z"));
ol.add(new DataModel("9", "a", "9", "1", "z"));
ol.add(new DataModel("10", "a", "10", "6", "z"));
ol.add(new DataModel("11", "a", "11", "0", "z"));
ol.add(new DataModel("12", "a", "12", "3", "z"));
ol.add(new DataModel("13", "a", "13", "4", "z"));
ol.add(new DataModel("14", "a", "14", "2", "z"));
ol.add(new DataModel("15", "a", "15", "9", "z"));
ol.add(new DataModel("16", "a", "16", "4", "z"));
ol.add(new DataModel("17", "a", "17", "5", "z"));
ol.add(new DataModel("18", "a", "18", "0", "z"));
ol.add(new DataModel("19", "a", "19", "1", "z"));
ol.add(new DataModel("20", "a", "20", "6", "z"));
olDefaultStyle.add(new DataModel("A", "1", "20", "6", "z"));
olDefaultStyle.add(new DataModel("B", "2", "20", "6", "z"));
olDefaultStyle.add(new DataModel("C", "3", "20", "6", "z"));
}
private class DataModel {
private final StringProperty field1;
private final StringProperty field2;
private final StringProperty field3;
private final StringProperty field4;
private final StringProperty field5;
public DataModel(
String field1,
String field2,
String field3,
String field4,
String field5
) {
this.field1 = new SimpleStringProperty(field1);
this.field2 = new SimpleStringProperty(field2);
this.field3 = new SimpleStringProperty(field3);
this.field4 = new SimpleStringProperty(field4);
this.field5 = new SimpleStringProperty(field5);
}
public String getField1() {return field1.get().trim();}
public void setField1(String field1) {this.field1.set(field1);}
public StringProperty field1Property() {return field1;}
public String getField2() {return field2.get().trim();}
public void setField2(String field2) {this.field2.set(field2);}
public StringProperty field2Property() {return field2;}
public String getField3() {return field3.get().trim();}
public void setField3(String field3) {this.field3.set(field3);}
public StringProperty field3Property() {return field3;}
public String getField4() {return field4.get().trim();}
public void setField4(String field4) {this.field4.set(field4);}
public StringProperty field4Property() {return field4;}
public String getField5() {return field5.get().trim();}
public void setField5(String field5) {this.field5.set(field5);}
public StringProperty field5Property() {return field5;}
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("OpenJFX11 - Synchronise two TableViews");
stage.setWidth(600D);
stage.setHeight(400D);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
一个CSS新手问题。
我在两个相邻的 TableView
中显示大量数据,并双向绑定了它们的 ScrollBar
、FocusModel
和 SelectionModel
让他们保持同步。
我现在正在尝试让两个 TableView
看起来像一个,并且想要:
- 当 either
TableView
有焦点时 bothTableView
s 周围的默认蓝色边框。 -
TableView
周围的默认灰色边框,当 都没有焦点时。 TableView
相交处没有边界。
我该怎么做?
像这样的东西会很棒:
到目前为止,我已经能够通过这样做删除 "meet" 边框:
tvLeft.getStyleClass().add("my-table-view-left");
tvRight.getStyleClass().add("my-table-view-right");
与 CSS 像这样:
.my-table-view-left:focused {
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
}
.my-table-view-right:focused {
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
}
当其中一行被选中时,这也正确地设置了单个 TableView
的边框。
但是,当 either 具有焦点时,我不知道如何在 both TableView
s 周围设置边框.
这是一个 MVCE。很抱歉它的长度,但我需要包含同步代码才能有一个测试用例。
我在 Windows 7 上的 Netbeans 10.0 中使用 11.0.2 版本的 OpenJDK 和 OpenJFX,运行。
MyTableViewCSS.css
/************************************************************************************************************
Trying to set the borders of the synchronised tableviews
*/
.my-table-view-left:focused {
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
-fx-focus-color: red; /* for testing only */
}
.my-table-view-right:focused {
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
-fx-focus-color: red; /* for testing only */
}
/************************************************************************************************************
The following section hides the horizontal and vertical tableview scrollbars.
They are replaced by scrollbars manually added to the form.
Source:
*/
.my-table-view *.scroll-bar:horizontal *.increment-button,
.my-table-view *.scroll-bar:horizontal *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
}
.my-table-view *.scroll-bar:horizontal *.increment-arrow,
.my-table-view *.scroll-bar:horizontal *.decrement-arrow {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
.my-table-view *.scroll-bar:vertical *.increment-button,
.my-table-view *.scroll-bar:vertical *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
}
.my-table-view *.scroll-bar:vertical *.increment-arrow,
.my-table-view *.scroll-bar:vertical *.decrement-arrow {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
Test014.java
package test014;
import java.util.Arrays;
import java.util.function.Function;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;
public class Test014 extends Application {
private final ObservableList<DataModel> ol = FXCollections.observableArrayList();
private final TableView<DataModel> tvLeft = new TableView();
private final TableView<DataModel> tvRight = new TableView();
//Show a tableview that should continue to use the default Modena style. That way I'll know
//if I've messed anything up!
private final ObservableList<DataModel> olDefaultStyle = FXCollections.observableArrayList();
private final TableView<DataModel> tvDefaultStyle = new TableView();
private final ScrollBar vScroll = new ScrollBar();
private final ScrollBar hScroll = new ScrollBar();
private Parent createContent() {
loadDummyData();
createTableColumns();
tvLeft.setItems(ol);
tvRight.setItems(ol);
tvLeft.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tvRight.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
//Bi-directionally bind the selection and focus models of the two tables.
tvLeft.selectionModelProperty().bindBidirectional(tvRight.selectionModelProperty());
tvLeft.focusModelProperty().bindBidirectional(tvRight.focusModelProperty());
tvLeft.getSelectionModel().selectFirst();
vScroll.setOrientation(Orientation.VERTICAL);
hScroll.setOrientation(Orientation.HORIZONTAL);
tvLeft.getStyleClass().add("my-table-view");
tvRight.getStyleClass().add("my-table-view");
tvLeft.getStyleClass().add("my-table-view-left");
tvRight.getStyleClass().add("my-table-view-right");
Platform.runLater(() -> {
Scene scene = tvLeft.getScene();
String appStyleSheet = "MyTableViewCSS.css";
scene.getStylesheets().add(this.getClass().getResource(appStyleSheet).toString());
//Do the bindings necesary to synchronise the tableviews
synchroniseTheTableViews();
});
//Load the tableviews in a gridpane so I can control the width of the left-hand tableview
GridPane gp = new GridPane();
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setPrefWidth(130D);
cc1.setMaxWidth(130D);
cc1.setMinWidth(130D);
gp.getColumnConstraints().addAll(Arrays.asList(cc1));
gp.add(tvLeft, 0, 0);
gp.add(tvRight, 1, 0);
GridPane.setValignment(tvLeft, VPos.TOP);
GridPane.setVgrow(tvRight, Priority.ALWAYS);
//Put the gridpane in a borderpane so I can then add vScroll and hScroll
BorderPane content = new BorderPane();
gp.prefHeightProperty().bind(content.heightProperty());
gp.prefWidthProperty().bind(content.widthProperty());
content.setCenter(gp);
content.setRight(vScroll);
content.setBottom(hScroll);
//Add buttons to show and hide the tableview that should continue to have the default Modena style
Button btnShowTvDefaultStyle = new Button("Show TV with default style");
btnShowTvDefaultStyle.setOnAction(event -> {
content.setLeft(tvDefaultStyle);
});
Button btnHideTvDefaultStyle = new Button("Hide TV with default style");
btnHideTvDefaultStyle.setOnAction(event -> {
content.setLeft(null);
});
HBox hb = new HBox();
hb.setSpacing(20D);
hb.setPadding(new Insets(20D));
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(Arrays.asList(btnShowTvDefaultStyle, btnHideTvDefaultStyle));
content.setTop(hb);
return content;
}
private void synchroniseTheTableViews() {
//Bind the first table's header row height to that of the second
Pane header1 = (Pane) tvLeft.lookup("TableHeaderRow");
Pane header2 = (Pane) tvRight.lookup("TableHeaderRow");
header1.prefHeightProperty().bind(header2.heightProperty());
//Now synchronise the scrollbars
ScrollBar scrollBarLeftTv;
ScrollBar scrollBarRightTv;
for ( Node node1: tvLeft.lookupAll(".scroll-bar") ) {
if ( node1 instanceof ScrollBar && ((ScrollBar) node1).getOrientation() == Orientation.VERTICAL ) {
scrollBarLeftTv = (ScrollBar) node1;
for ( Node node2: tvRight.lookupAll(".scroll-bar") ) {
if ( node2 instanceof ScrollBar && ((ScrollBar) node2).getOrientation() == Orientation.VERTICAL ) {
scrollBarRightTv = (ScrollBar) node2;
scrollBarRightTv.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
scrollBarRightTv.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
scrollBarRightTv.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
scrollBarRightTv.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
scrollBarRightTv.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
vScroll.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
vScroll.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
vScroll.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
vScroll.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
vScroll.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
}
}
}
}
for ( Node node: tvRight.lookupAll(".scroll-bar") ) {
if ( node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.HORIZONTAL ) {
ScrollBar scrollBar = (ScrollBar) node;
hScroll.valueProperty().bindBidirectional(scrollBar.valueProperty());
hScroll.maxProperty().bindBidirectional(scrollBar.maxProperty());
hScroll.minProperty().bindBidirectional(scrollBar.minProperty());
hScroll.unitIncrementProperty().bindBidirectional(scrollBar.unitIncrementProperty());
hScroll.visibleAmountProperty().bindBidirectional(scrollBar.visibleAmountProperty());
}
}
}
private void createTableColumns() {
for ( int i=0; i<2; i++ ) {
TableColumn<DataModel, String> col = createColumn(i, DataModel::field1Property);
tvLeft.getColumns().add(col);
}
for ( int i=0; i<12; i++ ) {
TableColumn<DataModel, String> col = createColumn(i, DataModel::field2Property);
tvRight.getColumns().add(col);
}
for ( int i=0; i<3; i++ ) {
TableColumn<DataModel, String> col = createColumn(i, DataModel::field1Property);
tvDefaultStyle.getColumns().add(col);
}
tvDefaultStyle.setItems(olDefaultStyle);
tvDefaultStyle.setPrefWidth(100D);
tvDefaultStyle.setMaxWidth(100D);
tvDefaultStyle.setMinWidth(100D);
}
private TableColumn<DataModel, String> createColumn (int colNum, Function<DataModel, StringProperty> property) {
TableColumn<DataModel,String> col = new TableColumn<>("field" + colNum);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
return col;
}
private void loadDummyData() {
for ( int i=0; i<20; i++ ) {
ol.add(new DataModel(Integer.toString(i), "a"));
}
for ( int i=0; i<30; i++ ) {
olDefaultStyle.add(new DataModel(Integer.toString(i), "a"));
}
}
private class DataModel {
private final StringProperty field1;
private final StringProperty field2;
public DataModel(
String field1,
String field2
) {
this.field1 = new SimpleStringProperty(field1);
this.field2 = new SimpleStringProperty(field2);
}
public String getField1() {return field1.get().trim();}
public void setField1(String field1) {this.field1.set(field1);}
public StringProperty field1Property() {return field1;}
public String getField2() {return field2.get().trim();}
public void setField2(String field2) {this.field2.set(field2);}
public StringProperty field2Property() {return field2;}
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("OpenJFX11 - Synchronise two TableViews");
stage.setWidth(700D);
stage.setHeight(400D);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
根据用户 kleopatra 的建议,我使用 PseudoClass
es 弄清楚了如何做到这一点。
对于每个同步的 TableView
,我将 CSS 定义为:
在除
TableView
s "met". 以外的所有边框上设置摩德纳、蓝色、聚焦样式
在
TableView
s "met". 的边框上设置摩德纳灰色非聚焦样式
然后我为每个边框样式声明了一个 PseudoClass
。
最后,我在每个 TableView
的 focusedProperty()
中添加了一个 ChangeListener
来激活两个 TableView
的 PseudoClass
当其中一个得到焦点并在失去焦点时停用 PseudoClass
es。
最终结果如下所示:
正如 kleopatra 正确指出的那样,这使用了不受支持的功能,但它对我有用。我也没有尝试使用可编辑的 TableView
s,也没有尝试设置单元格(而不是行)选择。无论如何,我发布了代码片段和一个包含所有同步代码的 SSCE,以防它们帮助其他人。
CSS:
/* For the LEFT-hand tableview */
.my-table-view:left_focussed {
/* Apply the standard Modena focussed style to all bar the right-hand border (which is the
border that abuts the right-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
-fx-background-radius: 2, 0, 0;
/* Set the abutting right-hand border to grey. */
-fx-border-width: 0 0.7 0 0;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
/* For the RIGHT-hand tableview */
.my-table-view:right_focussed {
/* Apply the standard Modena focussed style to all bar the left-hand border (which is the
border that abuts the left-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
-fx-background-radius: 2, 0, 0;
/* Set the abutting left-hand border to grey. */
-fx-border-width: 0 0 0 0.7;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
PseudoClass
声明:
private final PseudoClass leftFocussed = PseudoClass.getPseudoClass("left_focussed");
private final PseudoClass rightFocussed = PseudoClass.getPseudoClass("right_focussed");
ChangeListener
s:
tvLeft.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
tvRight.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
和togglePseudoClassStates()
方法:
private void togglePseudoClassStates(boolean requiredState) {
//Set the tableview border style
tvLeft.pseudoClassStateChanged(leftFocussed, requiredState);
tvRight.pseudoClassStateChanged(rightFocussed, requiredState);
}
这是 SSCE。我在 Windows 7 上的 Netbeans 10.0 中使用 11.0.2 版本的 OpenJDK 和 OpenJFX 运行。
MyTableViewCSS.css
/************************************************************************************************************
This section defines the border style to be applied to the synchronised tableviews.
a) For the LEFT-hand tableview that represents the "frozen" columns:
i) Set all bar the RIGHT border to the default Modena focussed style.
ii) Set the RIGHT border to be the default Modena unfocussed style.
b) For the RIGHT-hand tableview that represents the "scrollable" columns:
i) Set all bar the LEFT border to the default Modena focussed style.
ii) Set the LEFT border to be the default Modena unfocussed style.
*/
/* For the LEFT-hand tableview */
.my-table-view:left_focussed {
/* Apply the standard Modena focussed style to all bar the right-hand border (which is the
border that abuts the right-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 0 -1.4 -1.4, -0.3 0 -0.3 -0.3, 1 0 1 1;
-fx-background-radius: 2, 0, 0;
/* Set the abutting right-hand border to grey. */
-fx-border-width: 0 0.7 0 0;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
/* For the RIGHT-hand tableview */
.my-table-view:right_focussed {
/* Apply the standard Modena focussed style to all bar the left-hand border (which is the
border that abuts the left-hand tableview) */
-fx-background-color: -fx-faint-focus-color, -fx-focus-color, -fx-control-inner-background;
-fx-background-insets: -1.4 -1.4 -1.4 0, -0.3 -0.3 -0.3 0, 1 1 1 0;
-fx-background-radius: 2, 0, 0;
/* Set the abutting left-hand border to grey. */
-fx-border-width: 0 0 0 0.7;
-fx-border-color: -fx-selection-bar-non-focused;
-fx-border-radius: 0;
}
/************************************************************************************************************
This section defines the style of the selection bars for selected rows in the synchronised tableviews.
When a row in one tableview is selected or gets focus, the bi-directional bindings select and focus the
corresponding row in other tableview. To make it seem like the two tableviews are one, the default Modena
focussed style has to be set on the selection bars for both:
a) the actual selected/focussed row; and
b) the row in the other tableview that's automatically selected/focussed by the bi-directional bindings.
*/
.my-table-view:selectionbar_focussed > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected,
.my-table-view:selectionbar_focussed > .virtual-flow > .clipped-container > .sheet > .table-row-cell .table-cell:selected
{
-fx-background: -fx-selection-bar;
-fx-table-cell-border-color: derive(-fx-selection-bar, 20%);
}
/************************************************************************************************************
This section section hides the default horizontal and vertical scrollbars in the synchronised tableviews.
*/
.my-table-view:hidden_scrollbars *.scroll-bar *.increment-arrow,
.my-table-view:hidden_scrollbars *.scroll-bar *.decrement-arrow,
.my-table-view:hidden_scrollbars *.scroll-bar *.increment-button,
.my-table-view:hidden_scrollbars *.scroll-bar *.decrement-button {
-fx-background-color: null;
-fx-background-radius: 0;
-fx-background-insets: 0;
-fx-padding: 0;
-fx-shape: null;
}
Test014.java
/*
An example of using two, synchronised TableViews to display a wide set of data where:
a) The left-hand TableView contains the dataset's fixed (or "frozen") columns.
b) The right-hand TableView contains the dataset's scrollable columns.
The TableViews are synchronised by bi-directionally binding their ScrollBars and selection and focus models.
The in-built ScrollBars on both TableViews are hidden with CSS. Simultaneous scrolling of both TableViews is
achieved by manually adding vertical and horizontal ScrollBars and bi-directionally binding them to the
now-hidden TableView ScrollBars.
To then make all the components look like a single TableView:
a) The two TableViews are added to a GridPane, which in turn is added as the centre node of a BorderPane.
b) The manually created vertical ScrollBar is added as the right node of the BorderPane.
c) The manually created horizontal ScrollBar is added as the bottom node of the BorderPane.
CSS is then used to:
a) Set the borders around both TableViews (except for the borders that abut each other) to the default Modena
blue style when either TableView has focus.
b) Set the borders around both TableViews (except for the abutting borders) to the default Modena grey style
when both TableViews lose focus.
c) Set the abutting borders permanently to grey.
d) Set the selection bars on both TableViews to the default Modena blue style when a row in either TableView
is selected or receives focus.
e) Set the selection bars on both TableViews to the default Modena grey style when both TableViews lose focus.
CAVEAT: This has not been tested with editable TableViews nor with cell (rather than row) selection enabled.
*/
package test014;
import java.util.Arrays;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.converter.DefaultStringConverter;
public class Test014 extends Application {
private final ObservableList<DataModel> ol = FXCollections.observableArrayList();
private final TableView<DataModel> tvLeft = new TableView();
private final TableView<DataModel> tvRight = new TableView();
//Show a tableview that should continue to use the default Modena style. That way I'll know
//if I've messed anything up!
private final ObservableList<DataModel> olDefaultStyle = FXCollections.observableArrayList();
private final TableView<DataModel> tvDefaultStyle = new TableView();
//Declare scrollbars that will be used to scroll the synchronised tableviews.
private final ScrollBar vScroll = new ScrollBar();
private final ScrollBar hScroll = new ScrollBar();
//Declare the pseudoclasses
private final PseudoClass leftFocussed = PseudoClass.getPseudoClass("left_focussed");
private final PseudoClass rightFocussed = PseudoClass.getPseudoClass("right_focussed");
private final PseudoClass selectionbarFocussed = PseudoClass.getPseudoClass("selectionbar_focussed");
private final PseudoClass hiddenScrollBars = PseudoClass.getPseudoClass("hidden_scrollbars");
private Parent createContent() {
loadDummyData();
createTableColumns();
createTvWithDefaultStyle();
tvLeft.setItems(ol);
tvRight.setItems(ol);
tvLeft.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tvRight.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
//Bi-directionally bind the selection and focus models of the two tables.
tvLeft.selectionModelProperty().bindBidirectional(tvRight.selectionModelProperty());
tvLeft.focusModelProperty().bindBidirectional(tvRight.focusModelProperty());
tvLeft.getSelectionModel().selectFirst();
//Set the orientation of the scrollbars
vScroll.setOrientation(Orientation.VERTICAL);
hScroll.setOrientation(Orientation.HORIZONTAL);
//Add the "my-table-view" styleclass to both tableviews. This allows me to access the
//custom pseudoclasses needed to style the tableviews.
tvLeft.getStyleClass().add("my-table-view");
tvRight.getStyleClass().add("my-table-view");
//Activate the pseudoclass to hide the default scrollbars of the synchronised tableviews
tvLeft.pseudoClassStateChanged(hiddenScrollBars, true);
tvRight.pseudoClassStateChanged(hiddenScrollBars, true);
//Add listeners to detect and act on focus changes
addListeners();
Platform.runLater(() -> {
Scene scene = tvLeft.getScene();
String appStyleSheet = "MyTableViewCSS.css";
scene.getStylesheets().add(this.getClass().getResource(appStyleSheet).toString());
//Do the bindings necesary to synchronise the tableviews
synchroniseTheTableViews();
});
//Load the tableviews in a gridpane so I can control the width of the left-hand tableview
GridPane gp = new GridPane();
ColumnConstraints cc1 = new ColumnConstraints();
cc1.setPrefWidth(130D);
cc1.setMaxWidth(130D);
cc1.setMinWidth(130D);
gp.getColumnConstraints().addAll(Arrays.asList(cc1));
gp.add(tvLeft, 0, 0);
gp.add(tvRight, 1, 0);
GridPane.setValignment(tvLeft, VPos.TOP);
GridPane.setVgrow(tvRight, Priority.ALWAYS);
//Put the gridpane in a borderpane so I can then add vScroll and hScroll
BorderPane content = new BorderPane();
gp.prefHeightProperty().bind(content.heightProperty());
gp.prefWidthProperty().bind(content.widthProperty());
content.setCenter(gp);
content.setRight(vScroll);
content.setBottom(hScroll);
//Add buttons to show and hide the tableview that should continue to have the default Modena style
Button btnShowTvDefaultStyle = new Button("Show TV with default style");
btnShowTvDefaultStyle.setOnAction(event -> {
content.setLeft(tvDefaultStyle);
tvDefaultStyle.requestFocus();
});
Button btnHideTvDefaultStyle = new Button("Hide TV with default style");
btnHideTvDefaultStyle.setOnAction(event -> {
content.setLeft(null);
});
Button btnRequestFocus = new Button("request focus");
btnRequestFocus.setOnAction(event -> {
tvRight.requestFocus();
});
HBox hb = new HBox();
hb.setSpacing(20D);
hb.setPadding(new Insets(20D));
hb.setAlignment(Pos.CENTER);
hb.getChildren().addAll(Arrays.asList(btnShowTvDefaultStyle, btnHideTvDefaultStyle, btnRequestFocus));
content.setTop(hb);
return content;
}
private void addListeners() {
//Add a focus change listener to each of the tableviews to add or remove the border and
//selection bar styles when focus is gained or lost.
tvLeft.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if ( isNowFocused ) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
tvRight.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if ( isNowFocused ) {
togglePseudoClassStates(true);
} else {
togglePseudoClassStates(false);
}
});
//Add a value change listener to the manually added scrollbars to request focus on one of the
//synchronised tableviews.
hScroll.valueProperty().addListener((obs, oldValue, newValue) -> {
tvLeft.requestFocus();
});
vScroll.valueProperty().addListener((obs, oldValue, newValue) -> {
tvLeft.requestFocus();
});
//Likewise, add a handler to detect any mouse action on the scrollbars and request focus
//on one of the tableviews.
hScroll.setOnMouseReleased(event -> {
tvLeft.requestFocus();
});
vScroll.setOnMouseReleased(event -> {
tvLeft.requestFocus();
});
}
private void togglePseudoClassStates(boolean requiredState) {
//Set the tableview border style
tvLeft.pseudoClassStateChanged(leftFocussed, requiredState);
tvRight.pseudoClassStateChanged(rightFocussed, requiredState);
//Set the selection bar style
tvLeft.pseudoClassStateChanged(selectionbarFocussed, requiredState);
tvRight.pseudoClassStateChanged(selectionbarFocussed, requiredState);
}
private void synchroniseTheTableViews() {
//Bind the first table's header row height to that of the second
Pane header1 = (Pane) tvLeft.lookup("TableHeaderRow");
Pane header2 = (Pane) tvRight.lookup("TableHeaderRow");
header1.prefHeightProperty().bind(header2.heightProperty());
//Now synchronise the scrollbars
ScrollBar scrollBarLeftTv;
ScrollBar scrollBarRightTv;
for ( Node node1: tvLeft.lookupAll(".scroll-bar") ) {
if ( node1 instanceof ScrollBar && ((ScrollBar) node1).getOrientation() == Orientation.VERTICAL ) {
scrollBarLeftTv = (ScrollBar) node1;
for ( Node node2: tvRight.lookupAll(".scroll-bar") ) {
if ( node2 instanceof ScrollBar && ((ScrollBar) node2).getOrientation() == Orientation.VERTICAL ) {
scrollBarRightTv = (ScrollBar) node2;
scrollBarRightTv.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
scrollBarRightTv.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
scrollBarRightTv.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
scrollBarRightTv.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
scrollBarRightTv.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
vScroll.valueProperty().bindBidirectional(scrollBarLeftTv.valueProperty());
vScroll.maxProperty().bindBidirectional(scrollBarLeftTv.maxProperty());
vScroll.minProperty().bindBidirectional(scrollBarLeftTv.minProperty());
vScroll.unitIncrementProperty().bindBidirectional(scrollBarLeftTv.unitIncrementProperty());
vScroll.visibleAmountProperty().bindBidirectional(scrollBarLeftTv.visibleAmountProperty());
}
}
}
}
for ( Node node: tvRight.lookupAll(".scroll-bar") ) {
if ( node instanceof ScrollBar && ((ScrollBar) node).getOrientation() == Orientation.HORIZONTAL ) {
ScrollBar scrollBar = (ScrollBar) node;
hScroll.valueProperty().bindBidirectional(scrollBar.valueProperty());
hScroll.maxProperty().bindBidirectional(scrollBar.maxProperty());
hScroll.minProperty().bindBidirectional(scrollBar.minProperty());
hScroll.unitIncrementProperty().bindBidirectional(scrollBar.unitIncrementProperty());
hScroll.visibleAmountProperty().bindBidirectional(scrollBar.visibleAmountProperty());
}
}
}
private void createTableColumns() {
TableColumn<DataModel,String> col1 = new TableColumn<>("field1");
TableColumn<DataModel,String> col2 = new TableColumn<>("field2");
TableColumn<DataModel,String> col3 = new TableColumn<>("field3");
TableColumn<DataModel,String> col4 = new TableColumn<>("field4");
TableColumn<DataModel,String> col5 = new TableColumn<>("field5");
TableColumn<DataModel,String> col6 = new TableColumn<>("field6");
TableColumn<DataModel,String> col7 = new TableColumn<>("field7");
TableColumn<DataModel,String> col8 = new TableColumn<>("field8");
TableColumn<DataModel,String> col9 = new TableColumn<>("field9");
TableColumn<DataModel,String> col10 = new TableColumn<>("field10");
TableColumn<DataModel,String> col11 = new TableColumn<>("field11");
TableColumn<DataModel,String> col12 = new TableColumn<>("field12");
TableColumn<DataModel,?> colsGroup = new TableColumn<>("group of cols");
col1.setCellValueFactory(cellData -> cellData.getValue().field1Property());
col1.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col2.setCellValueFactory(cellData -> cellData.getValue().field2Property());
col2.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col3.setCellValueFactory(cellData -> cellData.getValue().field3Property());
col3.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col4.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col4.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col5.setCellValueFactory(cellData -> cellData.getValue().field5Property());
col5.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col6.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col6.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col7.setCellValueFactory(cellData -> cellData.getValue().field5Property());
col7.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col8.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col8.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col9.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col9.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col10.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col10.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col11.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col11.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
col12.setCellValueFactory(cellData -> cellData.getValue().field4Property());
col12.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
colsGroup.getColumns().addAll(Arrays.asList(col4, col5, col6, col7, col8));
tvLeft.getColumns().addAll(Arrays.asList(col1, col2));
tvRight.getColumns().addAll(Arrays.asList(col3, colsGroup, col9, col10, col11, col12));
}
private void createTvWithDefaultStyle() {
TableColumn<DataModel,String> colDefaultStyle1 = new TableColumn<>("f1");
TableColumn<DataModel,String> colDefaultStyle2 = new TableColumn<>("f2");
TableColumn<DataModel,String> colDefaultStyle3 = new TableColumn<>("f3");
colDefaultStyle1.setCellValueFactory(cellData -> cellData.getValue().field1Property());
colDefaultStyle1.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
colDefaultStyle2.setCellValueFactory(cellData -> cellData.getValue().field2Property());
colDefaultStyle2.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
colDefaultStyle3.setCellValueFactory(cellData -> cellData.getValue().field3Property());
colDefaultStyle3.setCellFactory(TextFieldTableCell.<DataModel, String>forTableColumn(new DefaultStringConverter()));
tvDefaultStyle.getColumns().addAll(Arrays.asList(colDefaultStyle1, colDefaultStyle2, colDefaultStyle3));
tvDefaultStyle.setItems(ol);
tvDefaultStyle.setPrefWidth(80D);
tvDefaultStyle.setMaxWidth(80D);
tvDefaultStyle.setMinWidth(80D);
}
private void loadDummyData() {
ol.add(new DataModel("1", "a", "1", "2", "z"));
ol.add(new DataModel("2", "a", "2", "3", "z"));
ol.add(new DataModel("3", "a", "3", "5", "z"));
ol.add(new DataModel("4", "a", "4", "9", "z"));
ol.add(new DataModel("5", "a", "5", "3", "z"));
ol.add(new DataModel("6", "a", "6", "4", "z"));
ol.add(new DataModel("7", "a", "7", "8", "z"));
ol.add(new DataModel("8", "a", "8", "7", "z"));
ol.add(new DataModel("9", "a", "9", "1", "z"));
ol.add(new DataModel("10", "a", "10", "6", "z"));
ol.add(new DataModel("11", "a", "11", "0", "z"));
ol.add(new DataModel("12", "a", "12", "3", "z"));
ol.add(new DataModel("13", "a", "13", "4", "z"));
ol.add(new DataModel("14", "a", "14", "2", "z"));
ol.add(new DataModel("15", "a", "15", "9", "z"));
ol.add(new DataModel("16", "a", "16", "4", "z"));
ol.add(new DataModel("17", "a", "17", "5", "z"));
ol.add(new DataModel("18", "a", "18", "0", "z"));
ol.add(new DataModel("19", "a", "19", "1", "z"));
ol.add(new DataModel("20", "a", "20", "6", "z"));
olDefaultStyle.add(new DataModel("A", "1", "20", "6", "z"));
olDefaultStyle.add(new DataModel("B", "2", "20", "6", "z"));
olDefaultStyle.add(new DataModel("C", "3", "20", "6", "z"));
}
private class DataModel {
private final StringProperty field1;
private final StringProperty field2;
private final StringProperty field3;
private final StringProperty field4;
private final StringProperty field5;
public DataModel(
String field1,
String field2,
String field3,
String field4,
String field5
) {
this.field1 = new SimpleStringProperty(field1);
this.field2 = new SimpleStringProperty(field2);
this.field3 = new SimpleStringProperty(field3);
this.field4 = new SimpleStringProperty(field4);
this.field5 = new SimpleStringProperty(field5);
}
public String getField1() {return field1.get().trim();}
public void setField1(String field1) {this.field1.set(field1);}
public StringProperty field1Property() {return field1;}
public String getField2() {return field2.get().trim();}
public void setField2(String field2) {this.field2.set(field2);}
public StringProperty field2Property() {return field2;}
public String getField3() {return field3.get().trim();}
public void setField3(String field3) {this.field3.set(field3);}
public StringProperty field3Property() {return field3;}
public String getField4() {return field4.get().trim();}
public void setField4(String field4) {this.field4.set(field4);}
public StringProperty field4Property() {return field4;}
public String getField5() {return field5.get().trim();}
public void setField5(String field5) {this.field5.set(field5);}
public StringProperty field5Property() {return field5;}
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle("OpenJFX11 - Synchronise two TableViews");
stage.setWidth(600D);
stage.setHeight(400D);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}