Extendable search pane in JavaFX


Posted by Steven

Yesterday, I implemented an extendable search pane in JavaFX. Having searched the web for a ready to use component, I found that most of the search panes hover above other nodes. But what if I want to extend the search pane by resizing other nodes, for example the results table? That is the only way to see all search results and all search options at the same time. This is what I build:

With the help of clipping (see this article by Sai Pradeep Dandem), I came up with the following solution. Because this code is written as a SSCCE, you can just copy > paste > see what it does.

ExtendableSearch.java:

  1. import javafx.animation.KeyFrame;
  2. import javafx.animation.KeyValue;
  3. import javafx.animation.Timeline;
  4. import javafx.application.Application;
  5. import javafx.event.ActionEvent;
  6. import javafx.event.EventHandler;
  7. import javafx.fxml.FXML;
  8. import javafx.fxml.FXMLLoader;
  9. import javafx.scene.Parent;
  10. import javafx.scene.Scene;
  11. import javafx.scene.layout.AnchorPane;
  12. import javafx.scene.shape.Rectangle;
  13. import javafx.stage.Stage;
  14. import javafx.util.Duration;
  15.  
  16. public class ExtendableSearch extends Application {
  17.  
  18. @FXML
  19. private AnchorPane extendableSearchPane;
  20.  
  21. private Rectangle clipRect;
  22.  
  23. public static void main(String[] args) {
  24. launch(args);
  25. }
  26.  
  27. @Override
  28. public void start(Stage stage) throws Exception {
  29. Parent root = FXMLLoader.load(getClass().getResource("extendableSearch.fxml"));
  30.  
  31. Scene scene = new Scene(root, 600, 400);
  32.  
  33. stage.setTitle("Extendable search pane demo");
  34. stage.setScene(scene);
  35. stage.show();
  36. }
  37.  
  38. @FXML
  39. void initialize() {
  40. double widthInitial = 200;
  41. double heightInitial = 200;
  42. clipRect = new Rectangle();
  43. clipRect.setWidth(widthInitial);
  44. clipRect.setHeight(0);
  45. clipRect.translateYProperty().set(heightInitial);
  46. extendableSearchPane.setClip(clipRect);
  47. extendableSearchPane.translateYProperty().set(-heightInitial);
  48. extendableSearchPane.prefHeightProperty().set(0);
  49. }
  50.  
  51. @FXML
  52. public void toggleExtendableSearch() {
  53.  
  54. clipRect.setWidth(extendableSearchPane.getWidth());
  55.  
  56. if (clipRect.heightProperty().get() != 0) {
  57.  
  58. // Animation for scroll up.
  59. Timeline timelineUp = new Timeline();
  60.  
  61. // Animation of sliding the search pane up, implemented via
  62. // clipping.
  63. final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 0);
  64. final KeyValue kvUp2 = new KeyValue(clipRect.translateYProperty(), extendableSearchPane.getHeight());
  65.  
  66. // The actual movement of the search pane. This makes the table
  67. // grow.
  68. final KeyValue kvUp4 = new KeyValue(extendableSearchPane.prefHeightProperty(), 0);
  69. final KeyValue kvUp3 = new KeyValue(extendableSearchPane.translateYProperty(), -extendableSearchPane.getHeight());
  70.  
  71. final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2, kvUp3, kvUp4);
  72. timelineUp.getKeyFrames().add(kfUp);
  73. timelineUp.play();
  74. } else {
  75.  
  76. // Animation for scroll down.
  77. Timeline timelineDown = new Timeline();
  78.  
  79. // Animation for sliding the search pane down. No change in size,
  80. // just making the visible part of the pane
  81. // bigger.
  82. final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), extendableSearchPane.getHeight());
  83. final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
  84.  
  85. // Growth of the pane.
  86. final KeyValue kvDwn4 = new KeyValue(extendableSearchPane.prefHeightProperty(), extendableSearchPane.getHeight());
  87. final KeyValue kvDwn3 = new KeyValue(extendableSearchPane.translateYProperty(), 0);
  88.  
  89. final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), createBouncingEffect(extendableSearchPane.getHeight()), kvDwn1, kvDwn2,
  90. kvDwn3, kvDwn4);
  91. timelineDown.getKeyFrames().add(kfDwn);
  92.  
  93. timelineDown.play();
  94. }
  95. }
  96.  
  97. private EventHandler<ActionEvent> createBouncingEffect(double height) {
  98. final Timeline timelineBounce = new Timeline();
  99. timelineBounce.setCycleCount(2);
  100. timelineBounce.setAutoReverse(true);
  101. final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (height - 15));
  102. final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15);
  103. final KeyValue kv3 = new KeyValue(extendableSearchPane.translateYProperty(), -15);
  104. final KeyFrame kf1 = new KeyFrame(Duration.millis(100), kv1, kv2, kv3);
  105. timelineBounce.getKeyFrames().add(kf1);
  106.  
  107. // Event handler to call bouncing effect after the scroll down is
  108. // finished.
  109. EventHandler<ActionEvent> handler = new EventHandler<ActionEvent>() {
  110. @Override
  111. public void handle(ActionEvent event) {
  112. timelineBounce.play();
  113. }
  114. };
  115. return handler;
  116. }
  117. }

