I have a TableView that scrolls vertically, and I would like the ScrollBar to extend to the top of it's parent AnchorPane and to be on top of the filler square at the top right. See below for what it's like by default. Note that my filler node is white, that is not a table column at the top right.
and below this line is what I want, correctly implemented by another program.
I was able to achieve this by doing
Platform.runLater(() ->
{
ScrollBar someScrollBar = (ScrollBar) someTable.lookup(".scroll-bar:vertical");
someScrollBar.setTranslateY(-12);
someScrollBar.setScaleY(1.2);
}
);
where someTable is a TableView made in FXML and is referred to in the controller initialize function.
It looks fine like this, but it doesn't scale correctly. If the containing AnchorPane resizes vertically, it looks awful.
Can anyone suggest a better way to do this?
Thank you so much for your time.
Custom layout of the scrollBars is not supported. And my initial comment you need a custom TableViewSkin with a custom TableHeaderRow: the latter is responsible for managing the .. well, tableHeader is only part of the story, unfortunately.
a TableHeaderRow indeed is responsible for laying out the table header
but: a TableHeaderRow can do nothing to layout the vertical scrollbar - when it tries to do so in an overridden layoutChildren() it's immediately reset
the reset happens in the VirtualFlow (which is the parent of the scrollBar)
So at the end of the day, we need
a custom TableHeaderRow that signals the need for enlarging and relocating the scrollBar: the example below sets a marker if the scrollBar is visible (tbd: check whether or not the menuButton is visible) with the desired additional height in the scrollBar's properties map
a custom VirtualFlow that can handle the marker and actually does the layout as needed: the example below checks for the marker and resizes/relocates the scrollBar if needed.
a custom TableViewSkin to inject both (via overridden factory methods)
The example is written against fx11, should work for fx10 but not for fx9 because the latter doesn't allow to provide a custom VirtualFlow:
public class TableWithoutCorner extends Application {
/**
* Custom TableHeaderRow that requests a larger vbar height
* if needed.
*/
private static class MyTableHeader extends TableHeaderRow {
private Region cornerAlias;
private ScrollBar vBar;
private TableViewSkinBase skin;
public MyTableHeader(TableViewSkinBase skin) {
super(skin);
this.skin = skin;
}
#Override
protected void layoutChildren() {
super.layoutChildren();
adjustCornerLayout();
}
private void adjustCornerLayout() {
checkAlias();
// tbd: check also if corner is visible
if (!vBar.isVisible()) {
vBar.getProperties().remove("DELTA");
} else {
vBar.getProperties().put("DELTA", getHeight());
}
}
private void checkAlias() {
if (cornerAlias == null) {
cornerAlias = (Region) lookup(".show-hide-columns-button");
}
if (vBar == null) {
vBar = (ScrollBar) skin.getSkinnable().lookup(".scroll-bar:vertical");
}
}
}
/**
* Custom VirtualFlow that respects additinal height for its
* vertical ScrollBar.
*/
private static class MyFlow extends VirtualFlow {
private ScrollBar vBar;
private Region clip;
public MyFlow() {
// the scrollbar to adjust
vBar = (ScrollBar) lookup(".scroll-bar:vertical");
// the clipped container to use for accessing viewport dimensions
clip = (Region) lookup(".clipped-container");
}
/**
* Overridden to adjust vertical scrollbar's height and y-location
* after calling super.
*/
#Override
protected void layoutChildren() {
super.layoutChildren();
adjustVBar();
}
/**
* Adjusts vBar height and y-location by the height as
* requested by the table header.
*/
protected void adjustVBar() {
if (vBar.getProperties().get("DELTA") == null) return;
double delta = (double) vBar.getProperties().get("DELTA");
vBar.relocate(clip.getWidth(), - delta);
vBar.resize(vBar.getWidth(), clip.getHeight() + delta);
}
}
/**
* Boilerplate: need custom TableViewSkin to inject a custom TableHeaderRow and
* custom VirtualFlow.
*/
private static class MyTableViewSkin<T> extends TableViewSkin<T> {
public MyTableViewSkin(TableView<T> control) {
super(control);
}
#Override
protected TableHeaderRow createTableHeaderRow() {
return new MyTableHeader(this);
}
#Override
protected VirtualFlow<TableRow<T>> createVirtualFlow() {
return new MyFlow();
}
}
private Parent createContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales())) {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTableViewSkin(this);
}
};
TableColumn<Locale, String> col = new TableColumn<>("Name");
col.setCellValueFactory(new PropertyValueFactory<>("displayName"));
table.getColumns().addAll(col);
return table;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
//stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableWithoutCorner.class.getName());
}
Related
Im working on a small JavaFX project. In one of my scenes, I want to dynamically add and remove a custom component I implemented, which extends from TitledPane, to respectively from an Accordion. This works all well and good, but after removing the pane from the Accordion the damn thing won't resize immidately, but only after I click somewhere on the gui. I prepared the following GIF to visualize the problem to you.
Can someone tell me why the accordion only resizes after I clicked somewhere on the gui interface and not immidiately? I mean auto resizing doesn't seem to be a problem, it just won't trigger...can someone tell me why that is the case? Maybe this is obvious, but I am not very familiar with JavaFX, so I am really stuck here. I also observed a similar behavior with other component, so maybe I am missing something fundamentally here.
UPDATE
Ok I created a minimal example for you to reproduce my problem. You can clone the repository on GitHub javafx-demo and try it out yourself. Doing this I noticed, that the Accordion resizes only if I click on it and not when I click anywhere else on the gui.
UPDATE 1
I simplified the example further. You can find the example in the GitHub repository above or see the code below:
App
public class App extends Application {
#Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("parentView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
ParentController
public class ParentController {
#FXML
private Accordion accordion;
public void onAddAction() {
var itemControl = new ItemControl();
EventHandler<ActionEvent> removeEventHandler = event -> {
accordion.getPanes().remove(itemControl);
};
itemControl.setOnRemoveProperty(removeEventHandler);
accordion.getPanes().add(itemControl);
}
}
Parent View
<StackPane xmlns="http://javafx.com/javafx/16"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ParentController">
<Group StackPane.alignment="CENTER">
<VBox>
<Accordion fx:id="accordion"/>
<Button onAction="#onAddAction" text="Add"/>
</VBox>
</Group>
</StackPane>
ItemControl
public class ItemControl extends TitledPane {
private final UUID id = UUID.randomUUID();
private final ObjectProperty<EventHandler<ActionEvent>> onRemoveProperty = new SimpleObjectProperty<>();
#FXML
private Button removeButton;
public ItemControl() {
FXMLLoader fxmlLoader = new FXMLLoader(ItemControl.class.getResource("itemControl.fxml"));
fxmlLoader.setRoot(this);
fxmlLoader.setController(this);
try {
fxmlLoader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
#FXML
public void initialize() {
removeButton.onActionProperty().bind(onRemoveProperty);
}
public void setOnRemoveProperty(EventHandler<ActionEvent> onRemoveProperty) {
this.onRemoveProperty.set(onRemoveProperty);
}
// equals and hashCode omitted for brevity (id instance variable is used as identifier)
}
ItemControl FXML
<fx:root type="javafx.scene.control.TitledPane" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
<VBox>
<Button fx:id="removeButton" text="Remove"/>
</VBox>
</fx:root>
The behavior is a bug in AccordionSkin. The technical reason is that it keeps internal references to the current and previous expanded pane - both used in calculating the min/pref height - which are not updated correctly on removing the expanded pane. A fix would be to null those references if the panes are no longer part of the accordion, f.i. from the skin's listener to the panes list.
There is no clean way to work around this because all involved fields/methods are private - if we are allowed to go dirty, though, we can hack around the bug with reflection.
The basics:
extend AccordionSkin and let our accordion use the extended version
in the skin, override both computeMin/Pref/Height to check/fix the references before returning super
check: the panes should be contained in the accordion's panes
fix: if not, set the reference to null
Notes:
the reflective access to internal fields requires that the package is opened at runtime
the usual beware: tweaking/relying on implementation internals is highly version dependent and might/will break eventually
FXUtils is my local utility class for reflection, you have to replace it with your own implementation
The code:
public class SimpleLayoutAccordionOnRemove extends Application {
/**
* AccordionSkin that hacks the broken layout after remove of expanded pane.
*/
public static class HackedAccordionSkin extends AccordionSkin {
public HackedAccordionSkin(Accordion control) {
super(control);
}
#Override
protected double computeMinHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
checkPaneFields();
return super.computeMinHeight(width, topInset, rightInset, bottomInset, leftInset);
}
#Override
protected double computePrefHeight(double width, double topInset, double rightInset,
double bottomInset, double leftInset) {
checkPaneFields();
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
private void checkPaneFields() {
checkPaneField("previousPane");
checkPaneField("expandedPane");
}
/**
* Check if the pane referenced by the field with the given name is contained
* in the accordion's panes and sets it to null if not.
*/
private void checkPaneField(String fieldName) {
TitledPane prev = (TitledPane) FXUtils.invokeGetFieldValue(AccordionSkin.class, this, fieldName);
if (!getSkinnable().getPanes().contains(prev)) {
FXUtils.invokeSetFieldValue(AccordionSkin.class, this, fieldName, null);
}
}
}
private Parent createContent() {
Accordion accordion = new Accordion() {
#Override
protected Skin<?> createDefaultSkin() {
return new HackedAccordionSkin(this);
}
};
Button add = new Button("add");
add.setOnAction(e -> {
addTitledPane(accordion);
});
VBox accBox = new VBox(accordion, add);
StackPane content = new StackPane(new Group(accBox));
return content;
}
int count;
private void addTitledPane(Accordion accordion) {
TitledPane pane = new TitledPane();
pane.setText("Pane " + count++);
Button remove = new Button("remove");
remove.setOnAction(e -> {
accordion.getPanes().remove(pane);
});
VBox box = new VBox(remove);
pane.setContent(box);
accordion.getPanes().add(pane);
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 600, 400));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The current implementation of the VirtualFlow only makes scrollbars visible when view rect becomes less than control size. By control I mean ListView, TreeView and whatever standard virtualized controls. The problem is that vertical scrollbar appearance causes recalculation of the control width, namely it slightly shifts cell content to the left side. This is clearly noticeable and very uncomfortable movement.
I need to reserve some space for the vertical scrollbar beforehand, but none of controls provide API to manipulate VirtualFlow scrollbars behavior, which is very unfortunate API design. Not to mention that most of the implementations place scrollbars on top of the component, thus just overlapping the small part of it.
The question is, "Which is the best way to achieve this?". Paddings won't help, and JavaFX has no margins support. I could put control (e.g ListView) inside of ScrollPane, but I'd bet VirtualFlow won't continue to reuse cells in that case, so it's not a solution.
EXAMPLE:
Expand and collapse node2, it shifts lbRight content.
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);
}
}
}
Reacting to
you can't just extend the VirtualFlow and override a method
certainly true if the method is deeply hidden by package/-private access (but even then: javafx is open source, checkout-edit-compile-distribute is also an option :). In this case we might get along with overriding public api as outlined below (not formally tested!).
VirtualFlow is the "layout" of cells and scrollBars: in particular, it has to cope with handling sizing/locating of all content w/out scrollBars being visible. There are options on how that can be done:
adjust cell width to always fill the viewport, increasing/decreasing when vertical scrollBar is hidden/visible
keep cell width constant such that there is always space left for the scrollBar, be it visible or not
keep cell width constant such that there is never space left the scrollBar, laying it out on top of cell
others ??
Default VirtualFlow implements the first with no option to switch to any other. (might be candidate for an RFE, feel free to report :).
Digging into the code reveals that the final sizing of the cells is done by calling cell.resize(..) (as already noted and exploited in the self-answer) near the end of the layout code. Overriding a custom cell's resize is perfectly valid and a good option .. but not the only one, IMO. An alternative is to
extend VirtualFlow and override layoutChildren to adjust cell width as needed
extend TreeViewSkin to use the custom flow
Example code (requires 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<>();
}
}
On-the-fly usage:
TreeView<UUID> tree = new TreeView<>(root) {
#Override
protected Skin<?> createDefaultSkin() {
return new XTreeViewSkin<>(this);
}
};
Ok, this is summary based on #kleopatra comments and OpenJFX code exploration. There will be no code to solve the problem, but still maybe it will spare some time to someone.
As being said, it's VirtualFlow responsibility to manage virtualized control viewport size. All magic happens in the 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.
Since all implementation details are private or package-private, you can't just extend the VirtualFlow and override method or two, you have to copy-paste and edit entire class (to remove one line, yes). Given that, changing internal components layout could be a better option.
Sometimes, I adore languages those have no encapsulation.
UPDATE:
I've solved the problem. There is no way no reserve space for vertical scrollbar without tweaking JavaFX internals, but we can limit cell width, so it would be always less than TreeView (or List View) width. Here is simple example.
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);
}
}
}
It's far from perfect, but it works.
Incredible easy question: I have a SWT table (viewer) and use a SWT.MeasureItem listener to set the cell height. How do I align the cell content to the bottom of the cell?
(It would probably work with another listener to SWT.PaintItem and some math and rendering all my cells manually, but that can't be the right way.)
public class TableDialog extends Dialog {
public static void main(String[] args) {
TableDialog dialog = new TableDialog(new Shell());
dialog.open();
}
public TableDialog(Shell parent) {
super(parent);
}
#Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText("Table Test");
newShell.setSize(500, 300);
}
#Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
container.setLayout(new FillLayout());
TableViewer viewer = new TableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
viewer.setContentProvider(new ArrayContentProvider());
viewer.setInput(Arrays.asList("A", "B", " C"));
Table table = viewer.getTable();
table.setLinesVisible(true);
table.addListener(SWT.MeasureItem, e -> e.height = 90);
return container;
}
}
Once you start using SWT.MeasureItem you need to do the drawing as well.
Since you are using TableViewer you can combine all this in one class by using an OwnerDrawLabelProvider as the viewer label provider. A very simple version would be something like this:
viewer.setLabelProvider(new OwnerDrawLabelProvider()
{
#Override
protected void paint(final Event event, final Object element)
{
String text = element.toString();
GC gc = event.gc;
int textHeight = gc.textExtent(text).y;
int yPos = event.y + event.height - textHeight;
gc.drawText(text, event.x, yPos);
}
#Override
protected void measure(final Event event, final Object element)
{
event.height = 90;
}
#Override
protected void erase(final Event event, final Object element)
{
// Stop the default draw of the foreground
event.detail &= ~SWT.FOREGROUND;
}
});
I am afraid, SWT.PaintItem is the right way in this case.
One of the SWT Snippets demonstrates how to draw multiple lines in a table item. It may serve as a starting point for your custom drawing code:
http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet231.java
The Custom Drawing Table and Tree Items article provides further information.
I'm trying to avoid horizontal scrolling in ListView. The ListView instance holds list of HBox items, each item has a different width.
So far I'm using such a cell factory:
public class ListViewCell extends ListCell<Data>
{
#Override
public void updateItem(Data data, boolean empty)
{
super.updateItem(data, empty);
if(empty || data == null){
setGraphic(null);
setText(null);
}
if(data != null)
{
Region region = createRow(data);
region.prefWidthProperty().bind(mListView.widthProperty().subtract(20));
region.maxWidthProperty().bind(mListView.widthProperty().subtract(20));
setGraphic(region);
}
}
}
Unfortunately it is not enough. Usually after adding several items ListView's horizontal scrollbar appears. Even if it seems to be unnecessary.
How can I assure, that ListViewCell will not exceed it's parent width and horizontal scrollbar will not appear?
There is a lot at play here that make customizing ListView horizontal scrollbar behavior difficult to deal with. In addition to that, common misunderstandings on how ListView works can cause other problems.
The main issue to address is that the width of the ListCells will not automatically adapt when the vertical scrollbar becomes visible. Therefore, the moment it is, suddenly the contents are too wide to fit between the left edge of the ListView and the left edge of the vertical scrollbar, triggering a horizontal scrollbar. There is also the default padding of a ListCell as well as the border widths of the ListView itself to consider when determining the proper binding to set.
The following class that extends ListView:
public class WidthBoundList extends ListView {
private final BooleanProperty vbarVisibleProperty = new SimpleBooleanProperty(false);
private final boolean bindPrefWidth;
private final double scrollbarThickness;
private final double sumBorderSides;
public WidthBoundList(double scrollbarThickness, double sumBorderSides, boolean bindPrefWidth) {
this.scrollbarThickness = scrollbarThickness;
this.sumBorderSides = sumBorderSides;
this.bindPrefWidth = bindPrefWidth;
Platform.runLater(()->{
findScroller();
});
}
private void findScroller() {
if (!this.getChildren().isEmpty()) {
VirtualFlow flow = (VirtualFlow)this.getChildren().get(0);
if (flow != null) {
List<Node> flowChildren = flow.getChildrenUnmodifiable();
int len = flowChildren .size();
for (int i = 0; i < len; i++) {
Node n = flowChildren .get(i);
if (n.getClass().equals(VirtualScrollBar.class)) {
final ScrollBar bar = (ScrollBar) n;
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
vbarVisibleProperty.bind(bar.visibleProperty());
bar.setPrefWidth(scrollbarThickness);
bar.setMinWidth(scrollbarThickness);
bar.setMaxWidth(scrollbarThickness);
} else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
bar.setPrefHeight(scrollbarThickness);
bar.setMinHeight(scrollbarThickness);
bar.setMaxHeight(scrollbarThickness);
}
}
}
} else {
Platform.runLater(()->{
findScroller();
});
}
} else {
Platform.runLater(()->{
findScroller();
});
}
}
public void bindWidthScrollCondition(Region node) {
node.maxWidthProperty().unbind();
node.prefWidthProperty().unbind();
node.maxWidthProperty().bind(
Bindings.when(vbarVisibleProperty)
.then(this.widthProperty().subtract(scrollbarThickness).subtract(sumBorderSides))
.otherwise(this.widthProperty().subtract(sumBorderSides))
);
if (bindPrefWidth) {
node.prefWidthProperty().bind(node.maxWidthProperty());
}
}
}
Regarding your code, your bindings could cause problems. A ListCell's updateItem() method is not only called when the ListCell is created. A ListView can contain a pretty large list of data, so to improve the performance only the ListCells scrolled into view (and possibly a few before and after) need their graphic rendered. The updateItem() method handles this. In your code, a Region is being created over and over again and each and every one of them is being bound to the width of your ListView. Instead, the ListCell itself should be bound.
The following class extends ListCell and the method to bind the HBox is called in the constructor:
public class BoundListCell extends ListCell<String> {
private final HBox hbox;
private final Label label;
public BoundListCell(WidthBoundList widthBoundList) {
this.setPadding(Insets.EMPTY);
hbox = new HBox();
label = new Label();
hbox.setPadding(new Insets(2, 4, 2, 4));
hbox.getChildren().add(label);
widthBoundList.bindWidthScrollCondition(this);
}
#Override
public void updateItem(String data, boolean empty) {
super.updateItem(data, empty);
if (empty || data == null) {
label.setText("");
setGraphic(null);
setText(null);
} else {
label.setText(data);
setGraphic(hbox);
}
}
}
The scrollbarThickness parameter of WidthBoundList constructor has been set to 12. The sumBorderSides parameter has been set to 2 because my WidthBoundList has a one pixel border on the right and left. The bindPrefWidth parameter has been set to true to prevent the horizontal scroller from showing at all (labels have ellipses, any non-text nodes that you might add to the hbox will simply be clipped). Set bindPrefWidth to false to allow a horizontal scrollbar, and with these proper bindings it should only show when needed. An implementation:
private final WidthBoundList myListView = new WidthBoundList(12, 2, true);
public static void main(final String... a) {
Application.launch(a);
}
#Override
public void start(final Stage primaryStage) throws Exception {
myListView.setCellFactory(c -> new BoundListCell(myListView));
VBox vBox = new VBox();
vBox.setFillWidth(true);
vBox.setAlignment(Pos.CENTER);
vBox.setSpacing(5);
Button button = new Button("APPEND");
button.setOnAction((e)->{
myListView.getItems().add("THIS IS LIST ITEM NUMBER " + myListView.getItems().size());
});
vBox.getChildren().addAll(myListView, button);
myListView.maxWidthProperty().bind(vBox.widthProperty().subtract(20));
myListView.prefHeightProperty().bind(vBox.heightProperty().subtract(20));
primaryStage.setScene(new Scene(vBox, 200, 400));
primaryStage.show();
}
I want to make an animation with the "width" of my node.
In this case my node is a "AnchorPane".
I try to make a navigation drawer in javafx.
there is no property "width Property ()"?
new Key Value (node.width Property (), 1, WEB_EASE)
node.widthProperty().getValue() not found
My code:
public void changeWidth(final Node node, double width) {
this.node = node;
this.timeline = TimelineBuilder.create()
.keyFrames(
new KeyFrame(Duration.millis(20),
new KeyValue( going here? , width, WEB_EASE)
)
)
.build();
setCycleDuration(Duration.seconds(5));
setDelay(Duration.seconds(0));
}
Example with "opacity" property:
new KeyValue(node.opacityProperty(), 1, WEB_EASE)
My class ConfigAnimationViewPane:
public class ConfigAnimationViewPane extends Transition {
protected static final Interpolator WEB_EASE = Interpolator.EASE_BOTH;
protected AnchorPane node;
protected Timeline timeline;
private boolean oldCache = false;
private CacheHint oldCacheHint = CacheHint.DEFAULT;
private final boolean useCache = true;
/**
* Called when the animation is starting
*/
protected void starting() {
if (useCache) {
oldCache = node.isCache();
oldCacheHint = node.getCacheHint();
node.setCache(true);
node.setCacheHint(CacheHint.SPEED);
}
}
/**
* Called when the animation is stopping
*/
protected void stopping() {
if (useCache) {
node.setCache(oldCache);
node.setCacheHint(oldCacheHint);
}
}
#Override protected void interpolate(double d) {
timeline.playFrom(Duration.seconds(d));
timeline.stop();
}
}
This is mi controller:
Move the menu to the left (the occult)
LeftTransition leftTransition = new LeftTransition();
leftTransition.OpenMenu(list1);
leftTransition.play();
Here I want to put my size "AnchorPane".
(Set the width of my "anchorpane")
/*ViewPaneTransition paneTransition = new ViewPaneTransition();
paneTransition.CloseMenu(viewPane, width );
paneTransition.play();*/
Here is a working example for java 9. It changes both the width and the height (just remove the height line if you don't need it)
The widthProperty is readOnly so yo have to set either maxWidth or minWidth switch the case you need.
the delay duration on timeline is 0 by default, no need to set it, and the cycleduration is computed from the keyframes durations.
public void changeSize(final Pane pane, double width, double height) {
Duration cycleDuration = Duration.millis(500);
Timeline timeline = new Timeline(
new KeyFrame(cycleDuration,
new KeyValue(pane.maxWidthProperty(),width,Interpolator.EASE_BOTH)),
new KeyFrame(cycleDuration,
new KeyValue(pane.maxHeightProperty(),height,Interpolator.EASE_BOTH))
);
timeline.play();
timeline.setOnFinished(event->{
/* insert code here if you need */
});
}
public void changeWidth(final Pane/*Region*/ node, double width) {//its a Pane or Regions
this.node = node;
this.timeline = TimelineBuilder.create()
.keyFrames(
new KeyFrame(Duration.millis(20),
new KeyValue( going here? , width, WEB_EASE)
)
)
.build();
setCycleDuration(Duration.seconds(5));
setDelay(Duration.seconds(0));
}
In this case my node is a "AnchorPane".
your AnchorPane is a subclass or Pane, let your wrappers or methods take in Pane or their respective class