Building GUI Applications With JavaFX

Lesson 8: Bringing Interactivity to GUI Elements


This section describes how to handle events in mobile and desktop applications by using the common profile of API. The application demonstrates an animated "Play-Pause" button that changes its appearance as you perform various mouse actions. Then, when you press or click the button, it fades. If you release the mouse button, its indicator changes from "Play" to "Pause." If you click the button again, the "Pause" indicator switches back to "Play." You can also drag and drop the button within the scene. Try the following applet to evaluate event handling. Press, release, and drag the button.


The following screen captures show all the possible button states.

All possible states of the button
Figure 1: States of the Button

Create a file with an .fx extension, for example Events.fx. Avoid using file names that match the names of existing classes, instance variables, or reserved words because this type of naming leads to errors during compilation. For more information about existing classes, variables, and reserved words, see JavaFX Script API and Learning the JavaFX Script Programming Language.

All button states are available in the following PNG images: play_onMouseExited.png, play_onMousePressed.png, stop_onMouseExited.png, and stop_onMousePressed.png. To use the images as scene objects, use a combination of the Image and ImageView classes.

  1. Add import statements for the Image and ImageView classes.

  2. Define four Image objects corresponding to different states of the button.

    When specifying the image url, you can set the URI of the resource or use the relative codebase. In this example, the image url is set by using the __DIR__ variable that indicates the directory where the image is located. By default it points to the current directory, so ensure that the images are located in the same directory as application-compiled classes. To run the application on the mobile emulator ensure that the images are packed into the application jar file along with the compiled classes.

  3. Define the image variable to store an image of the current button state, and set it to the initial state, playNormal.

  4. Define the button variable to the a scene object corresponding to the current state of the button, and bind it to the image variable.

  5. Specify the scene of the application and add the button to its content.

    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.Group;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;

    def playNormal = Image { url: "{__DIR__}play_onMouseExited.png"};
    def playPressed = Image { url: "{__DIR__}play_onMousePressed.png"};
    def stopNormal = Image { url: "{__DIR__}stop_onMouseExited.png"};
    def stopPressed = Image { url: "{__DIR__}stop_onMousePressed.png"};

    var image = playNormal;

    var button = ImageView {image: bind image}

    Stage {
    title: "Play Button"
    scene: Scene {
    width: 300
    height: 240
    content: Group {
    content: button
    }
    }
    }


Note: The button is added to the Group construct for further application enhancements. In your application you can add an ImageView object directly to the scene content..

This application handles three types of mouse events: mouse is pressed, mouse is released, and mouse is dragged. Each of these events is processed by a specific Node function. Because the ImageView class inherits all Node instance variables and functions, you can apply the onMousePressed, onMouseReleased, and onMouseDragged function to your button.

The press event happens when you press the button with a stylus or a mouse without releasing it. In this example, the press event changes the button's appearance.

To handle the mouse press event, perform the following steps.

  1. Define a Boolean variable named state to fix whether the button is in the Play or Stop mode. Set the true value so that the button will be in Play mode when the application is started.

  2. Define the X and Y variables to use them for calculating the button's position when processing the drag event.

  3. Use the if construction to check the mode of the button. If it is in Play mode, the image is set to playPressed, and stopPressed otherwise.

    var mode = true; //true for the Play mode,
    //false for the Stop mode

    var X: Number;
    var Y: Number;

    onMousePressed: function(event) {
    X = event.sceneX - event.node.translateX;
    Y = event.sceneY - event.node.translateY;
    image = if (mode){
    playPressed;
    } else {
    stopPressed;
    };
    }

As a result, the button changes its appearance when you press it depending on the mode of the button.

The release event occurs when you release a mouse pointer from the button. In this example, when you release the pointer from the button, it switches its mode.

To handle the mouse release event, use the following code:

onMouseReleased: function(event) {
if (mode){
image = stopNormal;
mode = false;
} else {
image = playNormal;
mode = true;
}
}

After an image is changed, the mode variable changes its value to the opposite. At this point, the application implements switching between the Play and Stop modes and changing the button appearance on the mouse press. The next section concerns dragging the button within the scene.

Unlike the mouse events mentioned in the previous sections, the onMouseDragged event does not change the button's appearance. This event enables you to alter the button position dragging it when the mouse is pressed. In this example, you cannot move the button beyond the bounds of the scene.

Use the following code fragment to handle the drag event:

onMouseDragged: function(event) {
if (event.sceneX - X <0) translatex =" 0;"> 300 - image.width){
event.node.translateX = 300 - image.width;
} else {
event.node.translateX = event.sceneX - X;
}
}
if (event.sceneY - Y <0) translatey =" 0;"> 240 - image.height){
event.node.translateY = 240 - image.height;
} else{
event.node.translateY = event.sceneY - Y;
}
}
}

