Mastodon

Extendable Search Pane in JavaFX

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:

Extendable Search Pane with JavaFX

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:

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ExtendableSearch extends Application {

	@FXML
	private AnchorPane extendableSearchPane;
 
	private Rectangle clipRect;
 
	public static void main(String[] args) {
		launch(args);
	}
 
	@Override
	public void start(Stage stage) throws Exception {
		Parent root = FXMLLoader.load(getClass().getResource("extendableSearch.fxml"));
 
		Scene scene = new Scene(root, 600, 400);
 
		stage.setTitle("Extendable search pane demo");
		stage.setScene(scene);
		stage.show();
	}
 
	@FXML
	void initialize() {
		double widthInitial = 200;
		double heightInitial = 200;
		clipRect = new Rectangle();
		clipRect.setWidth(widthInitial);
		clipRect.setHeight(0);
		clipRect.translateYProperty().set(heightInitial);
		extendableSearchPane.setClip(clipRect);
		extendableSearchPane.translateYProperty().set(-heightInitial);
		extendableSearchPane.prefHeightProperty().set(0);
	}
 
	@FXML
	public void toggleExtendableSearch() {
 
		clipRect.setWidth(extendableSearchPane.getWidth());
 
		if (clipRect.heightProperty().get() != 0) {
 
			// Animation for scroll up.
			Timeline timelineUp = new Timeline();
 
			// Animation of sliding the search pane up, implemented via
			// clipping.
			final KeyValue kvUp1 = new KeyValue(clipRect.heightProperty(), 0);
			final KeyValue kvUp2 = new KeyValue(clipRect.translateYProperty(), extendableSearchPane.getHeight());
 
			// The actual movement of the search pane. This makes the table
			// grow.
			final KeyValue kvUp4 = new KeyValue(extendableSearchPane.prefHeightProperty(), 0);
			final KeyValue kvUp3 = new KeyValue(extendableSearchPane.translateYProperty(), -extendableSearchPane.getHeight());
 
			final KeyFrame kfUp = new KeyFrame(Duration.millis(200), kvUp1, kvUp2, kvUp3, kvUp4);
			timelineUp.getKeyFrames().add(kfUp);
			timelineUp.play();
		} else {
 
			// Animation for scroll down.
			Timeline timelineDown = new Timeline();
 
			// Animation for sliding the search pane down. No change in size,
			// just making the visible part of the pane
			// bigger.
			final KeyValue kvDwn1 = new KeyValue(clipRect.heightProperty(), extendableSearchPane.getHeight());
			final KeyValue kvDwn2 = new KeyValue(clipRect.translateYProperty(), 0);
 
			// Growth of the pane.
			final KeyValue kvDwn4 = new KeyValue(extendableSearchPane.prefHeightProperty(), extendableSearchPane.getHeight());
			final KeyValue kvDwn3 = new KeyValue(extendableSearchPane.translateYProperty(), 0);
 
			final KeyFrame kfDwn = new KeyFrame(Duration.millis(200), createBouncingEffect(extendableSearchPane.getHeight()), kvDwn1, kvDwn2,
					kvDwn3, kvDwn4);
			timelineDown.getKeyFrames().add(kfDwn);
 
			timelineDown.play();
		}
	}
 
	private EventHandler<ActionEvent> createBouncingEffect(double height) {
		final Timeline timelineBounce = new Timeline();
		timelineBounce.setCycleCount(2);
		timelineBounce.setAutoReverse(true);
		final KeyValue kv1 = new KeyValue(clipRect.heightProperty(), (height - 15));
		final KeyValue kv2 = new KeyValue(clipRect.translateYProperty(), 15);
		final KeyValue kv3 = new KeyValue(extendableSearchPane.translateYProperty(), -15);
		final KeyFrame kf1 = new KeyFrame(Duration.millis(100), kv1, kv2, kv3);
		timelineBounce.getKeyFrames().add(kf1);
 
		// Event handler to call bouncing effect after the scroll down is
		// finished.
		EventHandler<ActionEvent> handler = new EventHandler<ActionEvent>() {
			@Override
			public void handle(ActionEvent event) {
				timelineBounce.play();
			}
		};
		return handler;
	}
}

extendableSearch.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.collections.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<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">
  <children>
    <VBox prefHeight="561.0" prefWidth="808.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
      <children>
        <AnchorPane prefHeight="38.0" prefWidth="808.0">
          <children>
            <Button mnemonicParsing="false" onAction="#toggleExtendableSearch" text="extended search" AnchorPane.bottomAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0" />
          </children>
        </AnchorPane>
        <BorderPane id="borderPane" prefHeight="561.0" prefWidth="808.0">
          <center>
            <AnchorPane id="AnchorPane">
              <children>
                <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">
                  <columns>
                    <TableColumn maxWidth="75.0" prefWidth="75.0" text="a column" />
                    <TableColumn id="fa" maxWidth="5000.0" minWidth="10.0" prefWidth="113.0" text="another one" />
                    <TableColumn maxWidth="5000.0" minWidth="10.0" prefWidth="195.0" text="oh look, another one!" />
                  </columns>
                </TableView>
              </children>
            </AnchorPane>
          </center>
          <top>
            <AnchorPane id="AnchorPane" fx:id="extendableSearchPane" minHeight="119.0" prefHeight="119.0" prefWidth="976.0">
              <children>
                <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">
                  <children>
                    <Label text="a search parameter" GridPane.columnIndex="0" GridPane.rowIndex="0">
                      <GridPane.margin>
                        <Insets fx:id="x1" />
                      </GridPane.margin>
                    </Label>
                    <Label text="another search parameter" GridPane.columnIndex="0" GridPane.margin="$x1" GridPane.rowIndex="2">
                      <labelFor>
                        <TextField fx:id="projektname" prefWidth="200.0" GridPane.columnIndex="0" GridPane.margin="$x1" GridPane.rowIndex="3" />
                      </labelFor>
                    </Label>
                    <TextField fx:id="faNummer" prefWidth="200.0" GridPane.columnIndex="0" GridPane.margin="$x1" GridPane.rowIndex="1" />
                    <fx:reference source="projektname" />
                    <Label text="some popup" GridPane.columnIndex="1" GridPane.margin="$x1" GridPane.rowIndex="0" />
                    <TextField fx:id="kunden" prefWidth="200.0" GridPane.columnIndex="1" GridPane.margin="$x1" GridPane.rowIndex="3" />
                    <Label text="oh look, here's another!" GridPane.columnIndex="1" GridPane.margin="$x1" GridPane.rowIndex="2" />
                    <ComboBox fx:id="status" maxWidth="1.7976931348623157E308" prefWidth="-1.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1">
                      <items>
                        <FXCollections fx:factory="observableArrayList">
                          <String fx:value="Item 1" />
                          <String fx:value="Item 2" />
                          <String fx:value="Item 3" />
                        </FXCollections>
                      </items>
                    </ComboBox>
                    <HBox id="HBox" alignment="CENTER_RIGHT" spacing="5.0" GridPane.columnIndex="1" GridPane.halignment="CENTER" GridPane.rowIndex="4">
                      <children>
                        <Button id="searchBtn" alignment="CENTER_RIGHT" contentDisplay="RIGHT" mnemonicParsing="false" text="search" />
                      </children>
                    </HBox>
                  </children>
                  <columnConstraints>
                    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
                  </columnConstraints>
                  <padding>
                    <Insets bottom="5.0" left="10.0" right="10.0" />
                  </padding>
                  <rowConstraints>
                    <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
                    <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
                    <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="ALWAYS" />
                    <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
                    <RowConstraints maxHeight="22.0" minHeight="10.0" prefHeight="22.0" vgrow="SOMETIMES" />
                  </rowConstraints>
                </GridPane>
              </children>
            </AnchorPane>
          </top>
        </BorderPane>
      </children>
    </VBox>
  </children>
</AnchorPane>