09 novembro 2009

Palestra: Tornando-se um Programador Java Sênior

Na sexta passada dei uma palestra no IFET Sul de Minas - Campus Muzambinho sobre Carreira Java na 4a Semana da Informática. A apresentação foi muito boa, e consegui deixar o pessoal atento até o fim. Espero que os alunos tenham ficado realmente animados à estudar essa tecnologia.
Aliás, aproveitei pra testar a ferramenta Prezi.com para criar uma apresentação diferente do tradicional Slide, o que ajudou muito a surpreender os espectadores. Recomendo fortemente essa ferramenta.
A apresentação está abaixo ou no link http://prezi.com/6aktm7nsn1fy/ pra quem quiser ver:

28 novembro 2008

Playing with layout: a custom JavaFX layout manager

Hi!

Did you looked at javafx.scene.Group class? In a first look, Group is only a simple collection of nodes. But, looking it more detailed, you will see that it has an important feature: a method to create layout managers! And JavaFX has two built in layouts: javafx.scene.layout.HBox and javafx.scene.layout.VBox.

The first one is a simple layout manager that puts all node in an horizontal line, one after other. And the second do the same, but in a vertical line. Try to use then, as you use Group.

But I need, for my application, a different layout manager: I need to put a collection of objects inside a box. This box has a limited size, and this object need to be arranged according to box size. So, I need an automatic grid layout, that defines the numbers of columns according to objects widths.

Some day ago, I've created this layout manager object, and only now it works fine. It works in horizontal or vertical orientation, and determines the number of columns/lines by objects sizes. I called it as AutoGridLayout, and it's code was here:

/*
* AutoGridLayout.fx
*
* Created on 12/11/2008, 17:26:00
*/

package brunogrossi.javafx.components;
import javafx.scene.*;

/**
* @author Bruno Grossi
*/

public class Alignment {
private attribute name: String;

/**
* Represents the central position.
*/
public static attribute CENTER = Alignment { name: "CENTER" }

/**
* Represents the begin position: LEFT when horizontal, TOP when vertical
*/
public static attribute BEGIN = Alignment { name: "BEGIN" }

/**
* Represents the end position: RIGTH when horizontal, BOTTOM when vertical
*/
public static attribute END = Alignment { name: "END" }

public function toString(): String { name }
}

public class AutoGridLayout extends Group {
/**The max width or height of this layout component*/
public attribute maxSize:Number on replace {
impl_requestLayout();
}

/**Indicates if it's horizontal(true) or vertical(false) layout*/
public attribute horizontal:Boolean=true on replace {
impl_requestLayout();
}

/**The spacing between elements*/
public attribute spacing:Number on replace {
impl_requestLayout();
}

/**The spacing between elements*/
public attribute alignment:Alignment=Alignment.CENTER on replace {
impl_requestLayout();
}

init {
impl_layout = doLayout;
}

private function doLayout(g:Group):Void {
if (sizeof this.content>0 and this.maxSize>0) {
var largerSize:Number = 0.0;

var size:Number;
for (node in this.content) {
if (node.visible) {
size = if (horizontal) node.getBoundsWidth() else node.getBoundsHeight();
if (size > largerSize)
largerSize = size;
}
}
var numberOfElements:Integer =
java.lang.Math.floor(this.maxSize / largerSize) as Integer;
size = this.maxSize / numberOfElements;
var maxOtherSize:Number=0.0;

var x:Number = 0;
var y:Number = 0;

for (node in this.content) {
if (node.visible) {
node.impl_layoutX = if (horizontal)
calcPosition(x, size, node.getBoundsWidth())
else x;
node.impl_layoutY = if (not horizontal)
calcPosition(y, size, node.getBoundsHeight())
else y;

if (indexof node mod numberOfElements == numberOfElements-1) {
if (horizontal) {
x=0;
y+=maxOtherSize+spacing;
maxOtherSize=0;
} else {
y=0;
x+=maxOtherSize+spacing;
maxOtherSize=0;
}
} else {
var otherSize;
if (horizontal) {
x += size + spacing;
otherSize = node.getBoundsHeight();
} else {//Vertical
otherSize = node.getBoundsWidth();
y += size + spacing;
}
if (otherSize>maxOtherSize)
maxOtherSize = otherSize;
}

}
}
}
}

private function calcPosition(pos:Number, size:Number, nodeSize:Number):Number {
if (this.alignment==Alignment.CENTER) {
var _center = pos + (size/2);
_center - (nodeSize/2);
} else if (this.alignment==Alignment.END) {
var _end = pos + size;
_end - nodeSize;
} else {//BEGIN is default option
pos;
}
}

}


Here is a demonstration code that uses the AutoGridLayout. You can test it with box of randon size or with same size, only changing "randomSize" value...

//Demo
function randSize():Number {
java.lang.Math.random()*50+50;
}