extendableSearch.fxml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2.  
  3. <?import java.lang.*?>
  4. <?import java.net.*?>
  5. <?import javafx.collections.*?>
  6. <?import javafx.geometry.*?>
  7. <?import javafx.scene.control.*?>
  8. <?import javafx.scene.layout.*?>
  9.  
  10. <AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="599.0" style="" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="ExtendableSearch">
  11. <children>
  12. <VBox prefHeight="561.0" prefWidth="808.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
  13. <children>
  14. <AnchorPane prefHeight="38.0" prefWidth="808.0">
  15. <children>
  16. <Button mnemonicParsing="false" onAction="#toggleExtendableSearch" text="extended search" AnchorPane.bottomAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0" />
  17. </children>
  18. </AnchorPane>
  19. <BorderPane id="borderPane" prefHeight="561.0" prefWidth="808.0">
  20. <center>
  21. <AnchorPane id="AnchorPane">
  22. <children>
  23. <TableView id="mytableView" fx:id="ergebnisTabelle" maxWidth="1.7976931348623157E308" minHeight="200.0" prefHeight="640.0" prefWidth="976.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
  24. <columns>
  25. <TableColumn maxWidth="75.0" prefWidth="75.0" text="a column" />
  26. <TableColumn id="fa" maxWidth="5000.0" minWidth="10.0" prefWidth="113.0" text="another one" />
  27. <TableColumn maxWidth="5000.0" minWidth="10.0" prefWidth="195.0" text="oh look, another one!" />
  28. </columns>
  29. </TableView>
  30. </children>
  31. </AnchorPane>
  32. </center>
  33. <top>
  34. <AnchorPane id="AnchorPane" fx:id="extendableSearchPane" minHeight="119.0" prefHeight="119.0" prefWidth="976.0">
  35. <children>
  36. <GridPane hgap="10.0" minHeight="111.0" prefHeight="119.0" prefWidth="976.0" snapToPixel="true" vgap="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
  37. <children>
  38. <Label text="a search parameter" GridPane.columnIndex="0" GridPane.rowIndex="0">
  39. <GridPane.margin>
  40. <Insets fx:id="x1" />
  41. </GridPane.margin>
  42. </Label>
  43. <Label text="another search parameter" GridPane.columnIndex="0" GridPane.margin="$x1" GridPane.rowIndex="2">
  44. <labelFor>
  45. <TextField fx:id="projektname" prefWidth="200.0" GridPane.columnIndex="0" GridPane.margin="$x1" GridPane.rowIndex="3" />
  46. </labelFor>
  47. </Label>
  48. <TextField fx:id="faNummer" prefWidth="200.0" GridPane.columnIndex="0" GridPane.margin="$x1" GridPane.rowIndex="1" />
  49. <fx:reference source="projektname" />
  50. <Label text="some popup" GridPane.columnIndex="1" GridPane.margin="$x1" GridPane.rowIndex="0" />
  51. <TextField fx:id="kunden" prefWidth="200.0" GridPane.columnIndex="1" GridPane.margin="$x1" GridPane.rowIndex="3" />
  52. <Label text="oh look, here's another!" GridPane.columnIndex="1" GridPane.margin="$x1" GridPane.rowIndex="2" />
  53. <ComboBox fx:id="status" maxWidth="1.7976931348623157E308" prefWidth="-1.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
  54. <items>
  55. <FXCollections fx:factory="observableArrayList">
  56. <String fx:value="Item 1" />
  57. <String fx:value="Item 2" />
  58. <String fx:value="Item 3" />
  59. </FXCollections>
  60. </items>
  61. </ComboBox>
  62. <HBox id="HBox" alignment="CENTER_RIGHT" spacing="5.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="4">
  63. <children>
  64. <Button id="searchBtn" alignment="CENTER_RIGHT" contentDisplay="RIGHT" mnemonicParsing="false" text="search" />
  65. </children>
  66. </HBox>
  67. </children>
  68. <columnConstraints>
  69. <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
  70. <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
  71. </columnConstraints>
  72. <padding>
  73. <Insets bottom="5.0" left="10.0" right="10.0" />
  74. </padding>
  75. <rowConstraints>
  76. <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
  77. <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
  78. <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="ALWAYS" />
  79. <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
  80. <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
  81. </rowConstraints>
  82. </GridPane>
  83. </children>
  84. </AnchorPane>
  85. </top>
  86. </BorderPane>
  87. </children>
  88. </VBox>
  89. </children>
  90. </AnchorPane>
Category: 

Comments

If you are fine to reliquish the bounce effect you can also consider using a default TitledPane and pass a Control to toggle the expanded state via setGraphics() mehtod.


Hi Jens,

that's right! One of the motivations for writing this was to show a little "FX magic" to the customer. I know the bouncing effect isn't much. But compared to Swing, this makes the difference. :)


Unfortunately this isnt working with jdk8 

 


Oh that's bad. Do you have a workaround?


good job! Question: when I try to copy your code and paste it to a notepad or a text editor, none of the indentations and tabs get copied. what's causing it to lose all the typesetting? something to do with the white board. can you look into it? thx I'm using firefox.


Hi manny,

Did you try an IDE such as Eclipse or Idea and format the code after pasting it? That's the way I would do it. To execute the code, you will have to use an IDE sooner or later and don't want to user notepad.

Let me hear if that works for you,

Steven