Thursday, October 2, 2008

Creating Custom Button in JavaFX

JavaFX is an exciting new technology to create RIA for different platforms and devices like desktop, web, mobile, tv etc. JavaFX provides a scripting language and API to create rich widgets, and user interfaces.
In this episode of blog, i will guide you how to create a custom button in JavaFX shown below. I also like to thanks Mr. Asim Rasool which guided me to choose color combinations. He is graphic designer and working for last five years as Graphic Expert.

First we create a Button class which extends from CustomNode class. In JavaFX, each UI Element is a Node. In order to design our own UI Nodes in JavaFX, one has to extend CustomNode.



public class Button extends CustomNode

{

}



Now if we think about features a button should provide to user then we come across width, height, x and y coordinates, and text which will appear on button. So we add following attributes to Button class.



public class Button extends CustomNode

{

attribute x:Number;

attribute y:Number;

attribute width:Number;

attribute height:Number;

attribute text:String;

private attribute txt:Text;


private attribute gradient:LinearGradient = LinearGradient {

startX: 0.0 startY: 0.0

endX: 0.0 endY: 1.0

proportional: true

stops: [

Stop { offset: 0.0 color: Color.web("#dceaff")},

Stop { offset: 0.49 color: Color.web("#6885b2") },

Stop { offset: 0.5 color: Color.web("#2c599c") },

Stop { offset: 1.0 color: Color.web("#bed3f4") }

]

}

}



In JavaFX, attribute is same as property in Java. Note that , there is a private attribute "txt" of type "Text" which is a node and used to draw text. In addition to txt, there is another private attribute which is gradient. I have used LinearGradient which is used to add gradient to node. In this case i have initialized LinearGradient using Object Literal notation. LinearGradient contains startX,startY, endX, and endY which specifies the area in which gradient should be applied. In this case, i want to apply gradient from top to bottom of button, so i mentioned startY as 0.0(top) and endY as 1.0(bottom). proportional attribute establishes smooth transition from one color to another color. As we know that gradient is a transition from one color to other, in order to mention those colors, stops array is used. Each Stop has two important attributes one is "offset" and second one is "color". offset is used to indicate where a particular color should be applied in full intensity. In my case it is from startX and startY.

Each class which extends from CustomNode, have to implement "create" function which returns Node object.


public function create():Node{
return Group{
translateX:x translateY:y;
content:[Rectangle{

width:width height:height
arcWidth:35 arcHeight:35
stroke:Color.web("#023493");
strokeWidth:1.5;
fill: bind gradient
onMouseEntered: function( e: MouseEvent ):Void {
gradient = LinearGradient {
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0

proportional: true
stops: [
Stop { offset: 0.0 color: Color.web("#ffffff")},
Stop { offset: 0.49 color: Color.web("#6885b2") },
Stop { offset: 0.5 color: Color.web("#2c599c") },
Stop { offset: 1.0 color: Color.web("#ffffff") }
]
}
}
onMouseExited: function( e: MouseEvent ):Void {
gradient = LinearGradient {
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0

proportional: true
stops: [
Stop { offset: 0.0 color: Color.web("#dceaff")},
Stop { offset: 0.49 color: Color.web("#6885b2") },
Stop { offset: 0.5 color: Color.web("#2c599c") },
Stop { offset: 1.0 color: Color.web("#bed3f4") }
]
}
}
},getText()

]
onMousePressed: function( e: MouseEvent ):Void {
txt.x = txt.x + 1;
txt.y = txt.y + 1;
}

onMouseReleased: function( e: MouseEvent ):Void {
txt.x = txt.x - 1;
txt.y = txt.y - 1;
}

}

}


In create function, i have returned Group which is a node used to combine two or more nodes in a single node. I have encapsulated a Rectangle node and Text node inside a Group node. Benefit of using Group will be that x, y coordinates of Rectangle and Text will be from left upper corner of Group, so just changing Group's coordinates, i can relocate Rectangle and Text automatically.
I have assigned gradient to fill attribute of Rectangle to produce gradient effect. I also used "bind" keyword which means, whenever gradient object will be changed, fill attribute of Rectangle will be recalculated. You can note it that i have changed gradient object on mouse events of Rectangle like onMouseEntered and onMouseExited. Other attributes are self explanatory. Text node is obtained through private function "getText".


private function getText():Text{
txt = Text{
content:text
fill:Color.WHITE
font:Font{
name:"Arial" size:14 style:FontStyle.BOLD
}
effect:DropShadow{
color:Color.BLACK
offsetX:1 offsetY:1

}

}
centralizeText(txt);

return txt;
}

private function centralizeText(text:Text):Void{
text.x = (width - text.getWidth())/2;
text.y = (height - text.getHeight())/2 + 12;

}


if you look at the getText function carefully, you will notice use of another function centralizeText, this function gets a Text node and sets its x and y on the basis of current width and height of text to appear on center of Rectangle. Best approach to centralize text is to get the text width in font and then adjust it but i don't find any such approach in JavaFX, so i have to get use of "Text" node's widht and height attributes which is i think not a good approach.
That's all now. You have created Custom Button successfully. Let's use it now. Code to use it given below:


Frame {
title: "Custom Button"
width: 220
height: 160
closeAction: function() {
java.lang.System.exit( 0 );
}
visible: true
stage: Stage {
content: [
Button{
x:20
y:20
width:120
height:30
text:"Submit"
onMouseClicked: function( e: MouseEvent ):Void {
System.out.println("This button has been clicked");
}
}
]
}
}



So, in this blog we explored following key concepts like:
  • Use of Object literal
  • Use of Group Node
  • How to implement events
  • How to create its own Node
In my coming blog i will create more complex nodes so stay in touch.