import javafx.scene.paint.Color;
var f:javafx.application.Frame = javafx.application.Frame {
var randomSize = true;
var colors = [Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW, Color.BLACK]
visible:true
width:1000
height:1000
stage: javafx.application.Stage {
content: AutoGridLayout {
maxSize: 1000
horizontal:true
alignment: Alignment.CENTER
content:
for(i in [0..70])
javafx.scene.geometry.Rectangle{
width:if (randomSize) randSize() else 100
height:if (randomSize) randSize() else 100
fill:colors[i mod sizeof colors]
}
}
}
}


Here is an image of the demo, using random size boxes, in horizontal orientation. Notes that each line begins behind the other and each column has same size.

14 novembro 2008

Java/JavaFX integration: Implementing a Java Interface on JavaFX and Multiple Inheritance

One of most curious things of JavaFX is the possibility of multiple inheritance, a feature not present on it's "parent language" Java (take a look here). When the Java was created, the designers decided that multiple inheritance was a confusing and not optimized thing. So, to avoid future problems, they created Interfaces, an virtual collection of method signatures that must be implemented by an concrete class, but there're no self implementation.

Resuming, an Interface can extends various other Interfaces, and a Class can implement various Interfaces, but it can extend only one other class. When a Class implements an Interface, we say that it implements those methods specified on that Interface.

Back to JavaFX: JavaFX hasn't Interfaces, and permits multiple inheritance of classes. This is a valid code:
public class ClassA extends ClassB, ClassC {

}

And that classes can be JavaFX classes or Java classes..

But, if JavaFX doesn't have Interface, how can we implement a Java interface? It's simple: JavaFX consider Java interfaces as abstract classes with abstract methods. So, it's possible to do things like this:
public class MyJavaFXClass extends java.io.Serializable, java.io.InputStream, java.io.StringWriter {
...
}

Serializable and InputStream are java interfaces, and StringWriter is a concrete class.

It works very well, but there are some things that we need to note: some of Java features was not implemented in JavaFX yet, like varargs, enums and generics. Lets look each one:
  • Generics: is not a problem to inheritance. You can simple ignore then when implement a method. Java don't distinguishes between Collection and Collection in a method signature.

  • Enum: can be used on method signatures, but it's manipulated as a common object. You can't access enum values direct, like MyEnum.VALUE1. But you can use myEnumValue.name() method to compare string name of the values. Or you can user MyEnum.values() static method to get all values declared there.

  • Primitive types: It isn't a problem. JavaFX doesn't have primitive types, but if you have a method that receive a primitive type, you can use the relative object. Look for correspondences here.

  • VarArgs: it's the more problematic feature to JavaFX. You can't implement an interface on a concreate class that has methods with varargs! VarArgs aren't arrays! And there are no other form to substitute then. So, to resolve this problem, you can implement an abstract Java class that implements that methods and delegate then to other method, implemented by your JavaFX class. It's your unique solution now. But I think that VarArgs will be implemented in a near future in JavaFX, because it exists in a current-build's reflection class javafx.reflect.FXFunctionType (on 11/14/2008).

  • Arrays attribute: this is a very important problem. If I have a Java interface/class with an method that receipt an array of elements, I can't override that. Example:
Java interface:
public interface TesteI1 {

public void method1(String[] values);

}

JavaFX class:
public class TesteF1 extends TesteI1 {
public function method1(values: String[]):Void {

}
}
This is a wrong code. Why? Because String[] in JavaFX isn't an array, but is a Sequence! To show this, use this code:
var f = ["String1", "String2"];
java.lang.System.out.println("f: {f.getClass()}");

This will show you that f is a com.sun.javafx.runtime.sequence.ArraySequence. You can send it as a parameter to a method that receives an array, like java.util.Arrays.asList(f), but it isn't an array! I think that it's will be corrected in the future.

12 novembro 2008

Creating a Scroller Panel in JavaFX

Today I've created an Scroller Panel in JavaFX. It was a good challenge.

Here is the code:
package brunogrossi.javafx.components;

import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;

/**
* @author Bruno Grossi
*/

private class Scroll extends CustomNode {
public attribute width:Integer;
public attribute height:Integer;
public attribute bodyHeight: Number;

private attribute position:Number;
private attribute barSizeMin:Number=30;
private attribute barSize:Number = 20;//bind if (height/(bodyHeight-height)< position="0"> maxPosition)
this.position=maxPosition
else
this.position=value;
}

postinit {
this.onMousePressed = function(e:javafx.input.MouseEvent):Void {
setPosition(e.getY() - (barSize / 2))
};

this.onMouseDragged = function(e:javafx.input.MouseEvent):Void {
setPosition(e.getY() - (barSize / 2))
};

this.onMouseWheelMoved = function(e:javafx.input.MouseEvent):Void {
setPosition(this.position + e.getWheelRotation())
};
}

