Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Friday, November 21, 2014

A JavaFX Image Map with SceneBuilder

An image map is an image supplemented with hotspots, areas of the screen capable of handling events.  This lets the user interact with a screen using the image as a guide.  Consider a geographical map of the US states where a mouse press allows selection of a state.

In JavaFX, you can create an image map in SceneBuilder using an ImageView containing an Image overlaid with one or more Rectangles.  The Rectangles are transparent, allowing the image to show, and are the receivers of events.

This screenshot of SceneBuilder shows an ImageView containing a JPEG of a bass guitar neck.  A blue Rectangle with partial transparency is dragged onto the screen, positioned and sized to an area of interest.

An ImageView with a Rectangle Overlay
The Rectangle is given an fx:id and associated with an @FXML member variable.  Additionally, I've written three event handlers to handle mouse events.  See the following screenshot of the SceneBuilder Inspector, Code View.

Events Associated with the Rectangle Hotspot
In this example, I create 40 rectangles for each of the bass guitar notes I want to make selectable.  Working in SceneBuilder is very productive because I can drag-and-drop the 40 rectangles on the screen, select all of them, then bring down their opacity and make the mouse event handler selections all together.  That is, I don't have to interact with each of the 40 rectangles all at once.

Event Handlers

Each Rectangle is attached to 3 mouse event handlers: pressNote (On Mouse Clicked), hoverOverNote (On Mouse Entered), leaveNote (On Mouse Exited).  I use the event.getSource() method to retrieve the object that was clicked, entered or exited.  For the entered and exited events, I tweak the opacity to give the user a guide as to what he or she is selecting.  This is done by adjusting the Opacity property.   I could set or re-set the value directly, but I'm providing an animation of the property to make the interaction more modern.

@FXML
public void hoverOverNote(MouseEvent evt) {
     FadeTransition ft = new FadeTransition(Duration.millis(200), (Rectangle)evt.getSource());
    ft.setFromValue(0.0);
    ft.setToValue(0.3);
 
    ft.play();      
}
@FXML
public void leaveNote(MouseEvent evt) {
     FadeTransition ft = new FadeTransition(Duration.millis(200), (Rectangle)evt.getSource());
    ft.setFromValue(0.3);
    ft.setToValue(0.0);
 
    ft.play();      
}

I'll describe the pressNote() handler in the next section.

Model

An image map is usually backed up with a model.  The map translates the graphical object (a JavaFX Rectangle) to something in the application domain.  In this case, a Rectangle is translated into a Note object.  A Note object contains a set of fields to describe the tone and position in terms of the bass guitar.

The getters, setters, and constructors are omitted for brevity from the Note class.

public class Note {

private Rectangle noteNode;
private String noteValue;
private String enharmonicValue;
private String onString;
}

The Notes are added to a java.util.Map called "noteMap".  Although the class itself contains a reference to the noteNode, I use the noteNode (a JavaFX Rectangle object) as the key for a convenient lookup.

The map must be set in the @FXML initialize() method so that the object references for the Rectangles are valid.

private Map<Rectangle, Note> noteMap;
@FXML
public void initialize() {
noteMap = new LinkedHashMap<Rectangle, Note>();
noteMap.put(rect_e_c, new Note(rect_e_c, "C", null, "E"));
        }

Finally, the pressNote() method will unpack the selected Rectangle using getSource() and there is an easy lookup for the Note object.  The method can continue with the intended processing, say playing a note or recording the selected value.

@FXML
public void pressNote(MouseEvent evt) {
Note n = noteMap.get( evt.getSource() );
if( log.isDebugEnabled() ) {
log.debug("[PRESS NOTE] note=" + n.getNoteValue() + " was pressed");
}
}

SceneBuilder provides a great way to create JavaFX image maps.  Simply create JavaFX shape objects (ex, Rectangle) over an ImageView.  Once all the objects are defined, select them all and assign event handlers and set the opacity to zero.  This beats the old days of the web where an image map required the developer to come up with a list of coordinates through trial-and-error.


No comments:

Post a Comment