A Note from Jim Weaver:
First of all, I would like to wish you a Happy and Blessed New Year! I'm excited about the momentum that JavaFX has achieved since its 1.0 release in December 2008, and about its increased adoption for RIA and mobile development in 2009.
Also, I'd like to introduce Dean Iverson, the author of today's article, who for many of you needs no introduction because he's been active in the Java/Swing/JavaFX community for some time now. Dean will be a regular contributor to the JavaFXpert blog, as well as to the JFXtras project that he mentions in todays article.
Dean has been writing software professionally for over 15 years. In that time he has worked on everything from games, to driving simulators, to large-scale enterprise applications and just about everything in between. Currently, he is employed by the Virginia Tech Transportation Institute where he has a cool job (of which I'm jealous) as a senior researcher where he is working on rich client applications. He also has a small software consultancy called Pleasing Software Solutions which he co-founded along with his wife who, by his own admission, is the brains of the outfit and the better programmer. I personally think that his wife develops these JavaFX examples and that he just writes about them :-) Also, he is the Technical Reviewer of the upcoming Pro JavaFX book that Weiqi Gao, Stephen Chin and I are currently writing. With that being said, please enjoy Dean's first post on the JavaFXpert blog:
JavaFX Skins Game
Simon Morris, author of the upcoming JavaFX in Action book, has just written a very informative post on his blog. He shows how to create a custom progress bar that is skinnable using JavaFX's support for CSS. It's definitely worth a read. Using CSS you can customize just about every aspect of the rectangles that Simon uses in his progress meter: their size, color, even how many there are! It's the ultimate in flexibility.
A New Shape
But what if you want to use a different shape entirely? Even that is possible without having to recompile Simon's original code. For example, there is a new shape in the JFXtras project called the VariableCornerRectangle. This shape is a rectangle that can have any combination of rounded or square corners. It looks like this:
The code to produce the shape above looks like this:
VariableCornerRectangle {
ulArc: 80
urArc: 0
lrArc: 80
llArc: 0
width: 200
height: 200
fill: Color.CORNFLOWERBLUE
stroke: Color.BLUE
strokeWidth: 5
}
We just use the shape's corner arc attributes to set the upper-left and lower-right corners as arcs with a radius of 80 pixels. We then set the other corners to be square by using an arc size of 0.
A New Skin
So how do we use this new shape in Simon's progress meter? First, we create a new skin class. It will be almost exactly the same as Simon's, but it will use the new shape.
public class VariableCornerProgressSkin extends Skin {
public var boxCount:Integer = 10;
public var boxWidth:Number = 15;
public var boxHeight:Number = 20;
public var boxHGap:Number = 2;
public var unsetHighColor:Color = Color.YELLOW;
public var unsetMidColor:Color = Color.GREEN;
public var unsetLowColor:Color = Color.DARKGREEN;
public var setHighColor:Color = Color.ORANGE;
public var setMidColor:Color = Color.RED;
public var setLowColor:Color = Color.DARKRED;
var boxValue:Integer = bind {
var p:Progress = control as Progress;
var v:Number = (p.value - p.minimum) / (p.maximum - p.minimum);
(boxCount * v) as Integer;
}
init {
def border:Number = bind boxWidth / 10;
def arc:Number = bind boxWidth / 2;
def lgUnset:LinearGradient = bind makeLG(unsetHighColor,unsetMidColor,unsetLowColor);
def lgSet:LinearGradient = bind makeLG(setHighColor,setMidColor,setLowColor);
scene = HBox {
spacing: bind boxHGap;
content: bind for(i in [0..<boxCount]) {
Group {
content: [
VariableCornerRectangle {
width: bind boxWidth;
height: bind boxHeight;
ulArc: bind arc;
lrArc: bind arc;
urArc: 0
llArc: 0
fill: bind if(i < boxValue) setLowColor else unsetLowColor;
},
VariableCornerRectangle {
translateX: bind border;
translateY: bind border;
width: bind boxWidth - border * 2;
height: bind boxHeight - border * 2;
ulArc: bind arc;
lrArc: bind arc;
urArc: 0
llArc: 0
fill: bind if(i < boxValue) lgSet else lgUnset;
}
]
}
}
}
}
function makeLG(c1:Color,c2:Color,c3:Color) : LinearGradient {
LinearGradient {
endX: 0;
endY: 1;
proportional: true;
stops: [
Stop {
offset:0;
color: c2;
},
Stop {
offset:0.25;
color: c1;
},
Stop {
offset:0.50;
color: c2;
},
Stop {
offset:0.85;
color: c3;
}
]
}
}
}
A Small Change Of Style
Simon's original code used the following CSS to style the three different progress meters in his sample program.
"Progress" {
boxWidth: 15;
boxHGap: 2;
setHighColor: yellow;
setMidColor: red;
setLowColor: darkred;
unsetHighColor: cyan;
unsetMidColor: blue;
unsetLowColor: darkblue;
}
"Progress"#testId {
boxWidth: 25;
boxHeight: 30;
boxCount: 7;
boxHGap: 1;
unsetHighColor: white;
unsetMidColor: silver;
unsetLowColor: dimgray;
}
"Progress".testClass {
boxWidth: 7;
boxHGap: 2;
boxCount: 20;
setHighColor: yellow;
setMidColor: limegreen;
setLowColor: darkgreen;
}
This stylesheet produced the three progress meters in Simon's original example.
All we have to do to use our new skin is to add a new CSS selector. In this case I want to change
the skin of the progress meter with the id of testId
. So I'll add a new selector just
above the existing one that applies the new skin.
"Progress"#testId {
skin: javafx_css.VariableCornerProgressSkin;
}
"Progress"#testId {
boxWidth: 25;
boxHeight: 30;
boxCount: 7;
boxHGap: 1;
unsetHighColor: white;
unsetMidColor: silver;
unsetLowColor: dimgray;
}
And voila, we have a new progress meter! Notice that the middle meter (whose id is testId
)
is the only one that changed. Click the Java Web Start Launch icon below to see it in action.
I have used the fully qualified path name for the VariableCornerProgressSkin class in the selector's
skin
declaration. In this case, the class is located in my javafx_css package.
It is important that the selector with the skin:
declaration be a separate selector and be
placed above any other selectors that match the same control. Otherwise all of the style declarations
will be applied to the original skin instead of the new one. Apparently the skin is only replaced after
the entire selector is parsed so any styles contained within the same selector will be applied to the
existing skin rather than the new one.
Thanks again to Simon Morris for the original example!
Regards, and Happy New Year,
Dean Iverson
JavaFXpert.com
Well, I don’t know if this rule holds true across the board or not, but it certainly seems to be the case for Bowman & Landes Free Range Turkey Farm in New Carlisle, OH. Recently I drove through two hours of snowy Ohio scenery to meet up with the folks at Bowman & Landes, who gave me the grand tour of their turkey farm.
Posted by: homes for sale costa rica | April 24, 2010 at 04:28 PM
Andrew,
You are correct. As far as I know, these types of boolean expressions are not yet supported in JavaFX controls.
However, you can use any public boolean var on your control class as a pseudo-class (i.e. MyControl:myBooleanVar). Although, there are a few bugs with pseudo-classes here and there (they don't work on shapes, for example).
Regards,
Dean
Dean
Posted by: Dean Iverson | January 06, 2009 at 11:35 PM
Hi Jim, thanks a HEAP for your posts on CSS they are most helpful indeed (and upstream thanks to Simon). Now that I have this working I can see an *almost* perfect separation between Model+Controller & View....
I'm impressed by Control Pseudoclasses (hover, pressed, selected). This allows you to style the Control if any of the Pseudoclasses are true
e.g.
"MyControl":pressed { fill: GREEN}
However, I'd really like to know how to add a custom Pseudoclass to a control. This would allow for a far more distinct seperation between Control+Model and View(css/skin).
I've posted this on the JavaFX developer forum here: http://forums.sun.com/thread.jspa?messageID=10564984�
The example requirement is to be able to create a stateful Control with Pseudoclass called "state" with possible values "LOW:MED:HIGH"
..and css...
"MyControl":${state=="LOW" { fill: Yellow}
"MyControl":${state=="MED" { fill: Orange}
"MyControl":${state=="HIGH" { fill: Red}
This would be a huge leap forward for true "cascading" stylesheet support (and JavaFX) :) Apparently JavaCSS supports this but it might not be in JavaFX yet (https://javacss.dev.java.net/docs/file_format.html)
Thanks Again.
Posted by: Andrew | January 06, 2009 at 08:12 PM
Mike,
Normally all of the control's logic will be contained in the control itself. If you look at Simon's post you can see that the min, max, and current value are kept in the control. This separation will allow the skins to handle user input and then call the control's logic as appropriate.
I hate to bring out an old fallback, but you can kind of think of the skin as the view/controller and the control as the model in MVC terms.
Dean
Posted by: Dean Iverson | January 01, 2009 at 04:56 PM