JavaFX Empty Path Issue

With rendering glitches

WITHOUT rendering glitches

Some time ago I stumbled upon an issue with JavaFX that was generating a lot of rendering glitches, namely: objects disappearing or partially disappearing. As can be seen it the pictures. Thankfully, I was able to isolate the source of the problem and also an easy workaround.

This issue happens when you have an empty Path object on your scenegraph, that is a Path without any elements added to it. The solution: just add paths which are not empty to the scenegraph, wait till you populate the path and then only then add it to the scenegraph. Another option is to pass in the following to the JVM “-Dprism.dirtyopts=false”, this will turn off the dirty region optimization and force a complete repaint of the scene each time the scene changes.

It has been fixed on JavaFX 2.1: : http://javafx-jira.kenai.com/browse/RT-18080. However till then you’ll have to rely on this easy workaround.

PopupMenu in JavaFX 2

Creating Popup Menus

To create a Popupmenu in JavaFX you can use the ContextMenu class. You add MenuItems to it and can also create visual separators using SeparatorMenuItem.

In the example below I’ve opted to subclass ContextMenu and add the MenuItems on its constructor.

public class AnimationPopupMenu extends ContextMenu{
public AnimationPopupMenu()
{
(...)
  getItems().addAll(
    MenuItemBuilder.create()
    .text(ADD_PARTICLE)
    .graphic(createIcon(...))
    .onAction(new EventHandler() {
    @Override
    public void handle(ActionEvent actionEvent) {
      // some code that gets called when the user clicks the menu item
    }
    })
    .build(),

(...)
    SeparatorMenuItemBuilder.create().build(),
    MenuItemBuilder.create()
    .text(ADD_DISTANCE_MEASURER)
    .onAction(new EventHandler() {
    @Override
    public void handle(ActionEvent actionEvent) {
      // Some code that will get called when the user clicks the menu item
    }
  })
  .graphic(createIcon(...))
  .build(),
(...)
  );
}

  • Line 5: I get the Collection of children of the ContextMenu and call addAll to add the MenuItems;
  • Line 6: Uses the MenuItem builder do create a MenuItem;
  • Line 7: Passes in the text of the menu item. Variable ADD_PARTICLE is equal to “Add Particle”;
  • Line 8: Calls graphic which receives the menu item icon returned by createIcon:
    
        ImageView createIcon(URL iconURL)
        {
            return ImageViewBuilder.create()
                                   .image(new Image(iconURL.toString()))
                                   .build();
        }
    
  • Line 9: onAction receives the event handler which will be called when the user clicks the menu item;
  • Line15: Finally the MenuItem gets created by executing build() on the MenuItemBuilder class;
  • Line18: Creates The Separator which you can see on the figure on the start of this post. It’s the dotted line between “Add Origin” and “Add Distance Measurer”;
  • The other lines of code just repeat the same process to create the rest of the menu items.


Using JavaFX Popup Menus inside JFXPanel

If your embeding a JavaFX scene in a Swing app you’ll have to do some extra steps manually, if you don’t there won’t be hover animations on the popup menu and it won’t get dismissed automatically when the user clicks outside of it. There is a fix targeted at JavaFX 3.0 for this –  http://javafx-jira.kenai.com/browse/RT-14899

First you’ll have to request the focus on the javafx container so that the popup gets hover animations and when you click outside your app window it gets dismissed. In my case I pass a reference to the javafx swing container on the construtor of the popup menu, then I’ve overwritten the show method of ContextMenu so as to request the focus on the swing container before actually showing the popup:

public void show(Node anchor, MouseEvent event)
{
  wrapper.requestFocusInWindow();
  super.show(anchor, event.getScreenX(), event.getScreenY());
}

And lastly you’ll have to also dismiss the popup when the user clicks inside the javafx scene but outside of the popup by calling hide().

I almost forgot.. thanks to Martin Sladecek (Oracle JavaFX team) for giving me some pointers.

Migrating from javafx 1.3 to javafx 2.0

Some days ago I finished migrating the source code of Modellus from javafx 1.3 script to Javafx 2.0 java language. So I thought it would be nice to write about what I’ve learned in the process.

I’d like to point out that if you want to keep using javafx script in javafx 2.0 you can use Visage: http://code.google.com/p/visage/

  1. CustomNode class doesn’t exist any more. Extend Group or Region to create “custom nodes”.
  2. No more blocksMouse.
    In javafx 2.0 mouse events are only received by the top most node.
    There is also a new method on Node: setMouseTransparent(boolean). Mouse events on node with mouseTransparent set to true will be ignored and captured by the topmost node below.
  3. Use properties to bind values.
    Javafx 2.0 has a set of classes you can use to bind values to each other. For each primitive type there is a class – SimpleBooleanProperty, SimpleDoubleProperty, etc, and for reference types you use an Object Property instance, for instance if you want to bind colors you can use SimpleObjectProperty<Color>.
  4. Not all variables from the API are “bindable”.
    In Javafx 1.3 script you could bind to any variable of the API. In javafx 2.0 java language, that means that all variables from the API would need to be available as propertys. But that is not the case, for instance Bounds, LinearGradient, Stop are examples of classes that do not have propertys, so you can’t bind directly to their fields.  In this situations you’ll need to use other methods like low-level binding.