Two if constructions are used to check whether the button has been dragged outside of the scene, and to set values for the translateX and translateY variables of the button. Consider an example when the drag event occurred at the point (320, 100). Suppose that the X was fixed as 5, and the Y was fixed as 10. Then the event.node.translateX - X = 320 - 5 = 315, while 300 - image.width = 300 -63 = 237. The value 315 is greater than 237, so the translateX variable will be set to 300 - image.width = 237, and the button will be placed at the right border of the scene. The translateY variable will be set to event.sceneY - Y = 100 - 10 = 90. Therefore, the button will be moved to the following position: translateX = 237, translateY = 90.

The following screen capture shows the application run on the Touch Phone emulator.

Event Handling on Mobile Devices
Figure 2: Event-Handling Application Run on the Mobile Emulator

You can find the complete code of this application in the EventsCommon.fx file. The next section discusses how to handle some additional desktop-specific events.

Desktop Profile

- Adding Tooltips
- Handling the Mouse-Enter Event
- Handling the Mouse-Exit Event

In this section, you will learn how to enhance the existing example by using additional features specific for desktop applications. Consider handling the mouse-enter and mouse-exit events by changing the button's appearance when the mouse pointer is on it. In addition, the modified application will show two variants of tooltips depending in the mode of the button. Try the following applet to evaluate event handling. Hover, press, release, and drag the button.



Two new images represent the button's state: play_onMouseEntered.png and stop_onMouseEntered.png. So you need to define two more Image variables.

def playHover = Image { url: "{__DIR__}play_onMouseEntered.png"};
def stopHover = Image { url: "{__DIR__}stop_onMouseEntered.png"};

Perform the following steps to create a textual tooltip and add it to the scene.

  1. Add import statements for the Text, Font, Timeline, and Color classes.

  2. Declare a Text object specifying the string to render, the color, and the position of the tooltip. Set the content variable depending on the current mode of the button. Set "Play Button" when the button is in Play mode, and "Stop Button" otherwise. Also bind the translateX and translateY variables to the corresponding variables of the button object.

  3. Add the tooltip object to the scene.

    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.Group;
    import javafx.animation.Timeline;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.paint.Color;
    import javafx.scene.text.Font;
    import javafx.scene.text.Text;

    var tooltip = Text {
    content: bind if (mode) "Play Button" else "Stop Button"
    translateX: bind button.translateX
    translateY: bind button.translateY + 80
    opacity: 0.0
    font: Font {
    size: 12
    name: "Tahoma"
    }
    fill: Color.BLACK
    };

    Stage {
    title: "Play Button"
    scene: Scene {
    fill: Color.WHITE
    width: 300
    height: 240
    content: Group {
    content: [button, tooltip]
    }
    }

This event happens when you position the mouse pointer in the button area. This event is controlled by the onMouseEntered function.

To handle the mouse enter event define the onMouseEntered function. Create a Timeline object to alter the opacity of tooltips so that they do not appear instantly as you hover the button, but gradually within five seconds. The following code fragment performs these tasks.

def appear = Timeline {
keyFrames: [
at(0s) {tooltip.opacity => 0.0},
at(0.5s) {tooltip.opacity => 1.0}
]
}

...

onMouseEntered: function(event) {
image = if (mode){
playHover;
} else {
stopHover
}
appear.rate = 1;
appear.play();
}

After you enter the button area with the mouse pointer, the playHover or stopHover image appears and an animation timeline starts adding the tooltip. For more information about the onMouseEntered function, see JavaFX Script API. For more information about the MouseEvent class, see JavaFX Script API. For more information about animation, see Creating Animated Objects.


Note: Because a new state was introduced, you need to apply the following code for the onMouseReleased function. When the mouse is released the button should be in a hovered state, not in the initial state.

onMouseReleased: function(event) {
if (mode){
image = stopHover;
mode = false;
} else {
image = playHover;
mode = true;
}
}


This type of event occurs when the mouse pointer exits the button area. The event is defined by the onMouseExited function.

To define the mouse-exit event, use the following code:

onMouseExited: function(event) {
image = if (mode){
playNormal;
} else {
stopNormal
}
appear.rate = -1;
appear.play();
}

When the mouse pointer exits the area defined by the graphic button, the button's appearance returns to its initial state. The tooltip disappears, because the rate variable of the animation timeline is set to -1 and the tooltip opacity alters from 1.0 to 0.0.

For more information about the onMouseExited function, see JavaFX Script API.

Here is the complete Events.fx file.

0 comments:

Post a Comment