protected function create():Node {
Group{
content: [
Rectangle {
width: bind width
height: bind height
fill: Color.WHITE
stroke: Color.WHITE
},
Line {
startX: bind width / 2
startY: bind 0
endX: bind width / 2
startY: bind height
stroke: Color.BLACK
strokeWidth: 1
effect: javafx.scene.effect.Shadow{radius:3}
},
Rectangle {
y: bind position
width: bind width
height: bind barSize
stroke: Color.BLACK
fill: Color.WHITESMOKE
arcWidth:10
arcHeight:20
smooth: true
effect: javafx.scene.effect.Lighting {
light: javafx.scene.effect.light.DistantLight{
azimuth: 60
elevation: 60
}
}
}
]
}

}
}

public class ScrollPanel extends CustomNode {
public attribute x:Integer;
public attribute y:Integer;
public attribute width:Integer;
public attribute height:Integer;

public attribute body: Node;
public attribute showScroll: Boolean = true;

private /*read-only*/ attribute scrollWidth = 15;
private /*read-only*/ attribute scroll:Scroll = Scroll{
translateX: bind width - scrollWidth - 1
width: scrollWidth
height: bind height
bodyHeight: bind body.getBoundsHeight()
};


postinit{
var onMouseWheelMovedOld = this.onMouseWheelMoved;
this.onMouseWheelMoved = function(e:javafx.input.MouseEvent):Void {
scroll.onMouseWheelMoved(e);
if (onMouseWheelMovedOld!=null)
onMouseWheelMovedOld(e);
};
}

protected function create():Node {
Group {
translateX: bind x
translateY: bind y
clip: Rectangle{width: bind width height: bind height}
content: [
Group{
clip: Rectangle{width: bind this.getInternalBoundsWidth() height: bind height}
content: Group{
content: body
translateY: bind -( scroll.position * (body.getBoundsHeight() - scroll.height)) / scroll.maxPosition
}
},
if (showScroll) scroll else null,
]
}
}

public function getInternalBoundsWidth(): Number {
if(showScroll) width - scrollWidth - 1 else width;
}
public function getInternalBoundsHeight(): Number {
height
}
}

/*This is for test */
var s = ScrollPanel {
width:100
height: 200
var colors = [Color.RED, Color.BLUE, Color.DARKGOLDENROD, Color.ALICEBLUE, Color.ANTIQUEWHITE, Color.AQUA, Color.AQUAMARINE, Color.AZURE, Color.BEIGE, Color.BISQUE, Color.CHOCOLATE]
body: Group{
content:
for(i in [0..30]) {[Rectangle{y: i * 10 width:99 height:10 fill:colors[
i mod sizeof colors] stroke:Color.BLACK},
javafx.scene.text.Text{y:i * 10 content:"{i}"}]
}
}
}

javafx.application.Frame {
visible: true
stage: javafx.application.Stage{
content: s
}
}

29 outubro 2008

Good surprise migrating from Eclipse to Netbeans 6.1

Now that we decided to use JavaFX to our new Interface, I will need to use Netbeans. There aren't a functional plugin to Eclipse.

We have more than 20 projects that are Eclipse projects builded with Maven2 (so, Maven projects). Eclipse don't manages Maven Projects, but eclipse projects can be adapted to Maven projects. Maven is executed as a "External Tool".

Actually, this projects are organized in 2 root projects that are composed by Modules. On Eclipse, I need to open each one as a eclipse project.

Now, I installed Maven2 plugin to Netbeans and checked out the projects from Subversion repository (see this to help with Subversion + SSH). And for my surprise, Netbeans recognized the two projects as Netbeans/Maven projects automatically, show me all modules and I could open each module as a new Project. When I configure my Maven2 repository, all compiled OK. It's simple!

28 outubro 2008

First JavaFX experiment

I constructed my first JavaFX example to became to answer some of my questions posted here and to "feel" this script language. I will explain it above:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package brunogrossi.javafx;

import javafx.application.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.geometry.*;
import javafx.scene.text.*;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.ext.swing.*;
import javafx.animation.*;

var slider = Slider{minimum: 0 maximum: 360 value: 20};
var canvas:Node = ComponentView{component:Canvas {
content: Group {
content: [
ComponentView {
component: slider
translateX: 10
translateY: 70
},
Rectangle {
x: 10 y: 10
width: 200 height: 50
arcWidth: 15 arcHeight: 15
fill: Color.LIGHTGRAY
},
Text {
x: 110 y: 40
content: "JavaFX"
font: Font {
name: "Serif"
size: 20
style: FontStyle.BOLD
}
fill: Color.YELLOW
horizontalAlignment: HorizontalAlignment.CENTER
effect: DropShadow {
offsetX: 2 offsetY: -2 radius: 6
color: Color.BLACK
}
}
]
effect: Lighting {
light: DistantLight {
azimuth: bind slider.value
}
}
}

}};

