Preview of What We Are Building
- Before we start working on our exercise is good idea to see the final demo running. This will give you a clear understanding of what you have to do. First, let's open the
ReceiverFX project. You can open a project in Netbeans by either clicking on the icon, by using the key combination Ctrl + Shift + O or by using the menu item under File:
Go to <lab_root>\ex-solution\exercise3\. Select ReceiverFX project and click on Open Project button.
- Before we run the solution, we need to be sure we are running the JavaFX code as a mobile application. For this, we need to verify that the
"Application Execution Model" is set to "Run in Mobile Emulator".
Right click to ReceiverFX Project and select Properties. On the "Project Properties" window click on Run. You will see all the configuration related to Run on your right hand side. Verify that the Application Execution Model is pointing to "Run in Mobile Emulator".
Because we are going to have two mobile projects running at the same time we need to use different emulators. (Only one emulator of the same kind can be running at a time.) For ReceiverFX Project we are going to use DefaultFxTouchPhone1, then verify that the selected Device is DefaultFxTouchPhone1. Click "OK" to confirm all the changes.
- Now you are ready to run the project. You can run the project by right clicking on
ReceiverFX Project, and choosing the "Run Project" option from the pop up menu.
The ReceiverFX application will display an animation of a snail moving along the screen. What is happening is that the application is just waiting for a message to arrive.
- Now let's open the
SenderFX project. You can find this project under <lab_root>\ex-solution\exercise3\.
- Again let's briefly if we are running this project as a mobile application. Right click to
SenderFX Project and select Properties. On the "Project Properties" window click on Run. You will see all the configuration related to Run on your right hand side. Verify that the Application Execution Model is pointing to "Run in Mobile Emulator". For this project we are going to use DefaultFxPhone1, then verify that the selected Device is DefaultFxPhone1. Click "OK" to confirm all the changes.
- Run
SenderFX project.
The SenderFX application is mainly designed for touch screens phones, so most of its functionality will be by using the Mouse click events. However a few things will work with the phone's keys. For example to move around the different options in the menu (without executing any of them) we use the keys:
Move around the menu, and see how the menu gets animated as you move around. Also notice the screen changes every time you select a different item in the menu.
The New Message screen will reflect the changes on the message's destination address and changes on the message's body. Let's see the application running:
- First notice that the receiver has a telephone number on top of it. For our example the number is 123456789
- Now by using the phone keys, move to the
To screen, and enter the destination phone: 123456789.
- Now move to
Body screen and enter any message you want.
- You can always preview your message by going back to the first screen,
New Message. Make your you move around with the phone's keys rather than clicking on the menu item. If you click on New menu item, you are actually creating a new message, but if you move with the phone keys you are just previewing the changes to the current message.
- Now go to
Send screen and either hitting the select key on the phone, or by clicking on the Send menu item. You will notice and animation showing that the message is leaving.
- Once the message leave, you will notice an incoming message on the receiver phone. An animation will be played and the message will be displayed.
Understanding the Java ME messaging API
Because the focus of this lab is JavaFX Mobile, we are just going to analyze a little bit the messaging part.
- Open the
ReceiverFX project under <lab_root>\exercises\exercise3\
- Expands all the project structure. We have 3 packages
receiverfx.connector: Contains the main Java ME Receiver class, in charge of the sms connection
receiverfx.tracking: Contains classes for managing messages and events notifications related to messages. The main classes contained in this package are: Msg, MsgEvent, MsgListener and MsgTracker
receiverfx.view: Package that will be populated with the JavaFX classes we will create in this exercise.
receiverfx.view.resources: Contains some images to be use in the project
- Let's verify some important information about our project. First we need to include any library that isn't included in the Java ME distribution. In our case we are going to be using WMA, so we need to verify that those libraries are included in the project.
- Right click on the project and go to
Properties.
- Click on Libraries and verify that on the right hand side you have the wma20.jar included in the library's list. If you need to use any other project, library, jar or folder, this is the place to include them.
- Another thing we want to check is that the JavaFX application is going to be run as JavaFX Mobile application. Under the same
Project Properties, select Run. Make sure you have Run in Mobile Emulator selected as your Application Execution Model.
Under Netbeans we can only have one instance of the different JavaFX emulators running at any time. Because we have two application we need to choose two different emulators for each application. We are going to use the DefaultFxTouchPhone1 for the Receiver project, as this application doesn't require much interaction from the user.
- Let's have a look at the Receiver class under receiverfx.connection project.
package receiverfx.connector;
import receiverfx.tracking.MsgTracker;
import javax.microedition.io.Connector;
import javax.wireless.messaging.Message;
import javax.wireless.messaging.MessageConnection;
import javax.wireless.messaging.TextMessage;
public class Receiver implements Runnable {
String smsPort = "50000";
MessageConnection smsconn = null;
Message msg = null;
String senderAddress;
Thread myThread = null;
private MsgTracker myTracker = new MsgTracker();
@Override
public void run() {
waitForMsg();
}
public MsgTracker getMyTracker(){
return myTracker;
}
public void waitForMsg() {
TextMessage myNewMsg;
if (smsconn == null) {
try {
smsconn = (MessageConnection)Connector.open("sms://:" + smsPort);
msg = smsconn.receive();
if (msg != null) {
if (msg instanceof TextMessage) {
myNewMsg = (TextMessage)msg;
myTracker.newMessageArrived(myNewMsg);
}
}
}catch(Exception e){}
}
}
public void startMyThread(){
myThread = new Thread(this);
myThread.start();
}
}
The Receiver class is a simple thread that opens a connection on port 5000 to wait for incoming messages. Once the message has arrived, we check that it's a TextMessage (for this particular application we only supports TextMessages). Finally we notify anyone tracking us that a new message has arrived.
- Now let's see the package
receiverfx.tracking.
Msg it's an encapsulation of message sender and content. This will be the only data we are going to use from TextMessages.
MsgEvent it encapsulate information about event messages. Each MsgEvent object will have a Msg and a description, specifying the type of events that was triggered for that particular message.
- MsgListener It's an interface that should be implemented by any class wanted to hear from MsgEvents.
MsgTracker. It's the class in charge of tracking messages, detecting when a new message has arrive, and notifying the listeners about it. It also keeps track of the MsgListeners.
- Open the
SenderFX project under <lab_root>\exercises\exercise3\
- Expand all the project structure
There are 3 packages:
connector: This package contains the Sender class, who will be in charge of making the connection, and sending a new message to the destination address.
keyboard. In this package we are going to design few JavaFX classes for creating keyboards. We are going to simulate a numeric keyboard and alphanumeric keyboard that responds to user events.
resources: Contain some images for creating our keyboards.
view: In this package we will have all the screen designs for our application
resources: Again some resources for our application.
- Let's now see in more detail our
Sender.java class:
package senderfx.connector;
import javax.microedition.io.Connector;
import javax.wireless.messaging.MessageConnection;
import javax.wireless.messaging.TextMessage;
public class Sender implements Runnable{
private String destinationAddr = "sms://123456789:50000";
private String msg;
MessageConnection smsconn = null;
Thread myThread = null;
@Override
public void run() {
sendSMS();
}
public void sendSMS(){
TextMessage txtmessage = null;
try{
smsconn = (MessageConnection)
Connector.open(destinationAddr);
txtmessage = (TextMessage)
smsconn.newMessage(MessageConnection.TEXT_MESSAGE);
txtmessage.setAddress(destinationAddr);
txtmessage.setPayloadText(msg);
smsconn.send(txtmessage);
if (smsconn != null) {
smsconn.close();
}
}catch(Exception e){}
}
public void startMyThread(){
myThread = new Thread(this);
myThread.start();
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getDestinationAddr() {
return destinationAddr;
}
public void setDestinationAddr(String destinationAddr) {
this.destinationAddr = "sms://" + destinationAddr + ":50000";
}
}
The Sender implementation is pretty much self explanatory. We open a connection to the destination address. From that connection we create a new TextMessage. We assign the address and payload to that message and we send it.
Building the Receiver
Creating the Receiver's Background
- The first class we are going to create in the
ReceiverFX project is the Background. We need to write a very simple code to get the following effect:
- Now we need to create an empty JavaFX file inside the
receiverfx.view package. If you look into the available packages, you will see receiverfx.view.resources, but not receiverfx.view alone. What happens is that the view package exist, but because there isn't any file inside it, it isn't displayed. Right click on receiverfx.view.resources package and and select New Empty JavaFX File... For the File Name use Background and for the package change receiverfx.view.resources to receiverfx.view
- From the
Palette drag and drop a CustomNode. Again name it Background.
- Create the following public variables:
myOpacity, type Number with initial value of 1.0
Integers x, y, width and height
- Create a variable called
background, this will be the filled background you see in the previous image.
- Drag a
Rectangle from the palette, and drop it after the background variable.
- Use the color information in the previous image to design a
LinearGradient for the Rectangle's filling.
- Notice that the end of this gradient will be
endX=1.0 and endY=1.0.
- Also notice from the image that there are 3 stops, at 0.0 and 0.5 and at 1.0
- background should look like this:
var background = Rectangle {
x: bind x
y: bind y
width: bind width
height: bind height
fill: LinearGradient {
startX: 0.0
startY: 0.0
endX: 1.0
endY: 1.0
stops: [
Stop {
color: Color.BLACK
offset: 0.0
},
Stop {
color: Color.rgb(65,65,135)
offset: 0.5
},
Stop {
color: Color.BLACK
offset: 1.0
},
]
}
}
- Now let's create the frame around the screen.
- Create a variable called
frame
- Drag a
Rectangle from the palette, and drop it after the frame variable.
- Use the color information in the previous image to design a
LinearGradient for the Rectangle's stroke.
- Notice that the end of this gradient will be
endX=1.0 and endY=1.0.
- Also notice from the image that there are 3 stops, at 0.0 and 0.5 and at 1.0
- You can select 2 or higher for the strokeWidth
- Make sure you use archHeight and archWidth to obtain rounded corners for the
Rectangle
frame should look like this:
var frame = Rectangle {
x: bind x,
y: bind y,
width: bind width - 18
height: bind height - 42
strokeWidth: 2
stroke: LinearGradient {
startX: 0.0
startY: 0.0
endX: 1.0
endY: 1.0
stops: [
Stop {
color: Color.rgb(85,85,155)
offset: 0.0
},
Stop {
color: Color.BLACK
offset: 0.5
},
Stop {
color: Color.rgb(85,85,155)
offset: 1.0
},
]
}
arcHeight: 20
arcWidth: 20
}
- Now, all we need to do is to put the pieces together and add
background and frame to the Group's content in the create method.
- If you remember from the previous exercise, you can preview your changes as you build your JavaFX application. Unfortunately this feature is not available for mobile applications. Then to be able to test this, we will need to change the
Application Execution Model to Standard Execution.
- Right click on the project and select
Properties
- Click on
Run under Categories
- Select
Standard Execution under Application Execution Model.
- Click
OK to save the changes
- Drag a
Stage from the palette and drop it right after the end of the Background class definition. For the Screen's content, create a Background, to test the class.
- Your
Background.fx file should look like this:
package receiverfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.Scene;
class Background extends CustomNode {
public var myOpacity = 1.0;
public var x: Integer;
public var y: Integer;
public var width: Integer;
public var height: Integer;
var frame = Rectangle {
x: bind x,
y: bind y,
width: bind width - 18
height: bind height - 42
strokeWidth: 2
stroke: LinearGradient {
startX: 0.0
startY: 0.0
endX: 1.0
endY: 1.0
stops: [
Stop {
color: Color.rgb(85,85,155)
offset: 0.0
},
Stop {
color: Color.BLACK
offset: 0.5
},
Stop {
color: Color.rgb(85,85,155)
offset: 1.0
},
]
}
arcHeight: 20
arcWidth: 20
}
var background = Rectangle {
x: bind x,
y: bind y
width: bind width,
height: bind height,
fill: LinearGradient {
startX: 0.0
startY: 0.0
endX: 1.0
endY: 1.0
stops: [
Stop {
color: Color.BLACK
offset: 0.0
},
Stop {
color: Color.rgb(65,65,135)
offset: 0.5
},
Stop {
color: Color.BLACK
offset: 1.0
},
]
}
}
public override function create(): Node {
return Group {
content: [background, frame]
};
}
}
Stage {
title: "MyApp"
scene: Scene {
width: 240
height: 320
content: [
Background{
width: 240
height: 320
x: 8
y: 25
} ]
}
}
- Preview your file and check the result
- Before we move to the next step, make sure you remove the
Stage.
- Once you delete the
Stage you will have some unused imports. Right click on the background and select Fix Imports. Netbeans will fix this for you.
- Add public to the class definition.
Creating the Receiver's Animations
The first animation we want to create is the waiting snail. What we want to do is to show a snail moving along the X coordinates, but, we want the snail to move only inside the background's frame.
To be able to simulate this, we are going to use the variable clip of the ImageView, allowing us to define a rectangular clipping shape for this Image.
The rectangular clipping area will be as tall as the image is (we want to see the full height of the image), and the width will be the frame's width. As we move the image along the X coordinates, the clip shape still remains in the same place, giving us the impression that the snail is coming out from the frame and disappearing as he moves to the other side of the frame.
- Right click on the
receiverfx.view package and create a new Empty JavaFX File... Name it Waiting
- From the palette drag a
CustomNode and drop it on Waiting. Name it Waiting.
- Create 2 public Integer variables
x and y.
- Create a variable type
Integer named snailX. We will be using this value for moving the image along the x coordinates.
- Create a variable called
snail. From the palette, drag an Image and drop it just after snail definition.
- For the
url of the image use waiting.jpg file that you can find under resources.
- Turns on the
cache variable, so this image will be cached as a bitmap
- Bind the
x coordinate to snailX variable
- Bind the
y coordinate to y variable
var snail = ImageView {
image: Image {
url: "{__DIR__}resources/waiting.jpg"
}
cache: true
x: bind snailX
y: bind y
};
- Now let's include the
clip variable inside snail definition.
- Type
clip and drag a Rectangle form the Palette and associate it with clip.
- Bind
x and y to the Waiting's public variables x and y.
- Define the
width of the Rectangle as 220, and its height as 195.
Fill should be black.
var snail = ImageView {
image: Image {
url: "{__DIR__}resources/waiting.jpg"
}
clip: Rectangle {
x: bind x,
y: bind y
width: 220,
height: 195
fill: Color.BLACK
}
cache: true
x: bind snailX
y: bind y
};
- Now let's create the animation that makes the snail move along.
- Define a variable called
snailMove.
- From the
palette drag a Timeline component and drop it in front of snailMove definition.
- From the previous exercise you should be a master in animations and key frames. At time 0s we want
snailX to have a value of 150. At 15s we want snailX to have a value of -480 and we want to use a linear Interpolator for this.
- We want to repeat indefinitely this animation.
- Following this criteria, our
Timeline should look like this:
var snailMove = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s){snailX => 150}
at(15s){snailX => -480 tween Interpolator.LINEAR}
]
}
- Create a second animation that allows us to hide the snail once the text message arrives
- Add a public variable called
myOpacity and provide the value 1.0
- Bind snail's opacity to
myOpacity so you can hide the image from the animation.
- Create a
Timeline called snailHide. With this animation turns myOpacity to 0.0 in 2 seconds.
- Start the
snailMove animation inside the create method.
- Add snail into the
Group's content from the create function.
Now Waiting should look like this:
package receiverfx.view;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class Waiting extends CustomNode {
public var myOpacity = 1.0;
public var x: Integer;
public var y: Integer;
var snailX: Integer = 0;
var snail = ImageView {
image: Image {
url: "{__DIR__}resources/waiting.jpg"
}
clip: Rectangle {
x: bind x,
y: bind y
width: 220,
height: 195
fill: Color.BLACK
}
cache: true
x: bind snailX
y: bind y
opacity: bind myOpacity
};
var snailMove = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: [
at(0s){snailX => 150}
at(15s){snailX => -480 tween Interpolator.LINEAR}
]
}
public var snailHide = Timeline {
keyFrames: [
at(2s){myOpacity => 0.0}
]
}
public override function create(): Node {
snailMove.play();
return Group {
content: [snail]
};
}
}
- Now let's test both the background and the waiting snail.
- Create an Empty JavaFX File and name it
Main.
- Create a
Background, a Waiting class and a Stage containing both.
- Add a
quit variable to display a quit.png file. When the user clicks it, we finish our application.
- Add
quit to the Scene's contents
- The file should be as simple as the following code:
package receiverfx.view;
import javafx.stage.Stage;
import javafx.scene.Scene;
var background = Background{
x:0
y:0
width: 240
height: 320
}
var waitingSnail = Waiting{
x: 10
y: 200
};
var quit = ImageView {
image: Image {
url: "{__DIR__}resources/quit.png"
}
x: 225
y: 5
onMouseClicked: function( e: MouseEvent ):Void {
FX.exit();
}
};
Stage {
title: "MyApp"
scene: Scene {
width: 240
height: 320
content: [background, quit, waitingSnail ]
}
}
- Preview the file. You should see the snail animation moving
Now we need to create another class that will allow us to display the incoming message.
- Create a new
Empty JavaFX File under receiverfx.view. Named the file Main.
- Copy and paste the following code.
package receiverfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.animation.Timeline;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.scene.Scene;
class Letter extends CustomNode{
def hideImg = 0.0;
def showImg = 1.0;
var yStartPos = -110;
var yEndPos = 185;
var opacityImg0 = showImg;
var opacityImg1 = hideImg;
var opacityImg2 = hideImg;
var sender: String;
var mailContent: String;
var img1 = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim1.png"
}
x: 70
y: bind yStartPos
opacity: bind opacityImg0
}
var img2 = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim2.png"
}
x: 50
y: 140
opacity: bind opacityImg1
}
var img3 = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim3.png"
}
x: 20
y: 50
opacity: bind opacityImg2
}
var newEmail = Timeline {
keyFrames: [
at(2s){ yStartPos => yEndPos;
opacityImg0 => showImg;
opacityImg1 => hideImg
}
at(4s){opacityImg0 => hideImg;
opacityImg1 => showImg;
opacityImg2 => hideImg;
}
at(6s){opacityImg1 => hideImg;
opacityImg2 => showImg}
]
}
var senderTxtField = Text {
font: Font {
size: 12
}
x: 40,
y: 80
wrappingWidth: 100
fill: Color.BLACK
content: bind "From: {sender}"
opacity: bind opacityImg2
}
var mailContentTxtField = Text {
font: Font {
size: 12
}
x: 43,
y: 130
wrappingWidth: 165
fill: Color.BLACK
content: bind "Body: {mailContent}"
opacity: bind opacityImg2
}
public override function create(): Node {
newEmail.playFromStart();
return Group {
content: [img1,img2,img3,senderTxtField,mailContentTxtField]
};
}
}
Stage {
title: "MyApp"
scene: Scene {
width: 240
height: 320
fill: Color.BLACK
content: [ Letter{} ]
}
}
This class should be very familiar for you
- There are 3 images. We will show and hide this images in sequences, to simulate an animation.
- There are 2 Text, that we will use for displaying information about where the message is coming from, and also the body of the message.
- An animation that allow us to show and hide the images.
- Preview the file. You should see the sequence:
- Envelop dropping
- Envelop opens
- Letter displayed.
- Remove the
Stage and make the Letter class public.
- Also remove the line
newEmail.playFromStart();
from the create method, as we only use this for testing. The animation will be play if and only if a new message arrives.
Connecting the Receiver with Java ME
Now it's time to bind our JavaFX user interface with our Java ME piece of software. The way we decided to do it so what through listeners. This means that one of our JavaFX classes will have to implement MsgListener interface.
- Open
Letter.fx if you don't have it already open.
- Add
MsgListener to the list of classes extended by the class Letter. There is no such a thing as implements in JavaFX, then we use the standard extends.
- We want to create a
null function, a function that will be implemented when the instance of the class will be created. This give us the opportunity to hear back from the code depending on the class instance. Let's add the following line the Letter class:
public var onMessageArrived: function() = null;
Within the Letter class we can refer and call onMessageArrived method, but the implementation of this method won't be known until a class instance is created and onMessageArrived function gets defined.
- Override the function
msgArrived from the MsgListener interface. This method will be called when a new message arrive.
- First, we want to call
onMessageArrived,if an implementation for it has been provided.
- We want to play
newEmail animation to show the arrival of a new text message
- From the
MsgEvent provided as parameter for this function, we need to get the sender and content of this arriving message, and because the Text are bounded to sender and mailContent variables, the information of the incoming message will be displayed on the screen.
override function msgArrived(newMsg: MsgEvent){
if(onMessageArrived != null){
onMessageArrived();
}
newEmail.play();
var arrivingMsg: Msg = newMsg.getNewMsg();
sender= arrivingMsg.getSender();
mailContent= arrivingMsg.getContent();
}
Our Letter class should look like this:
package receiverfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.animation.Timeline;
import receiverfx.tracking.MsgListener;
import receiverfx.tracking.MsgEvent;
import receiverfx.tracking.Msg;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
public class Letter extends CustomNode, MsgListener{
def hideImg = 0.0;
def showImg = 1.0;
var yStartPos = -110;
var yEndPos = 185;
var opacityImg0 = showImg;
var opacityImg1 = hideImg;
var opacityImg2 = hideImg;
var sender: String;
var mailContent: String;
public var onMessageArrived: function() = null;
var img1 = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim1.png"
}
x: 70
y: bind yStartPos
opacity: bind opacityImg0
}
var img2 = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim2.png"
}
x: 50, y: 140
opacity: bind opacityImg1
}
var img3 = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim3.png"
}
x: 20, y: 50
opacity: bind opacityImg2
}
var newEmail = Timeline {
keyFrames: [
at(2s){ yStartPos => yEndPos;
opacityImg0 => showImg;
opacityImg1 => hideImg
}
at(4s){opacityImg0 => hideImg;
opacityImg1 => showImg;
opacityImg2 => hideImg;
}
at(6s){opacityImg1 => hideImg;
opacityImg2 => showImg}
]
}
var senderTxtField = Text {
font: Font {
size: 12
}
x: 40,
y: 80
wrappingWidth: 100
fill: Color.BLACK
content: bind "From: {sender}"
opacity: bind opacityImg2
}
var mailContentTxtField = Text {
font: Font {
size: 12
}
x: 43,
y: 130
wrappingWidth: 165
fill: Color.BLACK
content: bind "Body: {mailContent}"
opacity: bind opacityImg2
}
public override function create(): Node {
return Group {
content: [img1,img2,img3,
senderTxtField, mailContentTxtField]
};
}
override function msgArrived(newMsg: MsgEvent){
if(onMessageArrived != null){
onMessageArrived();
}
newEmail.play();
var arrivingMsg: Msg = newMsg.getNewMsg();
sender=arrivingMsg.getSender();
mailContent=arrivingMsg.getContent();
}
}
Final Touches for the Receiver
Now it's time to put things together, but we are almost done with the Receiver.
- Inside
Main.fx file create a variable newLetter type Letter and define the method onMessageArrived. What we want to do when a message has arrived is to stop the snail animation, as we are not longer waiting for the incoming message.
var newLetter = Letter{
onMessageArrived: function(){
hideWaitingScreen();
}
};
function hideWaitingScreen(){
waitingSnail.snailHide.play();
}
- Add
newLetter to the Scene's contents.
- At the moment the
Main.fx script start execution with the Stage, but we actually want to start the execution with a method run() because we need to start some threads and perform some listeners registration.
- Use
myStage as a variable for the Stage.
- Include the method
run() and show myStage
Before
Stage {
title: "Receiver"
scene: Scene {
width: 240
height: 320
content: [background,
quit, waitingSnail ]
}
}
After
var myStage = Stage {
title: "Receiver"
width: 240
height: 320
scene: Scene {
content: [background, quit,
waitingSnail, newLetter
]
}
}
function run(){
myStage.visible = true;
}
- Now we need to create a Receiver's thread to open the Connection and start waiting for messages.
- First declare a variable
myReceiver type Receiver
- Inside the
run function:
- Create
myReceiver
- Register
newLetter as a listener for new messages
- Start
myReceiver thread.
- Fix imports if required
var myReceiver: Receiver;
function run(){
myReceiver = Receiver{
};
myReceiver.getMyTracker().addTrackerListener(newLetter);
myReceiver.startMyThread();
myStage.visible = true;
}
Finally your Main.fx file should look like this:
package receiverfx.view;
import java.lang.Object;
import javafx.scene.Scene;
import javafx.stage.Stage;
import receiverfx.connector.Receiver;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
var myReceiver: Receiver;
var waitingSnail = Waiting{
x: 10
y: 200
};
var background = Background{
x: 8
y: 25
width: 240
height: 320
};
var quit = ImageView {
image: Image {
url: "{__DIR__}resources/quit.png"
}
x: 225
y: 5
onMouseClicked: function( e: MouseEvent ):Void {
FX.exit();
}
};
var newLetter = Letter{
onMessageArrived: function(){
hideWaitingScreen();
}
};
function hideWaitingScreen(){
waitingSnail.snailHide.play();
}
var myStage = Stage {
title: "Receiver"
width: 250
height: 80
scene: Scene {
content: [
background,
quit, waitingSnail, newLetter
]
}
}
function run(){
myReceiver = Receiver{
};
myReceiver.getMyTracker().addTrackerListener(newLetter);
myReceiver.startMyThread();
myStage.visible = true;
}
- Congratulations! Now your
Receiver application is ready to wait and display text messages. If you run the application as it's right now, you will only see the waiting snail, as we need to wait for the Sender application to be ready to send some messages to us.
Building the Sender
Creating a JavaFX Keyboard for the Sender Application
For this particular part of the demo, we need the user to enter some data, like the telephone number and the content of the message. For this particular purpose we want to design a keyboard that works on a touch screen phone and also on a conventional phone.
- Open and expand the SenderFX project.
- Extends senderfx.keyboard.resources project. Inside you can see a bunch of jpg files. Double click on any of the files to see the content. We are going to use this images for creating our keyboards.
- Right click on
Source Packages and select New -> Empty JavaFX File... Select senderfx.keyboard as the package and Key for the namefile.
- Let's see the
Key class:
Key will display a key image specified by filename.
- Each
Key will have a value, this represent the String you get when you hit the key.
- There is a
null function onTyped that will allow you to implement it when you instantiate the Key.
- Two
mouseEvents related methods, to shrink the image on a mouse pressed, and return it to its original size on mouse released.
- On mouse clicked we invoke the
onType function if there is an available implementation
- One last thing is to assign the value. We tried to use the value as the filename of the image, but there are some exceptions, as some names where not allowed as filenames, like ",", ":", "." and " ". For this reason we implemented a little code to fix this on the create method.
package senderfx.keyboard;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
class Key extends CustomNode {
public var value: String;
public var filename: String;
public var x: Integer;
public var y: Integer;
var myImage: Image;
var zoomFactor = 1.0;
public var onTyped: function(data : String) = null;
public override var onMousePressed = function(e:MouseEvent) {
zoomFactor = 0.85;
}
public override var onMouseClicked = function(e:MouseEvent) {
if(onTyped != null){
onTyped(value);
}
}
public override var onMouseReleased = function(e:MouseEvent) {
zoomFactor = 1.0;
}
public override function create(): Node {
if(filename != null){
value = filename;
if(filename == "comma"){
value = ",";
}
if(filename == "dot"){
value = ".";
}
if(filename == "colon"){
value = ":";
}
if(filename == "space"){
value = " ";
}
}
return Group {
content: [
ImageView {
x: bind x;
y: bind y;
scaleX: bind zoomFactor
scaleY: bind zoomFactor
image: Image {
url: "{__DIR__}resources/{filename}.jpg"
}
}]
};
}
}
- Copy the previous code and paste it into the
Key.fx file.
- You can test your code by adding the following stage to the
Key.fx file:
Stage {
title: "MyApp"
scene: Scene {
width: 30
height: 30
content: [
Key{
filename: "1"
}
]
}
}
- Preview the file. Make sure before you do it that the execution model is Standard Execution.
- Test that when you click on it, it's size decreases, and return to normal once the mouse is released.
- You can now remove the
Stage and make your Key class public.
- Now we need to create a
Keyboard class. The code is also very straightforward
- The
Keyboard class will have a group of Keys (myKeys) and a history of typed keys (typedKeyList This actually represents the string typed by the user)
- The
keys array will hold the list of filenames (keys' images) to be included in the keyboard
load method will get as a parameter the number of rows and columns to use in the distribution of the Keyboard's keys. You can also specify the space between keys.
- The
load method is very straightforward, we just create a Key, put it in the appropriate place in the keyboard and add it to myKeys group. To denote an empty space we use the string "blank" in the keys array, then if we find a "blank" we ignore the Key creation. It's important to notice the implementation of the onTyped method, in our case we just want to know what key was hit, and it's value will be recorded in the addText method explained next.
- The
addText method verifies that we haven't reached the maximum allowed string's size, and add the hit key's value to the string. If we hit the backspace key, represented in our demo for the character "-" then we delete the last character of our string.
- The
loadUnit method will load one key in a particular position in the keyboard.
package senderfx.keyboard;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import senderfx.keyboard.Key;
class Keyboard extends CustomNode {
def MAXTXTSIZE = 50;
public var keys: String[];
public var x: Integer;
public var y: Integer;
var myKeys = Group{
};
public var typedKeyList: String = "";
public function load(columns: Integer, rows: Integer, keySpace: Integer){
var myKey: Key;
var cont = 0;
for(i in [0..
(rows - 1)]){
for(j in [0..
(columns - 1)]){
if(keys[cont] != "blank"){
myKey = Key {
x: (j * keySpace)
y: (i * keySpace)
filename: "{keys[cont]}"
onTyped: function(value:String){
addText(value);
}
}
insert myKey into myKeys.content;
}
cont++;
};
}
};
function addText(value: String){
if(value == "-"){
if(typedKeyList.length() > 0){
typedKeyList = "{typedKeyList.substring(0, typedKeyList.length()-1)}";
}
}else {
if(typedKeyList.length() < MAXTXTSIZE){
typedKeyList = "{typedKeyList}{value}";
}
}
}
public function loadUnit(column: Integer, row: Integer, keySpace: Integer,
position: Integer){
var myKey = Key {
x: ((column - 1) * keySpace)
y: ((row - 1) * keySpace)
filename: "{keys[(position-1)]}"
};
insert myKey into myKeys.content;
};
public override function create(): Node {
return Group {
translateX: bind x
translateY: bind y
content: [myKeys]
};
}
}
- Let's now test my
Keyboard class:
- First we need an array with the image's filenames. You can have a look inside the
receiverfx.keyboard.resources package for the entire file list. For this test we are going to load all the letters, and a dot, space bar, comma, backspace and colon. Notice that in the list we have "blank", this means we will skip one place in the keyboard after we place the space bar, this is required because the space bar occupies double space.
var keyNames =
["A","B","C","D","E","F","G","H",
"I","J","K","L","M","N","O","P",
"Q","R","S","T","U","V","W","X",
"Y","Z","dot","space", "blank","comma","-","colon"];
- We want to make sure
Keyboard class can remember what we type, so let's create a message String to bind to this value. As we type and new characters are been added to the string, we want to see it.
public var message = "" on replace
oldValue{
println("{message }");
};
- Finally we create the
Keyboard. We bind with inverse the typedKeyList with message, so whenever the typedKeyList gets updated, we will see those changes back into message.
var myKeyboard = Keyboard{
x: 5
y: 90
keys: keyNames
typedKeyList: bind message with inverse
};
- Now let's create the
Stage and the run method. We need to call the load method to populate the Keyboard. We will use 8 columns and 4 rows, and 25 for the space between keys.
var myStage = Stage {
title: "MyApp"
scene: Scene {
width: 200
height: 200
content: [ myKeyboard ]
}
}
function run(){
myKeyboard.load(8,4,23);
myStage.visible = true;
}
- Your file should look like this now:
package senderfx.keyboard;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import senderfx.keyboard.Key;
import javafx.stage.Stage;
import javafx.scene.Scene;
class Keyboard extends CustomNode {
def MAXTXTSIZE = 50;
public var keys: String[];
public var x: Integer;
public var y: Integer;
public var typedKeyList: String = "";
var myKeys = Group{
};
public function load(columns: Integer, rows: Integer, keySpace: Integer){
var myKey: Key;
var cont = 0;
for(i in [0..
(rows - 1)]){
for(j in [0..
(columns - 1)]){
if(keys[cont] != "blank"){
myKey = Key {
x: (j * keySpace)
y: (i * keySpace)
filename: "{keys[cont]}"
onTyped: function(value:String){
addText(value);
}
}
insert myKey into myKeys.content;
}
cont++;
};
}
};
function addText(value: String){
if(value == "-"){
if(typedKeyList.length() > 0){
typedKeyList = "{typedKeyList.substring(0, typedKeyList.length()-1)}";
}
}else {
if(typedKeyList.length() < MAXTXTSIZE){
typedKeyList = "{typedKeyList}{value}";
}
}
}
public function loadUnit(column: Integer, row: Integer,
keySpace: Integer, position: Integer){
var myKey = Key {
x: ((column - 1) * keySpace)
y: ((row - 1) * keySpace)
filename: "{keys[(position-1)]}"
};
insert myKey into myKeys.content;
};
public override function create(): Node {
return Group {
translateX: bind x
translateY: bind y
content: [myKeys]
};
}
}
public var message = "" on replace oldValue{
println("{message }");
};
var keyNames = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R",
"S","T","U","V","W","X","Y","Z","dot","space","blank","comma","-",
"colon"];
var myKeyboard = Keyboard{
x: 5
y: 90
keys: keyNames
typedKeyList: bind message with inverse
};
var myStage = Stage {
title: "MyApp"
scene: Scene {
width: 200
height: 200
content: [ myKeyboard ]
}
}
function run(){
myKeyboard.load(8,4,23);
myStage.visible = true;
}
- When you preview your file you should see something like this:
- As you click on the keys, you should see the printed string on your output. Make sure that the keys are working, specially the space bar and the backspace.
- Remove all the testing code, and make the
Keyboard class public. Fix the imports if required.
- Now that you know how to create an alphabetical keyboard, you should be able to create a separate class that do this. We just need to include a text area for displaying the text as you type, instead of using the standard output. The following code should be all familiar to you:
package senderfx.keyboard;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import senderfx.keyboard.Keyboard;
class AlphabeticalKeyboard extends CustomNode {
public var message = "";
public var x: Integer = 0;
public var y: Integer = 0;
var keyNames = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P",
"Q","R","S","T","U","V","W","X","Y","Z","dot","space","blank","comma",
"-", "colon"];
var textArea = Text {
font: Font {
size: 15
}
x: 12,
y: 25
wrappingWidth: 175
textAlignment: TextAlignment.JUSTIFY
content: bind message
fill: Color.WHITE
};
var backgroundText = Rectangle {
x: 5,
y: 5
width: 182,
height: 80
fill: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(100, 100, 100)
},
Stop{
offset: 1.0
color: Color.rgb(0, 0, 0)
}
]
}
strokeWidth: 1.0
stroke: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(150,150,150)
},
Stop{
offset: 1.0
color: Color.rgb(220,220,220)
}
]
}
}
var myKeyboard = Keyboard{
x: 5
y: 90
keys: keyNames
typedKeyList: bind message with inverse
};
public override function create(): Node {
myKeyboard.load(8,4,23);
return Group {
translateX: bind x
translateY: bind y
content: [backgroundText, textArea, myKeyboard]
};
}
}
- As you have done it before with your previous exercises, let's create the class:
- Right click to the project
senderfx.keyboard, and select New -> Empty JavaFX File...
- Name it
AlphabeticalKeyboard
- Copy the previous code and paste it inside the file
- If you preview your code with:
Stage {
title: "MyApp"
scene: Scene {
width: 200
height: 200
content: [AlphabeticalKeyboard{} ]
}
}
you should be able test your class. If you type "T"+"H"+"I"+"S"+" "+"I"+"S"+" "+"A"+" "+"T"+"E"+"S"+"T"+".", you should see:
remember to remove the Stage from the file, make your class public and remove unused imports.
- Now do the same with the numerical keyboard:
package senderfx.keyboard;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import senderfx.keyboard.Keyboard;
public class NumericalKeyboard extends CustomNode {
public var number = "";
public var x: Integer = 0;
public var y: Integer = 0;
var keyNames = ["1", "2", "3", "4", "5", "6","+","7", "8", "9", "0","-"];
var textArea = Text {
font: Font {
size: 15
}
x: 12,
y: 25
wrappingWidth: 175
textAlignment: TextAlignment.JUSTIFY
content: bind number
fill: Color.WHITE
};
var backgroundText = Rectangle {
x: 5,
y: 5
width: 182,
height: 30
fill: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(100, 100, 100)
},
Stop{
offset: 1.0
color: Color.rgb(0, 0, 0)
}
]
}
strokeWidth: 1.0
stroke: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(150,150,150)
},
Stop{
offset: 1.0
color: Color.rgb(220,220,220)
}
]
}
}
var myKeyboard = Keyboard{
x: 30
y: 40
keys: keyNames
typedKeyList: bind number with inverse
};
public override function create(): Node {
myKeyboard.load(6,2,23);
return Group {
translateX: bind x
translateY: bind y
content: [backgroundText, textArea, myKeyboard]
};
}
}
Now it's time to create the menu for the Sender. As you can see, each menu item has a text and a bar. If the menu item is selected, the bar grows, the text is moved up to allow the bar to grow, and the color of the item gets brighter.
- Our approach again is to build the smaller component, and from there grow the application. Let's start with the single Bar element. The following code should be really easy if you have followed the previous exercises. Let's highlight a few things:
- The main elements of the
Bar are a Text (barTxtBtn) and a Rectangle (bar)
- We bind the
fill of both components to color, because once the Bar gets selected the color will change.
- We bind the
height of the bar to barHeight as we need to grow the bar when it gets selected
- We bind the Bar's
Group to y, so we can rise the component (both text and bar) as it gets selected.
- We have two animations:
select and unselect.
- If the component is
selected, we move it up, we change the color and we grow the bar.
- If the component is
unselected, we move it back down, change it's color and put the bar to its normal size.
- Last thing to notice is the callback
onClick, function that is called when the Bar gets the click.
package senderfx.view;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.input.MouseEvent;
public class Bar extends CustomNode {
def delta = 8;
def minHeight = 7;
def maxHeight = minHeight + delta;
public var width: Integer = 40;
public var selectedColor: Color;
public var unSelectedColor: Color;
public var barTxt: String;
public var x: Integer;
public var y: Integer;
var color: Color;
var highY = y - delta;
var lowY = y;
var barHeight: Integer = minHeight;
var barTxtBtn = Text {
font: Font {
size: 13
}
x: 0,
y: 20
fill: bind color
content: bind barTxt
}
var bar = Rectangle {
x: 0,
y: 23
width: bind width
height: bind barHeight
fill: bind color
}
public var onClicked: function() = null;
public override var onMouseClicked = function(e:MouseEvent) {
if(onClicked != null){
onClicked();
}
}
public var select = Timeline {
keyFrames: [
at(0s){y => lowY;
barHeight => minHeight;
color => unSelectedColor;
}
at(0.2s){y=> highY;
barHeight => maxHeight;
color => selectedColor
}
]
}
public var unselect = Timeline {
keyFrames: [
at(0s){y=> highY;
barHeight => maxHeight;
color => selectedColor
}
at(0.5s){y => lowY;
barHeight => minHeight;
color => unSelectedColor;
}
]
}
public override function create(): Node {
color = unSelectedColor;
return Group {
translateX: bind x
translateY: bind y
content: [bar, barTxtBtn]
};
}
}
- Create an empty JavaFX File under
senderfx.view package. Copy the previous code and paste it in the file.
- Now its time to create the menu bar with the Bar items. The BarsBar looks very similar to the
MyButtonGroup class from the previous exercise:
- We basically need to know who is currently active to be able to go to the next or previous Bar (
activeButton and goNext and goPrevious function).
- Whenever the
activeButton changed, we need to play the unselect animation on the Bar that looses focus, and play the select animation on the Bar that gets the focus.
- Then we have the
createBars method to define the Bars that will be part of this menu.
- And finally we have a
Rectangle for background called frame
package senderfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.scene.Scene;
public class BarsBar extends CustomNode {
public var x: Integer;
public var y: Integer;
var myBars: Bar[];
public var activeButton: Integer = 0 on replace oldValue {
myBars[oldValue].unselect.playFromStart();
myBars[activeButton].select.playFromStart();
};
public function goNext(){
var barNum = sizeof myBars;
if(activeButton == (barNum - 1)){
activeButton = 0;
}else {
activeButton++;
}
}
public function goPrevious(){
if(activeButton == 0){
activeButton = (sizeof myBars) - 1;
}else {
activeButton--;
}
}
public var onClicked: function(selected: String) = null;
function createBars(){
insert
Bar{
barTxt: "New"
selectedColor: Color.rgb(161,157,150)
unSelectedColor: Color.rgb(66,58,45)
width: 32
x: 10,
onClicked: function(){
activeButton = 0;
if(onClicked != null){
onClicked("new");
}
}
} into myBars;
insert
Bar{
barTxt: " To"
selectedColor: Color.rgb(157,13,0)
unSelectedColor: Color.rgb(206,134,128)
width: 32
x: 52,
onClicked: function(){
activeButton = 1;
if(onClicked != null){
onClicked("to");
}
}
}into myBars;
insert
Bar{
barTxt: "Body"
selectedColor: Color.rgb(157,195,0)
unSelectedColor: Color.rgb(206,225,128)
width: 32
x: 94,
onClicked: function(){
activeButton = 2;
if(onClicked != null){
onClicked("body");
}
}
}into myBars;
insert
Bar{
barTxt: "Send"
selectedColor: Color.rgb(240,234,45)
unSelectedColor: Color.rgb(229,248,150)
width: 32
x: 136,
onClicked: function(){
activeButton = 3;
if(onClicked != null){
onClicked("send");
}
}
}into myBars;
insert
Bar{
barTxt: " Exit"
selectedColor: Color.rgb(36,39,155)
unSelectedColor: Color.rgb(128,130,206)
width: 32
x: 176,
onClicked: function(){
FX.exit();
}
}into myBars;
myBars[activeButton].select.playFromStart();
}
var frame = Rectangle {
x: 0,
y: 0,
width: 222,
height: 30
strokeWidth: 2
stroke: LinearGradient {
startX: 0.0
startY: 0.0
endX: 1.0
endY: 1.0
stops: [
Stop {
color: Color.rgb(85,85,155)
offset: 0.0
},
Stop {
color: Color.BLACK
offset: 0.5
},
Stop {
color: Color.rgb(85,85,155)
offset: 1.0
},
]
}
arcHeight: 20
arcWidth: 20
}
public override function create(): Node {
createBars();
return Group {
translateX: bind x
translateY: bind y
content: [frame, myBars]
};
}
}
- Create an empty JavaFX File under
senderfx.view package. Copy the previous code and paste it in the file.
- You can test your code with:
Stage {
title: "MyApp"
scene: Scene {
width: 200
height: 200
content: [ BarsBar{} ]
}
}
- You should see:
make sure you can move around the menu and get the animations as you move along.
- Now to be able to continue, remove this testing code by removing the
Stage from the file, make the class public and correct the imports are needed.
Designing the Screens
Now let's create the different screens for the Sender. You can see that we already have a lot of the components, so mainly we need to put things together.
- First we have a title in every screen, so we can create a simple class to be reuse for all the screens. The class
Title has a Rectangle called background for the coloring, and one Text called titleStr for the title itself.
package senderfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
public class Title extends CustomNode {
public var x: Integer;
public var y: Integer;
public var width: Integer;
public var height: Integer;
public var colorBG: Color;
public var colorFG: Color = Color.BLACK;
public var content: String;
var background = Rectangle {
x: 0,
y: 0
width: bind width,
height: bind height
fill: bind colorBG
arcHeight: 10
arcWidth: 10
}
var titleStr = Text {
font: Font {
size: 15
}
x: 15,
y: 19
fill: bind colorFG
content: bind content
}
public override function create(): Node {
return Group {
translateX: bind x
translateY: bind y
content: [background, titleStr]
};
}
}
- Create an empty JavaFX file inside the
senderfx.view package. Add this code into that new file.
- MessageScreen is also very simple, just create a couple of Text and the Rectangles for the background. Make sure you also add this file to your project.
package senderfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
public class MessageScreen extends CustomNode {
public var x: Integer;
public var y: Integer;
public var message: String;
public var telephone: String;
var toTxt = Text {
font: Font {
size: 15
}
x: 0,
y: 15
fill: Color.WHITE
content: "To:"
}
var phoneTxt = Text {
font: Font {
size: 15
}
x: 12,
y: 45
wrappingWidth: 175
textAlignment: TextAlignment.JUSTIFY
content: bind telephone
fill: Color.WHITE
};
var toRect = Rectangle {
x: 0,
y: 20
width: 182,
height: 40
fill: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(100, 100, 100)
},
Stop{
offset: 1.0
color: Color.rgb(0, 0, 0)
}
]
}
strokeWidth: 1.0
stroke: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(150,150,150)
},
Stop{
offset: 1.0
color: Color.rgb(220,220,220)
}
]
}
}
var bodyTxt = Text {
font: Font {
size: 15
}
x: 0,
y: 90
fill: Color.WHITE
content: "Message Body:"
}
var contentTxt = Text {
font: Font {
size: 15
}
x: 8,
y: 115
wrappingWidth: 175
textAlignment: TextAlignment.JUSTIFY
content: bind message
fill: Color.WHITE
};
var bodyRect = Rectangle {
x: 0,
y: 95
width: 182,
height: 80
fill: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(100, 100, 100)
},
Stop{
offset: 1.0
color: Color.rgb(0, 0, 0)
}
]
}
strokeWidth: 1.0
stroke: LinearGradient{
startX: 0
startY: 0
endX: 0
endY: 1
stops: [
Stop{
offset: 0.0
color: Color.rgb(150,150,150)
},
Stop{
offset: 1.0
color: Color.rgb(220,220,220)
}
]
}
}
public override function create(): Node {
return Group {
translateX: bind x
translateY: bind y
content: [toTxt, toRect, phoneTxt, bodyTxt, bodyRect, contentTxt]
}
}
}
- SendScreen is just a simple screen with one animation that makes the envelop image smaller has its get away. Also include this file into your view project.
package senderfx.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.animation.Timeline;
public class SendScreen extends CustomNode {
public var x: Integer;
public var y: Integer;
var hideX: Integer = 240;
var hideY: Integer = -40;
var xCoord: Integer = x;
var yCoord: Integer = y;
var zoomValue: Number = 1.0;
var myOpacity: Number = 1.0;
var envelop = ImageView {
image: Image {
url: "{__DIR__}resources/envelopanim1b.png"
}
x: bind xCoord
y: bind yCoord
scaleX: bind zoomValue
scaleY: bind zoomValue
opacity: bind myOpacity
}
public var anim = Timeline {
keyFrames: [
at(0s){
xCoord => x;
yCoord => y;
zoomValue => 1.0;
myOpacity => 1.0;
}
at(3s){
xCoord => hideX;
yCoord => hideY;
zoomValue => 0.0;
myOpacity => 0.0;
}
at(10s){
xCoord => x;
yCoord => y;
zoomValue => 1.0;
myOpacity => 0.0;
}
at(11s){
myOpacity => 1.0;
}
]
}
public override function create(): Node {
return Group {
content: [envelop]
};
}
}
- And finally putting everything together:
package senderfx.view;
import java.lang.Object;
import javafx.lang.FX;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import senderfx.connector.Sender;
import senderfx.view.Background;
import senderfx.view.BarsBar;
var screens = ["new", "to", "body", "send", "quit"];
var message: String;
var phone: String;
var currentScreen: Integer = 0;
var myScreens = Screens{
currentScreen: bind currentScreen with inverse
message: bind message with inverse
phone: bind phone with inverse
}
function sendMessage(){
var mySender = Sender{
};
mySender.setMsg(message);
mySender.setDestinationAddr(phone);
mySender.startMyThread();
myScreens.sendMessage();
}
function barClicked(item: String){
if(item == "new"){
currentScreen = 0;
message = "";
phone = "";
}
if(item == "to"){
currentScreen = 1;
}
if(item == "body"){
currentScreen = 2;
}
if(item == "send"){
currentScreen = 3;
sendMessage();
}
if(item == "quit"){
FX.exit();
}
}
var bars = BarsBar{
x: 8
y: 280
onClicked: function(event: String){
barClicked(event);
}
}
var background = Background{
x: 8
y: 25
height: 250
onKeyPressed: function(e:KeyEvent):Void {
if(e.code == KeyCode.VK_LEFT){
bars.goPrevious();
currentScreen = (bars.activeButton);
}
if(e.code == KeyCode.VK_RIGHT){
bars.goNext();
currentScreen = (bars.activeButton);
}
if(e.code == KeyCode.VK_SOFTKEY_0) {
bars.goPrevious();
}
if(e.code == KeyCode.VK_SOFTKEY_1){
bars.goNext();
}
if(e.code == KeyCode.VK_ENTER){
barClicked(screens[currentScreen]);
}
}
};
var quit = ImageView {
image: Image {
url: "{__DIR__}resources/quit.png"
}
x: 225
y: 5
onMouseClicked: function( e: MouseEvent ):Void {
FX.exit();
}
};
var myStage = Stage {
title: "Sender"
width: 250
height: 80
scene: Scene {
content: [background, quit,bars, myScreens]
}
}
function run(){
myStage.visible = true;
background.requestFocus();
}
Summary
The key point in this exercise was to show you how you can integrate JavaFX with Java ME code. We also showed how to create interactive components, like the keyboards, and we learned about animation.
Back to top
|
Contents
|