一个 CSS 新手问题。
我在两个相邻TableView
的 s 中显示大量数据,并双向绑定它们ScrollBar
的 s、FocusModel
s 和SelectionModel
s 以使它们保持同步。
我现在正试图让这两个TableView
s 看起来像一个,并希望拥有:
- 当两个s有焦点时,两个 s周围的默认蓝色边框。
TableView
TableView
- 当两者都没有焦点时,两个
TableView
s周围的默认灰色边框。 TableView
s 相交的地方没有边界。
我该怎么做呢?
这样的事情会很棒:
到目前为止,我已经能够通过这样做来删除“相遇”边界:
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;
}
This also correctly sets the border on a single TableView
when one of its rows is selected.
但是,我无法弄清楚当任何一个都有焦点时如何在两个 TableView
s周围获得边框。
这是一个MVCE。抱歉它的长度,但我需要包含同步代码才能有一个测试用例。
我正在使用 11.0.2 版本的 OpenJDK 和 OpenJFX,在 Windows 7 上的 Netbeans 10.0 中运行。
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: https://stackoverflow.com/questions/26713162/javafx-disable-horizontal-scrollbar-of-tableview
*/
.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);
}
}