    For example suppose you wanted to bind a variable to the width of the layout bounds of a node. Since the field width of Bounds is not available as a property you would have to do something like this:

    In Javafx script:

    float nameLabelXPosition = bind - nameLabel.layoutBounds.width / 2;
    

    In Javafx2.0 java language:

    nameLabelXPosition.bind(new DoubleBinding() {
        {
            super.bind(nameLabel.layoutBoundsProperty());
        }
    
        @Override
        protected double computeValue() {
            return nameLabel.getLayoutBounds().getWidth() / 2;
        }
    });
    
  5. When you used javafx script initiliazer blocks you can now use javafx builders.
    However in javafx script you could use binding in the initializer block, on java you can’t do that with builders. Only in JavaFX 3.0 (Lombard) will you be able to do that:http://javafx-jira.kenai.com/browse/RT-13680. So, whenever you used binding on javafx script initializer blocks you can’t use builders in java javafx 2.0.
  6. No more language level support for sequences on javafx 2.0 java.
    Wherever you used sequences you now will use ObservableLists. To create ObservableLists you can use FXColections creator methods, there you’ll find all sorts of methods to create ObservableLists, even empty ones.
    Sequences present on the API have been converted to ObservableLists. If, for instance, you want to insert a node on a Group you need to get it’s children ObservableList and than call the method add on it. Like so: .getChildren().add(Node)
  7. No more function types.
    Since only on java8 will there be support for Closures, the Oracle team has relied on the use of SAM types instead. That is a Class with only a single abstract method that you’ll have to override (Single Abstract Method). You can use the same strategy as Oracle and write SAM types wherever you used function objects.
  8. No more triggers.
    Replace triggers with change listeners. You can assign a change listener to a property which is the same as assign a trigger on javafx script.
  9. No more variable overrides on subclasses.
    For these one you won’t have a substitute on java, the best thing you can do is reassign a value to the variable on a subclass. But it is not the same, since overriding variables, assigned values before initializer blocks of superclass were invoked.

For further reading on this topic checkout:
http://weblogs.java.net/blog/opinali/archive/2011/05/28/javafx-20-beta-first-impressions

If you have any more valuable tips on this topic which I don’t cover please add them in the comments and I’ll insert them in the post.

Convert to and from BufferedImage in javafx 2.0

While porting Modellus from javafx1.3 using  javafx script to javafx2.0 using java language I ended up creating a class with a few helper methods for converting swing objects to and from javafx objects:

  • Converting from java.awt.color  to javafx.scene.paint.Color and vice versa
  • Converting from java.awt.image.BufferedImage to javafx.scene.image.Image and vice versa

Nothing too fancy but I thought it might come in handy to other people so here’s the code:

public class SwingUtils {

    public static java.awt.Color toAWTColor(javafx.scene.paint.Color fxColor)
    {
        return new java.awt.Color((float)fxColor.getRed(), (float)fxColor.getGreen(), (float)fxColor.getBlue(), (float)fxColor.getOpacity());
    }

    public static javafx.scene.paint.Color fromAWTColor(java.awt.Color awtColor)
    {
        return ColorBuilder.create()
                            .red(awtColor.getRed() / 255.0)
                            .green(awtColor.getGreen() / 255.0)
                            .blue(awtColor.getBlue() / 255.0).build();
    }

    // There is a problem with this implementation: transparent pixels on the BufferedImage aren't converted to transparent pixels on the fxImage.
    public static javafx.scene.image.Image convertToFxImage(java.awt.image.BufferedImage awtImage) {
    	if (Image.impl_isExternalFormatSupported(BufferedImage.class)) {
    		return javafx.scene.image.Image.impl_fromExternalImage(awtImage);
    	} else {
    		return null;
    	}
    }

    public static java.awt.image.BufferedImage convertToAwtImage(javafx.scene.image.Image fxImage) {
    	if (Image.impl_isExternalFormatSupported(BufferedImage.class)) {
    		java.awt.image.BufferedImage awtImage = new BufferedImage((int)fxImage.getWidth(), (int)fxImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
        	return (BufferedImage)fxImage.impl_toExternalImage(awtImage);
    	} else {
    		return null;
    	}
    }
}

This will probably be added on Lombard (next javafx version) as shown by this issue: http://javafx-jira.kenai.com/browse/RT-14038