public class TitleBar extends CustomNode {

private attribute frame: Frame;
private attribute width: Number = 10;
private attribute dragStartPoint: java.awt.geom.Point2D;
attribute bgcolor: Color = Color.BLUE;
attribute fgcolor: Color = Color.WHITE;

private attribute iRotate = 0;
private attribute iRotating = false;

public function create():Node {
this.onMousePressed = function(e:MouseEvent):Void {
this.dragStartPoint = e.getLocalXY();
}
this.onMouseReleased = function(e:MouseEvent):Void {
this.dragStartPoint = null;
}
this.onMouseDragged = function(e:MouseEvent):Void {
this.frame.x = (e.getScreenX() - this.dragStartPoint.getX() as Integer);
this.frame.y = (e.getScreenY() - this.dragStartPoint.getY() as Integer);
}

return Group {
content: [
Rectangle{
x: 0
y: 0
width: bind width
height: bind frame.height

fill: LinearGradient {
startX: 0.0
startY: 0.0
endX: 0.0
endY: 1.0
proportional: true
stops: [
Stop { offset: 0.0 color: Color.WHITE },
Stop { offset: 1.0 color: bind bgcolor }
]
}
},

javafx.scene.geometry.Polygon {
var t = Timeline {
autoReverse: false

keyFrames: [KeyFrame{time : 0s
values: iRotate => 0.0},

KeyFrame{time : 2s
values: iRotate => 180.0 tween Interpolator.LINEAR
action: function() {this.iRotating = false}
}
]//keyFrames
}//Timeline;
transform: javafx.scene.transform.Rotate{
angle: bind iRotate
x: 2+(width-2-2)/2
y: 2+(width-2-2)/2
};
stroke: Color.BLACK
fill: Color.BLANCHEDALMOND
points: [
2, 2,
2, width-2,
width-2, width-2,
width-2, 2,
2, width-2,
2, 2,
width-2, width-2,
width-2, 2
]
onMouseClicked: function(e: MouseEvent):Void {
//Interessant point to be analized: how to call function on variables.
if (frame.closeAction!=null)
frame.closeAction();
}
onMouseEntered: function(e: MouseEvent):Void {
if (not this.iRotating) {
this.iRotating = true;
t.start();
}
}
},
Text {
content: bind frame.title
fill: bind fgcolor

textOrigin: TextOrigin.BASELINE
rotate: -90
x: 0
y: 0
translateX: bind width
translateY: bind frame.height
}
]
}
}
}

public class MyFrame extends Frame {

postinit {
insert TitleBar{
frame: this
bgcolor:Color.BLACK
} before this.stage.content[0];
}

function createWindow(): java.awt.Window {
//this is a trick, because Frame don't has a undecorated attribute.
var f: java.awt.Window = super.createWindow();
(f as javax.swing.JFrame).setUndecorated(true);
return f;
}

}

var f:Frame = MyFrame {
title: "Example"
width: 228
height: 140
opacity: 0.5
closeAction: function() {
java.lang.System.exit(1);
}
stage: Stage {
content: [canvas]
}

}

f.visible=true;

My goal with this test was to create a custom title bar to my Frame. And a lateral (rotated) title bar! So, the slider and canvas variables are part of the body of my frame, and was adapted from this tutorial, and are doesn't explained here. The content of MyFrame must be anything...

First, I created a CustomNode called TitleBar (lines 54-144). The javafx.scene.CustomNode defines a Node that can be customized, only defining the create():Node function.

This TitleBar class has an attribute frame, that will be the frame that this component will control. The TitleBar will be associated with the frame on MyFrame's postinit instruction (lines 146-153). The magic is to insert a new TitleBar object as a first element on this frame's Stage content. Here, we define the frame as this frame, and the background's color. Only this..

In MyFrame's definition there is other function defined: createWindow(). I just defined it to set the window as a Undecorated window. This is a trick. As I report on JavaFX forum, the old javafx.ui.frame has an attribute to define it as a undecorated frame, but the new javafx.application.Frame and javafx.ext.swing.SwingFrame hasn't. That's how I do to turn the frame undecorated.

The TitleBar class has a reference to the Frame that it will command and an MouseListener that change frame's position based on Drag events. The Text value binds to Frame's title and close button's event simply calls the Frame's closeAction() function. So, we don't need to configure the title bar, because it uses "frame's configurations".

By the away, in the close button, I created an effect to test simple animation. When the cursor is over square, it will rotate. For my surprise, the construction that I used on previews version, var i=[0..360] dur 1000 don't work now. I need to use a javafx.animation.Timeline (lines 99-110). Timeline is a good class, but for simple animations, the other construction is better and simple... Is there another construction like that?

That's the window: