JavaFX accordion with multiple open panes


Question

Is it possible to have an accordion with more then 1 open pane in JavaFX?

1
17
3/13/2013 1:09:28 PM

Accepted Answer

No, a JavaFX 2.2 Accordion can only have one open pane at a time.

I created an enhancement request (JDK-8090554 StackedTitledPanes control) for a feature which allows you to open more than one pane in the accordion at a time, however the feature request has currently not been implemented.

In the meantime, you can construct a similar control yourself quite easily by creating multiple TitledPane instances and placing these in a VBox.

private VBox createStackedTitledPanes() {
  final VBox stackedTitledPanes = new VBox();
  stackedTitledPanes.getChildren().setAll(
    new TitledPane("Pane 1",  contentNode1),
    new TitledPane("Pane 2",  contentNode2),
    new TitledPane("Pane 3",  contentNode3)
  );
  ((TitledPane) stackedTitledPanes.getChildren().get(0)).setExpanded(true);

  return stackedTitledPanes;
}

If necessary, you can wrap the VBox containing your panes in a ScrollPane, so that the contents of all of your expanded panes can be usable if their area overflows the available area.

I created a sample solution (icons are linkware from: http://www.fasticon.com).

fishyfishy

34
2/16/2017 5:51:44 PM

I had slightly different requirements

  1. The accordion either expands or manages the view space for the embedded views
  2. The entire view can be put into a scroll view
  3. Every box fully expands to the size of the whole view, if the accordion is fixed size, or it expands to the size of the content, if it is not fixed view.

Although in my case, I was not able to fulfill all of 3. and test 2., I was able to come up with the following fix:

1) Use a ScrollPane, with a VBox inside, with TitledWindows inside. 2) Make sure your TitledPanes are set to VBox.grow="SOMETIMES" . 3) Add a VBox as the last element and set VBox.vgrow="ALWAYS" - this pushes the TitledPanes up to their minimum size. Everybody else has provided Code examples, if you want to use fxml, or don't want to use Java, just using the elements directly works just as well (generated with SceneBuilder):

<ScrollPane fitToHeight="true" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER">
     <content>
        <VBox fx:id="leftVBox" maxHeight="1.7976931348623157E308" prefHeight="200.0" prefWidth="100.0">
           <children>
              <TitledPane fx:id="titledPanelOne" animated="false" expanded="false" style="-fx-background-color: red;" text="Pane One" VBox.vgrow="SOMETIMES">
                 <content>
                    <ListView fx:id="listViewOne" maxHeight="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" />
                 </content>
              </TitledPane>
              <TitledPane fx:id="titledPanelTwo" animated="false" expanded="false" style="-fx-background-color: green;" text="Pane Two" VBox.vgrow="SOMETIMES">
                 <content>
                    <ListView fx:id="listViewTwo" maxHeight="1.7976931348623157E308" prefHeight="200.0" prefWidth="200.0" />
                 </content>
              </TitledPane>
              <VBox prefHeight="0.0" prefWidth="0.0" VBox.vgrow="ALWAYS" />
           </children>
        </VBox>
     </content>
 </ScrollPane>

4) Although this does get you stacked boxes which expand/contract independently of one another, this doesn't fix the issue where you have boxes which do not resize properly to their contents (if for example you have a List View embedded as in the above example), and so you now have to scroll quite a bit when there is plenty of screen real-estate left. The solution? A bit of Java is required.

To implement this fix, we first bind the TitledPane's maxHeightProperty() to the outer VBox's heightProperty():

public class Controller implements Initializable {
  //... controller code
  @Override
  public void initialize(URL location, ResourceBundle resources) {
    //...
    variablesViewPane.maxHeightProperty().bind(leftVBox.heightProperty());
    historyViewPane.maxHeightProperty().bind(leftVBox.heightProperty());
  }
}

The we bind to each pane's expandedProperty(), and dynamically bind and unbind the prefHeighProperty():

private static void bindExpanded(TitledPane pane, ReadOnlyDoubleProperty prop) {
  pane.expandedProperty().addListener((observable, oldValue, newValue) -> {
    if(newValue) {
      pane.prefHeightProperty().bind(prop);
    } else {
      pane.prefHeightProperty().unbind();
      pane.prefHeightProperty().set(0);
    }
  });

}

If we are shown, we ask to be as large as the VBox, if we aren't shown, we ask to be as small as possible. The benefit of doing things this way is that the layout then automatically calculated the available height based on the number of currently shown TitledPanes - which leads to exactly the behavior that we want.

I go into more detail here:

http://sebastianaudet.com/blog/playing-with-javafx/


Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon