Shadow Fields vs. Property Accessor Interface

Carl Dea recently followed up on a blog post of mine called Save Memory! Use Shadow Fields for Properties. In his blog he suggested the use of an interface called “Property Accessor” to eliminate the heavy use of boilerplate code that is needed in order to use  shadow fields. Carl also mentioned that he hasn’t tested his approach with a lot of data and that he or some reader might follow up with a performance comparison. So here it comes.

I wrote a small test application that implements the three strategies that Carl mentions in his post:

  1. standard properties that are instantiated at the same time when the class gets instantiated
  2. property accessor interface as proposed by Carl
  3. shadow fields as proposed in my recent blog post

The code can be found on GitHub. (when you run it please make sure to set the initial and the maximum heap size to 2048 MB -ms2048m -mx2048m, otherwise the memory allocations will mess up the results).

The application allows the user to execute these strategies either with or without asking for the properties. It measures the time spent and the memory used. It should be noted that the measurements are not scientific as I used System.currentTimeInMillis() and Runtime.gc(). When run several times I would still argue that the qualitative value of these tests are acceptable.

The first screenshot below shows the numbers you get when you create between 1,000 and 2,000,000 instances of the Employee class that Carl used for his blog. The tests do not ask for the properties that are available on Employee (name, powers, supervisor, minions):

A1

As you can see the “shadow field” strategy is the fastest and also uses the least amount of memory. This makes sense as the “standard properties” strategy always creates those fat property objects and the “property accessor interface” internally manages a hash map for each model object. Only the “shadow field” strategy works with a minimal amount of data structures. In the case of the selected test it saves a total of 230 MB. If you now imagine that typical applications have many model classes and many of those much more complex than the Employee test class then you can imagine how much memory you can save.

The next screenshot shows the measurements taken when also accessing all four properties and observables inside the Employee class.

A2.png

Now the “standard properties” strategy is the fastest and also the one that uses the least amount of memory. Once again, this makes sense, as this strategy now implements the perfect approach for the given use case. However, the “shadow field” strategy comes in at a very close 2nd place.

Conclusion

The “Property Accessor Interface” strategy is successful at reducing  the noise created by all the boilerplate code needed for shadow fields but it comes at a price that I believe is too high to pay for any application that creates more than just a few model objects.

 

 

P.S.: it should be noted that the comparison is even more in favour of the “shadow fields” strategy when the initial heap size of the JVM is left at its default setting. In this case the test app has to keep asking for more heap space which is quite an expensive operation.

JavaFX Tip 23: Save Memory! Shadow Fields for Properties.

Properties and property bindings introduced in Java 8 are extremely useful programming concepts. They are especially useful when you are developing user interfaces. In fact they are so useful that developers have fallen victim to the idea that everything should be a property instead of a primitive. Unfortunately they easily forget that properties such as SimpleLongProperty are much bigger objects than standard types such as Long. And of course they are much bigger than primitive data types such as long.

In one of my current projects pretty much every model object used by the client is composed of properties. For many of these model objects it is the right approach because they will be edited / modified via JavaFX controls. But there are also many model objects that are not edited. They exist to support the rendering of schedules in the FlexGanttFX control. These objects do not need to be observed, hence they do not need to provide properties … but they do and they waste a lot of memory because they do.

One way to fix this would be to refactor the model classes and to get rid of all properties, but then again we might want to use these objects in a future release in a different context and then we might need properties because we want to edit them directly. What to do?

Shadow Fields

The solution to this problem is something I recently saw Gerrit Grunwald do in the code of his Medusa project and a pattern that was described by Mr. Properties himself Michael Heinrichs.  The pattern makes use of a “shadow field” that is of the same type as the wrapped object inside the property. When using this pattern a property will only be created when it is really needed (“when somebody asks for it”).

Example

In this example we want to manage an attribute called “title”. We need a setter, a getter, and the property accessor.

private String _title = "Untitled"; // shadow field

private StringProperty title;

public final String getTitle() {
    return title == null ? _title : title.get();
}

public final void setTitle(String newTitle) {
    if (title == null) {
        _title = newTitle;
    } else {
        title.set(newTitle);
    }
}

public final StringProperty titleProperty() {
    if (title == null) {
        /// !!!! pass shadow field to constructor
        title = new SimpleStringProperty(this, "title", _title);  
    }

    return title;
}

By using this pattern I was able to bring down the memory footprint from 310 MB to 250 MB for a specific use case in my project. The saved memory is ten times the total memory my computer had when I was a student. Just think about that!

JavaFX Tip 22: Autosize (Tree) Table Columns

One of the first things mentioned as a “missing feature” in the JavaFX “Missing Features Survey” was the ability to auto-resize columns in tables / tree tables. It is correct that there is no public API for it, but when you pay close attention then you will notice that there must be code for doing this somewhere inside JavaFX, because the user can auto-resize a column by double clicking on the divider line between the column and the next column to the right.

But like most people I felt that this was not good enough for my code. I wanted an API for FlexGanttFX that would allow the user to auto resize one or all columns inside the Gantt charts. So I searched for the code that was hidden somewhere in the tree table  or tree table skin (can’t actually remember where) and reused it with some minor modifications in my classes.

The following is the result of this work. It targets the TreeTableView and not the TableView, but making it work for the standard table is straight-forward. Simply replace all TreeTableColumn occurrences with TableColumn. Please notice that resizing all rows can have a serious performance impact, so you might have to limit the number of rows that will be considered for the calculations via the maxRows parameter.

	/**
	 * This method will resize all columns in the tree table view to ensure that
	 * the content of all cells will be completely visible. Note: this is a very
	 * expensive operation and should only be used when the number of rows is
	 * small.
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumns() {
		resizeColumns(-1);
	}

	/**
	 * This method will resize all columns in the tree table view to ensure that
	 * the content of all cells will be completely visible. Note: this is a very
	 * expensive operation and should only be used with a small number of rows.
	 *
	 * @param maxRows
	 *            the maximum number of rows that will be considered for the
	 *            width calculations
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumns(int maxRows) {
		for (TreeTableColumn<R, ?> column : getTreeTable().getColumns()) {
			resizeColumn(column, maxRows);
		}
	}

	/**
	 * This method will resize the given column in the tree table view to ensure
	 * that the content of the column cells will be completely visible. Note:
	 * this is a very expensive operation and should only be used when the
	 * number of rows is small.
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumn(TreeTableColumn<R, ?> column) {
		resizeColumn(column, -1);
	}

	/**
	 * This method will resize the given column in the tree table view to ensure
	 * that the content of the column cells will be completely visible. Note:
	 * this is a very expensive operation and should only be used when the
	 * number of rows is small.
	 *
	 * @see #resizeColumn(TreeTableColumn, int)
	 */
	public final void resizeColumn(TreeTableColumn<R, ?> tc, int maxRows) {
		final TreeTableColumn col = tc;

		List<?> items = getItems();
		if (items == null || items.isEmpty()) {
			return;
		}

		Callback cellFactory = tc.getCellFactory();
		if (cellFactory == null) {
			return;
		}

		TreeTableCell<R, ?> cell = (TreeTableCell<R, ?>) cellFactory.call(tc);
		if (cell == null) {
			return;
		}

		// set this property to tell the TableCell we want to know its actual
		// preferred width, not the width of the associated TableColumnBase
		cell.getProperties().put("deferToParentPrefWidth", Boolean.TRUE); //$NON-NLS-1$

		// determine cell padding
		double padding = 10;
		Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
		if (n instanceof Region) {
			Region r = (Region) n;
			padding = r.snappedLeftInset() + r.snappedRightInset();
		}

		TreeTableRow<R> treeTableRow = new TreeTableRow<>();
		treeTableRow.updateTreeTableView(treeTableView);

		int rows = maxRows == -1 ? items.size()
				: Math.min(items.size(), maxRows);
		double maxWidth = 0;
		for (int row = 0; row < rows; row++) {
			treeTableRow.updateIndex(row);
			treeTableRow.updateTreeItem(treeTableView.getTreeItem(row));

			cell.updateTreeTableColumn(col);
			cell.updateTreeTableView(treeTableView);
			cell.updateTreeTableRow(treeTableRow);
			cell.updateIndex(row);

			if ((cell.getText() != null && !cell.getText().isEmpty())
					|| cell.getGraphic() != null) {
				getChildren().add(cell);
				cell.impl_processCSS(false);

				double w = cell.prefWidth(-1);

				maxWidth = Math.max(maxWidth, w);
				getChildren().remove(cell);
			}
		}

		// dispose of the cell to prevent it retaining listeners (see RT-31015)
		cell.updateIndex(-1);

		// RT-23486
		double widthMax = maxWidth + padding;
		if (treeTableView
				.getColumnResizePolicy() == TreeTableView.CONSTRAINED_RESIZE_POLICY) {
			widthMax = Math.max(widthMax, tc.getWidth());
		}

		tc.impl_setWidth(widthMax);
	}

JavaFX Tip 21: Animate!

When developers try to sell the switch from Swing to JavaFX to their superiors they often say that the user interfaces will look better and more modern. However, to really deliver on this promise I believe that one can not just rely on the improved look and feel of the built-in controls of JavaFX but that some extra effort has to be made to make the UI really stand out and to make everyone say “yeah, this is really an improvement”.

One of the things that can be done to spice up the UI is to animate some of its elements, e.g. fade in / out, slide in / out.

In CalendarFX one of the places where I use animation is the red horizontal line that marks the current time.

Bildschirmfoto 2015-06-19 um 19.02.43

This line is only shown when the user is looking at the current day (“today”). So the line’s visibility has to be toggled, depending on the current date. When this happens the line does not just appear or disappear but it fades in or out. The following video shows this feature in action:

(For some reason the “red” line shows up in “black” in this quicktime screen capture, but the important thing can still be seen.)

The line softly appears or disappears with a very subtle fade-in / fade-out animation. This kind of animation is very well supported by JavaFX via its observable properties and the FadeTransition. CalendarFX manages the visibility of the line in a property called showCurrentTimeMarker. When this value changes the method updateTimeMarkerVisibility will be called.

	view.showCurrentTimeMarkerProperty()
                  .addListener(it -> updateTimeMarkerVisibility());

The update method first determines the current opacity of the time marker (1 if visible, 0 if not visible) and then uses the fade transition to change the start value to the new end value.

private Line currentTimMarker;
private Circle currentTimeCircle;

private void updateTimeMarkerVisibility() {
	double lineOpacity =
           getSkinnable().isShowCurrentTimeMarker() ? 1 : 0;
	FadeTransition lineTransition =
           new FadeTransition(Duration.millis(600),
                                   currentTimeMarker);
	lineTransition.setToValue(lineOpacity);
	lineTransition.play();

	double circleOpacity =
           getSkinnable().isShowCurrentTimeTodayMarker() ? 1 : 0;
	FadeTransition circleTransition =
           new FadeTransition(Duration.millis(600),
                                   currentTimeCircle);
	circleTransition.setToValue(circleOpacity);
	circleTransition.play();
}

This is really all it takes to add some spice to your user interface but it will help you to make the little difference that will make people enjoy working with your application (and they will not even know why).

JavaFX Tip 20: A lot to show? Use Canvas!

There seem to be two kinds of JavaFX applications: the first one is using a scene graph with nodes and CSS styling, and the second one is using a single canvas. However, it is perfectly legal to mix these two approaches. Especially when your application has to show a lot of detailed information where you would easily end up creating thousands and thousands of nodes. Even though the overall performance of JavaFX is fantastic you will most likely bring your system down to its knees when styling is required for all of these nodes (especially when styling is required over and over again because of the dynamic nature of your visualization).

For me it was an epiphany when I realized that the only way to guarantee high performance in FlexGanttFX was to use a ListView with each cell containing a canvas. Unfortunately the code of this framework is too complex to share it with you in a small blog, so I wrote a small example that illustrates the basic concepts. The image below shows the result when running the example. The data shown by the ListView covers the years of my life span with randomly generated values for each day of each year.

Bildschirmfoto 2015-06-15 um 19.25.50

The most important class is called CanvasCell. It is a specialized list view cell containing a label and a canvas. The label is used to display the year, the canvas is used to draw the chart.

import java.util.Collections;
import java.util.List;

import javafx.geometry.Pos;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;

public class CanvasCell extends ListCell<YearEntry> {

	private Label yearLabel;
	private ResizableCanvas canvas;

	public CanvasCell() {
		/*
		 * Important, otherwise we will keep seeing a horizontal scrollbar.
		 */
		setStyle("-fx-padding: 0px;");

		yearLabel = new Label();
		yearLabel
		  .setStyle("-fx-padding: 10px; -fx-font-size: 1.2em; -fx-font-weight: bold;");
		StackPane.setAlignment(yearLabel, Pos.TOP_LEFT);

		/*
		 * Create a resizable canvas and bind its width and height to the width
		 * and height of the table cell.
		 */
		canvas = new ResizableCanvas();
		canvas.widthProperty().bind(widthProperty());
		canvas.heightProperty().bind(heightProperty());

		StackPane pane = new StackPane();
		pane.getChildren().addAll(yearLabel, canvas);

		setGraphic(pane);
		setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
	}

	@Override
	protected void updateItem(YearEntry entry, boolean empty) {
		if (empty || entry == null) {
			yearLabel.setText("");
			canvas.setData(Collections.emptyList());
			canvas.draw();
		} else {
			yearLabel.setText(Integer.toString(entry.getYear()));
			canvas.setData(entry.getValues());
			canvas.draw();
		}
	}

	/*
	 * Canvas is normally not resizable but by overriding isResizable() and
	 * binding its width and height to the width and height of the cell it will
	 * automatically resize.
	 */
	class ResizableCanvas extends Canvas {

		private List<Double> data = Collections.emptyList();

		public ResizableCanvas() {

			/*
			 * Make sure the canvas draws its content again when its size
			 * changes.
			 */
			widthProperty().addListener(it -> draw());
			heightProperty().addListener(it -> draw());
		}

		@Override
		public boolean isResizable() {
			return true;
		}

		@Override
		public double prefWidth(double height) {
			return getWidth();
		}

		@Override
		public double prefHeight(double width) {
			return getHeight();
		}

		public void setData(List<Double> data) {
			this.data = data;
		}

		/*
		 * Draw a chart based on the data provided by the model.
		 */
		private void draw() {
			GraphicsContext gc = getGraphicsContext2D();
			gc.clearRect(0, 0, getWidth(), getHeight());

			Stop[] stops = new Stop[] { new Stop(0, Color.SKYBLUE),
					new Stop(1, Color.SKYBLUE.darker().darker()) };
			LinearGradient gradient = new LinearGradient(0, 0, 0, 300, false,
					CycleMethod.NO_CYCLE, stops);

			gc.setFill(gradient);

			double availableHeight = getHeight() * .8;
			double counter = 0;
			for (Double value : data) {
				double x = getWidth() / 365 * counter;
				double barHeight = availableHeight * value / 100;
				double barWidth = getWidth() / 365 + 1;
				gc.fillRect(x, getHeight() - barHeight, barWidth, barHeight);
				counter++;
			}
		}
	}
}

For the data we use a very simple class that stores the year and a list of values.

import java.util.ArrayList;
import java.util.List;


/**
 * Just some fake model object.
 */
public class YearEntry {

	private int year;

	public YearEntry(int year) {
		this.year = year;
	}

	public int getYear() {
		return year;
	}

	private List<Double> values = new ArrayList<>();

	/**
	 * Stores the values shown in the chart.
	 */
	public List<Double> getValues() {
		return values;
	}
}

And the following listing shows the main class.

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.stage.Stage;

public class CanvasApp extends Application {

	@Override
	public void start(Stage stage) throws Exception {

		/*
		 * Create some random data for my life span.
		 */
		ObservableList<YearEntry> data = 
			FXCollections.observableArrayList();
		for (int year = 1969; year < 2015; year++) {
			YearEntry entry = new YearEntry(year);
			for (int day = 0; day < 365; day++) {
				entry.getValues().add(Math.random() * 100);
			}
			data.add(entry);
		}

		ListView<YearEntry> listView = new ListView<>(data);
		listView.setCellFactory(param -> new CanvasCell());
		listView.setFixedCellSize(200);

		Scene scene = new Scene(listView);

		stage.setTitle("Canvas Cell");
		stage.setScene(scene);
		stage.setWidth(600);
		stage.setHeight(600);
		stage.show();
	}

	public static void main(String[] args) {
		launch(args);
	}
}

JavaFX Tip 19: Watch Your Skin

A common mistake that I do over and over again is that I put certain code into the skin class of a custom control while it should have been inside the control class itself.

I just noticed this again when going through the list of bugs filed for the PopOver control of ControlsFX. I contributed this control, hence I feel responsible for fixing those bugs (when time permits). One of the issues mentioned that it is hard or impossible to change the styling of the PopOver control. After some investigation I realised that I am adding a stylesheet to the root pane of the PopOver inside the skin class. This is working but it causes a problem when applications have already added their stylesheets when the PopOver is created. The PopOver stylesheet will then override the application’s stylesheet and only because it is added too late.

The only way to actually style a PopOver is to listen to the skin property and wait for the skin to be set. Once set a stylesheet can be added to its children. Not a very elegant solution.

In case of the PopOver control I do have a very good excuse for adding the stylesheet inside the skin class. PopOver extends PopupControl and you can not add a stylesheet to it. PopupControl and its superclass PopupWindow inherit their styling from the owning window. However, what I could have done is to create a root pane inside the PopOver control, add a stylesheet to it and make it accessible via a getter method. Then the skin could have retrieved the root pane from the control and do whatever it needs to do with it so that it becomes a PopOver.

To summarize: do not add anything to your control inside the skin of the control if you want applications to be able to change it because the skin will most likely be set after applications have configured the control, hence overriding it. Typical examples are:

  • styling: you can set style classes but do not add stylesheets
  • events: any event handler you “set” in the skin will clear all previously handlers of the same type, e.g. control.setOnMouseClicked(…) will remove all mouse click event handlers that were “added” by the application via control.addEventHandler(MouseEvent.MOUSE_CLICKED, …)

JavaFX Tip 18: Path Clipping

I recently noticed that the PopOver control, which I committed to the ControlsFX project, does not properly clip its content. It became obvious when I was working on the accordion popover for the FlexCalendarFX framework. Whenever the last titled pane was expanded the bottom corners were no longer rounded but square. After placing a red rectangle as content to the titled pane it became clear to me that I forgot to add clipping. The following picture shows the problem.Bildschirmfoto 2015-02-18 um 12.20.09

Normally clipping in JavaFX is quite easy. All it takes is an additional node and a call to setClip(node). However, normally this clip is a simple shape, like a rectangle. In the PopOver case the clip had to be a path, just like the original path that was used for the shape of the PopOver. Why a path? Because the popover, when “attached” to an owner, also features an arrow pointing at the owner. See screenshot below.

Bildschirmfoto 2015-02-18 um 12.38.53

So the good thing was that the original path gets constructed based on a list of path elements. These are not nodes and can be reused for a second path. When I tried this the result was a PopOver that only consisted of a border with no content at all.

Bildschirmfoto 2015-02-18 um 12.25.23

The reason for this was the fact that the path was not filled. Once I set a fill on the clip path the result was what I was aiming for.

Bildschirmfoto 2015-02-18 um 12.25.40

Now the PopOver control clipped its content correctly. The image below shows the final result.

Bildschirmfoto 2015-02-18 um 12.19.55

Some might say that this is just a minor detail and they are right, but it is this attention to detail that makes an application stand out and look professional.

The image below shows how the PopOver is used within FlexCalendarFX.

Bildschirmfoto 2015-02-17 um 15.00.39

JavaFX Tip 17: Animated Workbench Layout with AnchorPane

I recently had to implement a layout for an application where the menu area and the status area could be hidden or shown with a slide-in / slide-out animation based on whether the user was logged in or not. The following video shows the the layout in action:

Update: a new video shows even better how the panes slide in and out.

In the past I probably would have implemented this kind of behavior with a custom control and custom layout code (as in “override layoutChildren() method in skin”). But this time my setup was different because I was using afterburner.fx from Adam Bien and now I had FXML and a controller class.

So what do do? I decided to try my luck with an anchor pane and to update the constraints on the stack panes via a timeline instance. Constraints are stored in the observable properties map of the stack panes. Whenever these constraints change, a layout of the anchor pane is requested automatically. If this happens without any flickering then we end up with a nice smooth animation. By the way, coming from Swing, I always expect flickering, but it normally doesn’t happen with JavaFX.

I ended up writing the following controller class managing the anchor pane and its children stack panes. Please notice the little trick with the intermediate properties menuPaneLocation and bottomPaneLocation. They are required because the animation timeline works with properties. So it updates these properties and whenever they change new anchor pane constraints are applied.

import static javafx.scene.layout.AnchorPane.setBottomAnchor;
import static javafx.scene.layout.AnchorPane.setLeftAnchor;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;&lt;/code&gt;

/**
 * This presenter covers the top-level layout concepts of the workbench.
 */
public class WorkbenchPresenter {

@FXML
private StackPane topPane;

@FXML
private StackPane menuPane;

@FXML
private StackPane centerPane;

@FXML
private StackPane bottomPane;

public WorkbenchPresenter() {
}

private final BooleanProperty showMenuPane = new SimpleBooleanProperty(this, &quot;showMenuPane&quot;, true);

public final boolean isShowMenuPane() {
    return showMenuPane.get();
}

public final void setShowMenuPane(boolean showMenu) {
    showMenuPane.set(showMenu);
}

/**
* Returns the property used to control the visibility of the menu panel.
* When the value of this property changes to false then the menu panel will
* slide out to the left).
*
* @return the property used to control the menu panel
*/
public final BooleanProperty showMenuPaneProperty() {
    return showMenuPane;
}

private final BooleanProperty showBottomPane = new SimpleBooleanProperty(this, &quot;showBottomPane&quot;, true);

public final boolean isShowBottomPane() {
    return showBottomPane.get();
}

public final void setShowBottomPane(boolean showBottom) {
    showBottomPane.set(showBottom);
}

/**
* Returns the property used to control the visibility of the bottom panel.
* When the value of this property changes to false then the bottom panel
* will slide out to the left).
*
* @return the property used to control the bottom panel
*/
public final BooleanProperty showBottomPaneProperty() {
    return showBottomPane;
}

public final void initialize() {
    menuPaneLocation.addListener(it -&gt; updateMenuPaneAnchors());
    bottomPaneLocation.addListener(it -&gt; updateBottomPaneAnchors());

    showMenuPaneProperty().addListener(it -&gt; animateMenuPane());
    showBottomPaneProperty().addListener(it -&gt; animateBottomPane());

    menuPane.setOnMouseClicked(evt -&gt; setShowMenuPane(false));

    centerPane.setOnMouseClicked(evt -&gt; {
        setShowMenuPane(true);
        setShowBottomPane(true);
    });

    bottomPane.setOnMouseClicked(evt -&gt; setShowBottomPane(false));
}

/*
 * The updateMenu/BottomPaneAnchors methods get called whenever the value of
 * menuPaneLocation or bottomPaneLocation changes. Setting anchor pane
 * constraints will automatically trigger a relayout of the anchor pane
 * children.
 */

private void updateMenuPaneAnchors() {
    setLeftAnchor(menuPane, getMenuPaneLocation());
    setLeftAnchor(centerPane, getMenuPaneLocation() + menuPane.getWidth());
}

private void updateBottomPaneAnchors() {
    setBottomAnchor(bottomPane, getBottomPaneLocation());
    setBottomAnchor(centerPane, 
           getBottomPaneLocation() + bottomPane.getHeight());
    setBottomAnchor(menuPane,
           getBottomPaneLocation() + bottomPane.getHeight());
}

/*
* Starts the animation for the menu pane.
*/
private void animateMenuPane() {
    if (isShowMenuPane()) {
        slideMenuPane(0);
    } else {
        slideMenuPane(-menuPane.prefWidth(-1));
    }
}

/*
* Starts the animation for the bottom pane.
*/
private void animateBottomPane() {
    if (isShowBottomPane()) {
        slideBottomPane(0);
    } else {
        slideBottomPane(-bottomPane.prefHeight(-1));
    }
}

/*
 * The animations are using the JavaFX timeline concept. The timeline updates
 * properties. In this case we have to introduce our own properties further
 * below (menuPaneLocation, bottomPaneLocation) because ultimately we need to
 * update layout constraints, which are not properties. So this is a little
 * work-around.
 */

private void slideMenuPane(double toX) {
    KeyValue keyValue = new KeyValue(menuPaneLocation, toX);
    KeyFrame keyFrame = new KeyFrame(Duration.millis(300), keyValue);
    Timeline timeline = new Timeline(keyFrame);
    timeline.play();
}

private void slideBottomPane(double toY) {
    KeyValue keyValue = new KeyValue(bottomPaneLocation, toY);
    KeyFrame keyFrame = new KeyFrame(Duration.millis(300), keyValue);
    Timeline timeline = new Timeline(keyFrame);
    timeline.play();
}

private DoubleProperty menuPaneLocation = new SimpleDoubleProperty(this, &quot;menuPaneLocation&quot;);

private double getMenuPaneLocation() {
    return menuPaneLocation.get();
}

private DoubleProperty bottomPaneLocation = new SimpleDoubleProperty(this, &quot;bottomPaneLocation&quot;);

private double getBottomPaneLocation() {
    return bottomPaneLocation.get();
}
}

The following is the FXML that was required for this to work:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;

&lt;?import java.lang.*?&gt;
&lt;?import javafx.scene.layout.*?&gt;

&lt;AnchorPane maxHeight=&quot;-Infinity&quot; maxWidth=&quot;-Infinity&quot; minHeight=&quot;-Infinity&quot; minWidth=&quot;-Infinity&quot; prefHeight=&quot;400.0&quot; prefWidth=&quot;600.0&quot; xmlns=&quot;http://javafx.com/javafx/8&quot; xmlns:fx=&quot;http://javafx.com/fxml/1&quot; fx:controller=&quot;com.workbench.WorkbenchPresenter&quot;&gt;
   &lt;children&gt;
      &lt;StackPane fx:id=&quot;bottomPane&quot; layoutX=&quot;-4.0&quot; layoutY=&quot;356.0&quot; prefHeight=&quot;40.0&quot; AnchorPane.bottomAnchor=&quot;0.0&quot; AnchorPane.leftAnchor=&quot;0.0&quot; AnchorPane.rightAnchor=&quot;0.0&quot; /&gt;
      &lt;StackPane fx:id=&quot;menuPane&quot; layoutY=&quot;28.0&quot; prefWidth=&quot;200.0&quot; AnchorPane.bottomAnchor=&quot;40.0&quot; AnchorPane.leftAnchor=&quot;0.0&quot; AnchorPane.topAnchor=&quot;40.0&quot; /&gt;
      &lt;StackPane fx:id=&quot;topPane&quot; prefHeight=&quot;40.0&quot; AnchorPane.leftAnchor=&quot;0.0&quot; AnchorPane.rightAnchor=&quot;0.0&quot; AnchorPane.topAnchor=&quot;0.0&quot; /&gt;
      &lt;StackPane fx:id=&quot;centerPane&quot; layoutX=&quot;72.0&quot; layoutY=&quot;44.0&quot; AnchorPane.bottomAnchor=&quot;40.0&quot; AnchorPane.leftAnchor=&quot;200.0&quot; AnchorPane.rightAnchor=&quot;0.0&quot; AnchorPane.topAnchor=&quot;40.0&quot; /&gt;
   &lt;/children&gt;
&lt;/AnchorPane&gt;

JavaFX Tip 16: Undecorated & Transparent Stages

Last week I had some fun playing around with the “Undecorator” classes from Arnaud Nouard. I was looking into it because I was thinking about writing a JavaFX showcase application that integrates / lists the various resources available for JavaFX development. My goal for this application is to come up with something very slick and sexy, something unconventional, something with a lot of animations and cool effects. So I started by taking over full control of the appearance of the stage by using “Undecorator”.

With the apperance of Yosemite transparent stages / windows are the way to go so I wanted that, too. I modified Arnaud’s CSS files to make the window transparent and then added a pane that contains 12 large icons for a menu.

The icons are animated when the user clicks on one of them. They shrink and are relocated to the toolbar area at the top of the window. This is done via the standard Timeline class of JavaFX. The key values added to the timeline modify the layout x and y coordinates of the icons based on target bounds that are calculated whenever the width or height of the stage changes.

The result can be seen in the video below.

 

 

 

 

JavaFX Tip 15: ListView Autoscrolling

I recently had to implement autoscrolling functionality for FlexGanttFX and thought that my solution might be useful for others. You find the basic concepts of it in the listing below. The main idea is that a background thread is used to adjust the pixel location of the virtual flow node used by the list view. The thread starts when a drag over is detected “close” to the top or bottom edges. “Close” is defined by a proximity variable.

This code can obviously be improved by using a property for the proximity value and the types “Task” and “Service” for the threading work.

package com.dlsc;

import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Region;

/*
 * Yes, unfortunately we need to use private API for this.
 */
import com.sun.javafx.scene.control.skin.VirtualFlow;

public class AutoscrollListView<T> extends ListView<T> {

	final double proximity = 20;

	public AutoscrollListView() {
		addEventFilter(MouseEvent.DRAG_DETECTED,
                      evt -> startDrag());
		addEventFilter(DragEvent.DRAG_OVER,
                      evt -> autoscrollIfNeeded(evt));
		addEventFilter(DragEvent.DRAG_EXITED,
                      evt -> stopAutoScrollIfNeeded(evt));
		addEventFilter(DragEvent.DRAG_DROPPED,
                      evt -> stopAutoScrollIfNeeded(evt));
		addEventFilter(DragEvent.DRAG_DONE,
                      evt -> stopAutoScrollIfNeeded(evt));
	}

	private void startDrag() {
		Dragboard db = startDragAndDrop(TransferMode.MOVE);
		ClipboardContent content = new ClipboardContent();

		/*
		 * We have to add some content, otherwise drag over
                 * will not be called.
		 */
		content.putString("dummy");
		db.setContent(content);
	}

	private void autoscrollIfNeeded(DragEvent evt) {
		evt.acceptTransferModes(TransferMode.ANY);

		/*
		 * Determine the "hot" region that will trigger automatic scrolling.
		 * Ideally we use the clipped container of the list view skin but when
		 * the rows are empty the dimensions of the clipped container will be
		 * 0x0. In this case we try to use the virtual flow.
		 */
		Region hotRegion = getClippedContainer();
		if (hotRegion.getBoundsInLocal().getWidth() < 1) {
			hotRegion = this;
			if (hotRegion.getBoundsInLocal().getWidth() < 1) {
				stopAutoScrollIfNeeded(evt);
				return;
			}
		}

		double yOffset = 0;

		// y offset

		double delta = evt.getSceneY() -
                                  hotRegion.localToScene(0, 0).getY();
		if (delta < proximity) {
			yOffset = -(proximity - delta);
		}

		delta = hotRegion.localToScene(0, 0).getY() +
                                  hotRegion.getHeight() -
				  evt.getSceneY();
		if (delta < proximity) {
			yOffset = proximity - delta;
		}

		if (yOffset != 0) {
			autoscroll(yOffset);
		} else {
			stopAutoScrollIfNeeded(evt);
		}
	}

	private VirtualFlow<?> getVirtualFlow() {
		return (VirtualFlow<?>) lookup("VirtualFlow");
	}

	private Region getClippedContainer() {

		/*
		 * Safest way to find the clipped container. lookup() does not work at
		 * all.
		 */
		for (Node child :
                             getVirtualFlow().getChildrenUnmodifiable()) {
			if (child.getStyleClass().
                                       contains("clipped-container")) {
				return (Region) child;
			}
		}

		return null;
	}

	class ScrollThread extends Thread {
		private boolean running = true;
		private double yOffset;

		public ScrollThread() {
			super("Autoscrolling List View");
			setDaemon(true);
		}

		@Override
		public void run() {

			/*
			 * Some initial delay, especially useful when
                         * dragging something in from the outside.
			 */

			try {
				Thread.sleep(300);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}

			while (running) {

				Platform.runLater(() -> {
					scrollY();
				});

				try {
					sleep(15);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		private void scrollY() {
			VirtualFlow<?> flow = getVirtualFlow();
			flow.adjustPixels(yOffset);
		}

		public void stopRunning() {
			this.running = false;
		}

		public void setDelta(double yOffset) {
			this.yOffset = yOffset;
		}
	}

	private ScrollThread scrollThread;

	private void autoscroll(double yOffset) {
		if (scrollThread == null) {
			scrollThread = new ScrollThread();
			scrollThread.start();
		}

		scrollThread.setDelta(yOffset);
	}

	private void stopAutoScrollIfNeeded(DragEvent evt) {
		if (scrollThread != null) {
			scrollThread.stopRunning();
			scrollThread = null;
		}
	}
}