如何为 VirtualFlow 滚动条预留 space?
How to reserve space for the VirtualFlow scrollbars?
VirtualFlow 的当前实现仅在视图矩形小于控件大小时使滚动条可见。我所说的控件是指 ListView、TreeView 和任何标准的虚拟化控件。问题在于垂直滚动条的出现会导致重新计算控件宽度,即将单元格内容稍微向左移动。这是明显明显且非常不舒服的运动。
我需要预先为垂直滚动条保留一些space,但是none的控件提供API来操纵VirtualFlow滚动条的行为,这是非常不幸的API设计。更不用说大多数实现都将滚动条放在组件的顶部,因此只覆盖了它的一小部分。
问题是,“实现此目标的最佳方法是什么?”。填充无济于事,而且 JavaFX 不支持边距。我可以将控件(例如 ListView)放在 ScrollPane 中,但我敢打赌 VirtualFlow 在这种情况下不会继续重用单元格,所以这不是解决方案。
示例:
展开和折叠 node2
,它移动 lbRight
内容。
public class Launcher extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
TreeItem<UUID> root = new TreeItem<>(UUID.randomUUID());
TreeView<UUID> tree = new TreeView<>(root);
tree.setCellFactory(list -> new CustomCell());
TreeItem<UUID> node0 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node1 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node2 = new TreeItem<>(UUID.randomUUID());
IntStream.range(0, 100)
.mapToObj(index -> new TreeItem<>(UUID.randomUUID()))
.forEach(node2.getChildren()::add);
root.getChildren().setAll(node0, node1, node2);
root.setExpanded(true);
node2.setExpanded(true);
BorderPane pane = new BorderPane();
pane.setCenter(tree);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(t -> Platform.exit());
primaryStage.show();
}
static class CustomCell extends TreeCell<UUID> {
public HBox hBox;
public Label lbLeft;
public Label lbRight;
public CustomCell() {
hBox = new HBox();
lbLeft = new Label();
lbRight = new Label();
lbRight.setStyle("-fx-padding: 0 20 0 0");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().setAll(lbLeft, spacer, lbRight);
}
@Override
protected void updateItem(UUID uuid, boolean empty) {
super.updateItem(uuid, empty);
if (empty) {
setGraphic(null);
return;
}
String s = uuid.toString();
lbLeft.setText(s.substring(0, 6));
lbRight.setText(s.substring(6, 12));
setGraphic(hBox);
}
}
}
好的,这是基于@kleopatra 评论和 OpenJFX 代码探索的总结。不会有解决问题的代码,但还是会腾出一些时间给别人。
如前所述,VirtualFlow
负责管理虚拟化控件视口大小。所有的魔法都发生在 layoutChildren(). First it computes scrollbars visibility and then recalculates size of all children based on that knowledge. Here is the code which causes the problem.
由于所有实现细节都是私有的或包私有的,您不能只扩展 VirtualFlow
和覆盖一两个方法,您必须复制粘贴并编辑整个 class(以删除一行,是的)。鉴于此,更改内部组件布局可能是更好的选择。
有时候,我喜欢那些没有封装的语言。
更新:
我已经解决了这个问题。如果不调整 JavaFX 内部结构,就无法为垂直滚动条保留 space,但我们可以限制单元格宽度,因此它总是小于 TreeView(或列表视图)宽度。这是一个简单的例子。
public class Launcher extends Application {
public static final double SCENE_WIDTH = 500;
@Override
public void start(Stage primaryStage) {
TreeItem<UUID> root = new TreeItem<>(UUID.randomUUID());
TreeView<UUID> tree = new TreeView<>(root);
tree.setCellFactory(list -> new CustomCell(SCENE_WIDTH));
TreeItem<UUID> node0 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node1 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node2 = new TreeItem<>(UUID.randomUUID());
IntStream.range(0, 100)
.mapToObj(index -> new TreeItem<>(UUID.randomUUID()))
.forEach(node2.getChildren()::add);
root.getChildren().setAll(node0, node1, node2);
root.setExpanded(true);
node2.setExpanded(true);
BorderPane pane = new BorderPane();
pane.setCenter(tree);
Scene scene = new Scene(pane, SCENE_WIDTH, 600);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(t -> Platform.exit());
primaryStage.show();
}
static class CustomCell extends TreeCell<UUID> {
public static final double RIGHT_PADDING = 40;
/*
this value depends on tree disclosure node width
in my case it's enforced via CSS, so I always know exact
value of this padding
*/
public static final double INDENT_PADDING = 14;
public HBox hBox;
public Label lbLeft;
public Label lbRight;
public double maxWidth;
public CustomCell(double maxWidth) {
this.maxWidth = maxWidth;
hBox = new HBox();
lbLeft = new Label();
lbRight = new Label();
lbRight.setPadding(new Insets(0, RIGHT_PADDING, 0, 0));
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().setAll(lbLeft, spacer, lbRight);
}
@Override
protected void updateItem(UUID uuid, boolean empty) {
super.updateItem(uuid, empty);
if (empty) {
setGraphic(null);
return;
}
String s = uuid.toString();
lbLeft.setText(s.substring(0, 6));
lbRight.setText(s.substring(6, 12));
setGraphic(hBox);
}
@Override
public void resize(double width, double height) {
// enforce item width
double maxCellWidth = getTreeView().getWidth() - RIGHT_PADDING;
double startLevel = getTreeView().isShowRoot() ? 0 : 1;
double itemLevel = getTreeView().getTreeItemLevel(getTreeItem());
if (itemLevel > startLevel) {
maxCellWidth = maxCellWidth - ((itemLevel - startLevel) * INDENT_PADDING);
}
hBox.setPrefWidth(maxCellWidth);
hBox.setMaxWidth(maxCellWidth);
super.resize(width, height);
}
}
}
它远非完美,但它确实有效。
对
的反应
you can't just extend the VirtualFlow and override a method
如果方法被 package/-private 访问深深隐藏,那当然是正确的(但即便如此:javafx 是开源的,checkout-edit-compile-distribute 也是一个选项:)。在这种情况下,我们可能会按照下面概述的方式覆盖 public api(未经正式测试!)。
VirtualFlow 是单元格和滚动条的“布局”:特别是,它必须处理 sizing/locating 所有内容的处理 w/out 滚动条可见。有关于如何完成的选项:
- 调整单元格宽度以始终填充视口,increasing/decreasing 当垂直滚动条为 hidden/visible
- 保持单元格宽度不变,这样无论滚动条是否可见,总有 space 空间
- 保持单元格宽度不变,这样就不会 space 离开滚动条,将其放置在单元格顶部
- 其他??
默认 VirtualFlow 实现第一个,没有切换到任何其他选项的选项。 (可能是 RFE 的候选人,请随时报告 :)。
深入研究代码表明,单元格的最终大小是通过在布局代码末尾附近调用 cell.resize(..)
(如前所述和利用 )来完成的。覆盖自定义单元格的调整大小是完全有效的并且是一个不错的选择..但不是唯一的,IMO。另一种选择是
- 扩展 VirtualFlow 并覆盖 layoutChildren 以根据需要调整单元格宽度
- 扩展 TreeViewSkin 以使用自定义流程
示例代码(需要 fx12++):
public static class XVirtualFlow<I extends IndexedCell> extends VirtualFlow<I> {
@Override
protected void layoutChildren() {
super.layoutChildren();
fitCellWidths();
}
/**
* Resizes cell width to accomodate for invisible vbar.
*/
private void fitCellWidths() {
if (!isVertical() || getVbar().isVisible()) return;
double width = getWidth() - getVbar().getWidth();
for (I cell : getCells()) {
cell.resize(width, cell.getHeight());
}
}
}
public static class XTreeViewSkin<T> extends TreeViewSkin<T>{
public XTreeViewSkin(TreeView<T> control) {
super(control);
}
@Override
protected VirtualFlow<TreeCell<T>> createVirtualFlow() {
return new XVirtualFlow<>();
}
}
即时使用:
TreeView<UUID> tree = new TreeView<>(root) {
@Override
protected Skin<?> createDefaultSkin() {
return new XTreeViewSkin<>(this);
}
};
VirtualFlow 的当前实现仅在视图矩形小于控件大小时使滚动条可见。我所说的控件是指 ListView、TreeView 和任何标准的虚拟化控件。问题在于垂直滚动条的出现会导致重新计算控件宽度,即将单元格内容稍微向左移动。这是明显明显且非常不舒服的运动。
我需要预先为垂直滚动条保留一些space,但是none的控件提供API来操纵VirtualFlow滚动条的行为,这是非常不幸的API设计。更不用说大多数实现都将滚动条放在组件的顶部,因此只覆盖了它的一小部分。
问题是,“实现此目标的最佳方法是什么?”。填充无济于事,而且 JavaFX 不支持边距。我可以将控件(例如 ListView)放在 ScrollPane 中,但我敢打赌 VirtualFlow 在这种情况下不会继续重用单元格,所以这不是解决方案。
示例:
展开和折叠 node2
,它移动 lbRight
内容。
public class Launcher extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
TreeItem<UUID> root = new TreeItem<>(UUID.randomUUID());
TreeView<UUID> tree = new TreeView<>(root);
tree.setCellFactory(list -> new CustomCell());
TreeItem<UUID> node0 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node1 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node2 = new TreeItem<>(UUID.randomUUID());
IntStream.range(0, 100)
.mapToObj(index -> new TreeItem<>(UUID.randomUUID()))
.forEach(node2.getChildren()::add);
root.getChildren().setAll(node0, node1, node2);
root.setExpanded(true);
node2.setExpanded(true);
BorderPane pane = new BorderPane();
pane.setCenter(tree);
Scene scene = new Scene(pane, 600, 600);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(t -> Platform.exit());
primaryStage.show();
}
static class CustomCell extends TreeCell<UUID> {
public HBox hBox;
public Label lbLeft;
public Label lbRight;
public CustomCell() {
hBox = new HBox();
lbLeft = new Label();
lbRight = new Label();
lbRight.setStyle("-fx-padding: 0 20 0 0");
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().setAll(lbLeft, spacer, lbRight);
}
@Override
protected void updateItem(UUID uuid, boolean empty) {
super.updateItem(uuid, empty);
if (empty) {
setGraphic(null);
return;
}
String s = uuid.toString();
lbLeft.setText(s.substring(0, 6));
lbRight.setText(s.substring(6, 12));
setGraphic(hBox);
}
}
}
好的,这是基于@kleopatra 评论和 OpenJFX 代码探索的总结。不会有解决问题的代码,但还是会腾出一些时间给别人。
如前所述,VirtualFlow
负责管理虚拟化控件视口大小。所有的魔法都发生在 layoutChildren(). First it computes scrollbars visibility and then recalculates size of all children based on that knowledge. Here is the code which causes the problem.
由于所有实现细节都是私有的或包私有的,您不能只扩展 VirtualFlow
和覆盖一两个方法,您必须复制粘贴并编辑整个 class(以删除一行,是的)。鉴于此,更改内部组件布局可能是更好的选择。
有时候,我喜欢那些没有封装的语言。
更新:
我已经解决了这个问题。如果不调整 JavaFX 内部结构,就无法为垂直滚动条保留 space,但我们可以限制单元格宽度,因此它总是小于 TreeView(或列表视图)宽度。这是一个简单的例子。
public class Launcher extends Application {
public static final double SCENE_WIDTH = 500;
@Override
public void start(Stage primaryStage) {
TreeItem<UUID> root = new TreeItem<>(UUID.randomUUID());
TreeView<UUID> tree = new TreeView<>(root);
tree.setCellFactory(list -> new CustomCell(SCENE_WIDTH));
TreeItem<UUID> node0 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node1 = new TreeItem<>(UUID.randomUUID());
TreeItem<UUID> node2 = new TreeItem<>(UUID.randomUUID());
IntStream.range(0, 100)
.mapToObj(index -> new TreeItem<>(UUID.randomUUID()))
.forEach(node2.getChildren()::add);
root.getChildren().setAll(node0, node1, node2);
root.setExpanded(true);
node2.setExpanded(true);
BorderPane pane = new BorderPane();
pane.setCenter(tree);
Scene scene = new Scene(pane, SCENE_WIDTH, 600);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.setOnCloseRequest(t -> Platform.exit());
primaryStage.show();
}
static class CustomCell extends TreeCell<UUID> {
public static final double RIGHT_PADDING = 40;
/*
this value depends on tree disclosure node width
in my case it's enforced via CSS, so I always know exact
value of this padding
*/
public static final double INDENT_PADDING = 14;
public HBox hBox;
public Label lbLeft;
public Label lbRight;
public double maxWidth;
public CustomCell(double maxWidth) {
this.maxWidth = maxWidth;
hBox = new HBox();
lbLeft = new Label();
lbRight = new Label();
lbRight.setPadding(new Insets(0, RIGHT_PADDING, 0, 0));
Region spacer = new Region();
HBox.setHgrow(spacer, Priority.ALWAYS);
hBox.getChildren().setAll(lbLeft, spacer, lbRight);
}
@Override
protected void updateItem(UUID uuid, boolean empty) {
super.updateItem(uuid, empty);
if (empty) {
setGraphic(null);
return;
}
String s = uuid.toString();
lbLeft.setText(s.substring(0, 6));
lbRight.setText(s.substring(6, 12));
setGraphic(hBox);
}
@Override
public void resize(double width, double height) {
// enforce item width
double maxCellWidth = getTreeView().getWidth() - RIGHT_PADDING;
double startLevel = getTreeView().isShowRoot() ? 0 : 1;
double itemLevel = getTreeView().getTreeItemLevel(getTreeItem());
if (itemLevel > startLevel) {
maxCellWidth = maxCellWidth - ((itemLevel - startLevel) * INDENT_PADDING);
}
hBox.setPrefWidth(maxCellWidth);
hBox.setMaxWidth(maxCellWidth);
super.resize(width, height);
}
}
}
它远非完美,但它确实有效。
对
的反应you can't just extend the VirtualFlow and override a method
如果方法被 package/-private 访问深深隐藏,那当然是正确的(但即便如此:javafx 是开源的,checkout-edit-compile-distribute 也是一个选项:)。在这种情况下,我们可能会按照下面概述的方式覆盖 public api(未经正式测试!)。
VirtualFlow 是单元格和滚动条的“布局”:特别是,它必须处理 sizing/locating 所有内容的处理 w/out 滚动条可见。有关于如何完成的选项:
- 调整单元格宽度以始终填充视口,increasing/decreasing 当垂直滚动条为 hidden/visible
- 保持单元格宽度不变,这样无论滚动条是否可见,总有 space 空间
- 保持单元格宽度不变,这样就不会 space 离开滚动条,将其放置在单元格顶部
- 其他??
默认 VirtualFlow 实现第一个,没有切换到任何其他选项的选项。 (可能是 RFE 的候选人,请随时报告 :)。
深入研究代码表明,单元格的最终大小是通过在布局代码末尾附近调用 cell.resize(..)
(如前所述和利用
- 扩展 VirtualFlow 并覆盖 layoutChildren 以根据需要调整单元格宽度
- 扩展 TreeViewSkin 以使用自定义流程
示例代码(需要 fx12++):
public static class XVirtualFlow<I extends IndexedCell> extends VirtualFlow<I> {
@Override
protected void layoutChildren() {
super.layoutChildren();
fitCellWidths();
}
/**
* Resizes cell width to accomodate for invisible vbar.
*/
private void fitCellWidths() {
if (!isVertical() || getVbar().isVisible()) return;
double width = getWidth() - getVbar().getWidth();
for (I cell : getCells()) {
cell.resize(width, cell.getHeight());
}
}
}
public static class XTreeViewSkin<T> extends TreeViewSkin<T>{
public XTreeViewSkin(TreeView<T> control) {
super(control);
}
@Override
protected VirtualFlow<TreeCell<T>> createVirtualFlow() {
return new XVirtualFlow<>();
}
}
即时使用:
TreeView<UUID> tree = new TreeView<>(root) {
@Override
protected Skin<?> createDefaultSkin() {
return new XTreeViewSkin<>(this);
}
};