Saturday, November 17, 2012

JavaFX 2.0 CSS Styling Part 2 JavaFX 2.0 CSS Styling Part 1 JavaFX 2.0 Custom ComboBox JavaFX 2.0 Deck Pane Control

I got a lot of requests for part 2, hope it will be helpful as much as part 1.

In this part i will cover Background images and image borders Region CSS properties. All Java code from previous post remains same (ExtendedApplication.java and CSSTest.java) i will only change CSS code. Used images are in same directory/package as css file. In this part i will leave a lot of stuff to play with it.

Note: This are all mine tests it does not mean that all stuff said here is 100 percent correct because i could not find any examples/tutorials for properties explained here, it was helpful to play with these and sometimes hard to understand some of them but when you start playing it is not so hard.

So lets begin with some examples, here is example with 2 background images and some background colors, insets have negative values try to use positive and you will know why I have used negative. If you read carefully Region CSS properties you can see that you can add as many as you want urls for background images.  Also <bg-position> values are a little bit tricky, type  <size> can be used or other values like “left top”, “left”, “center”, etc can also be used, feel free to play with them.

Also play with <repeat-style> values and with <bg-size> values, <bg-size>  (width and height) or you can use auto, cover, contain.

.custom-node {-fx-background-color: skyblue, derive(skyblue, 25%);-fx-background-insets: -20, -10;-fx-background-image: url("bg2.jpg"), url("DukeWithHelmet.png");-fx-background-position: left top, center;-fx-background-repeat: no-repeat;-fx-background-size: cover, auto;}

This is result with just few lines of CSS.

Try to lower background insets to : -2, -1 to see result.

Another example with more then 1 image background

.custom-node {-fx-background-color: skyblue, derive(skyblue, 25%);-fx-background-insets: 0, 10;-fx-background-image: url("bg2.jpg"), url("mybg.jpg"), url("mybg.jpg"), url("bg2.jpg");-fx-background-position: 20 20, 160 20, 20 160, 160 160;-fx-background-repeat: no-repeat no-repeat;-fx-background-size: 120 120;}

The result is 4 images positioned in “rectangle” with 2 background colors.

With adding one more image a really nice results can be accomplished

.custom-node {-fx-background-color: skyblue, derive(skyblue, 25%);-fx-background-insets: 0, 10;-fx-background-image: url("bg2.jpg"), url("mybg.jpg"), url("mybg.jpg"), url("bg2.jpg"), url("DukeWithHelmet.png");-fx-background-position: 20 20, 160 20, 20 160, 160 160, center;-fx-background-repeat: no-repeat no-repeat;-fx-background-size: 120 120, 120 120, 120 120, 120 120, auto;}

Well in my opinion it is best to play a lot with css you will learn better, and now a little bit about image borders. ImageBorders have similar CSS properties like ImageBackground but sometimes they are a little bit tricky. So you sometimes will get weird and unexpected results well they were unexpected until i figured out how to use ImageBorders properly. Here are some of the unexpected results that i have experienced.

Here is little warning/tip : I noticed that border-image-slice values have to be “equal or greater then” their corresponding border-image-width values because in most cases i got results similar to those ugly borders shown in images. Third image shows result without specified border-image-slice values even after changing repeat result remains same. And sometimes if slice values are too big (lets say bigger then half of image) i also got similar results like in third image. For border-image-slice i recommend reading Region CSS documentation.

It is time to show some nice examples with image-borders.

.custom-node {    -fx-border-image-source: url("bg2.jpg"), url("mybg.jpg");    -fx-border-image-insets: 10, 40;    -fx-border-image-repeat: stretch, stretch;    -fx-border-image-width: 20, 20;    -fx-border-image-slice: 25, 30;}

And a bit more complex example :

.custom-node {    -fx-background-image: url("DukeWithHelmet.png");    -fx-background-position: center;    -fx-background-repeat: no-repeat;    -fx-background-size: auto;    -fx-border-image-source: url("bg2.jpg"), url("mybg.jpg");    -fx-border-image-insets: 10, 30;    -fx-border-image-repeat: stretch, stretch;    -fx-border-image-width: 50, 10;    -fx-border-image-slice: 50, 20;}

I have not explained -fx-shape property because if you know a little bit of SVG it won’t be hard to understand it. If you read/use CSS reference you will see that some of Region properties are “similar” to Shape properties like dash-array etc. So i hope Shape properties won’t be so hard to learn. Node CSS properties are practically well known and used in your applications if not in CSS then in some javaFX code.

Next part continues the CSS  styling but in other ways with more java code and not so much CSS code.

Feel free to leave come some comments, critics, or anything and if i forgot to cover something feel free to contact me by comment or mail.

I saw a lot of forums that some people are having big problems with CSS and am not sure why.

Am not CSS expert but i have learned css with javaFX I did not know how to use it before i met javaFX, i did not know what are selectors, pseudo classes…

In this post I will talk about my experience with CSS and JavaFX, how i learned it, my experiments, what I have been noticing, trying to accomplish and other stuff.

I remember when i met javaFX 1.2 CSS seemed so hard to me i though i will never try to use CSS, but i have tried and i like it. Here  is  some basic tutorial on css for more advanced  use this .

For me the most important class is Region and Node it is always good to know/remember  their CSS properties others like Shape will be easy to remember later when u learn these two.

I will start with Region/StackPane as first class in this post.

I wrote ExtendedApplication class so I don’t have to type same code allover again, i will start will simple StackPane in root of scene, why StackPane well SkinBase extends StackPane. Only CSS property in StackPane is -fx-alignment and default value is center (we all know that stack positions children in the center), Regions don’t have that property.

Here is code for ExtendedApplication

package cssPlayground.utils;import javafx.application.Application;import javafx.scene.Scene;import javafx.scene.layout.Pane;import javafx.scene.layout.StackPane;import javafx.stage.Stage;/** * * @author jojorabbit */public abstract class ExtendedApplication extends Application {    protected double DEFAULT_APP_WIDTH = 500;    protected double DEFAULT_APP_HEIGHT = 400;    protected Stage stage;    protected Pane root;    protected Scene scene;    @Override    public void start(Stage primaryStage) {        preSetup(primaryStage);        setup();        postSetup();    }    protected abstract void setup();    protected void preSetup(Stage primaryStage) {        stage = primaryStage;        stage.setTitle(getAppTitle());        root = new StackPane();        root.getStyleClass().add("root-pane");        scene = new Scene(root, getAppWidth(), getAppHeight());        stage.setScene(scene);        setupCss();    }    protected void postSetup() {        stage.centerOnScreen();        stage.setVisible(true);    }    // override to change APP WIDTH    protected double getAppWidth() {        return DEFAULT_APP_WIDTH;    }    // override to change APP HEIGHT    protected double getAppHeight() {        return DEFAULT_APP_HEIGHT;    }    // override to set App Title    protected String getAppTitle() {        return "Test";    }    // override to add CSS styles    protected void setupCss() {    }}

And the starting sample code

package cssPlayground;import cssPlayground.utils.ExtendedApplication;import javafx.scene.Group;import javafx.scene.layout.StackPane;/** * * @author jojorabbit */public class CssTest001 extends ExtendedApplication {    StackPane cssNode;    public static void main(final String[] args) {        launch(CssTest001.class, args);    }    @Override    protected void setup() {        cssNode = new StackPane();        cssNode.getStyleClass().add("custom-node");        cssNode.setPrefSize(300, 300);        root.getChildren().add(new Group(cssNode)); // adding to group so root does not resize custom node    }    @Override    protected String getAppTitle() {        return "CSS Test 001";    }    @Override    protected void setupCss() {        scene.getStylesheets().add("/cssPlayground/csstest001.css");    }}

csstest001.css

.custom-node {    -fx-background-color: skyblue; /* set background to skyblue */    -fx-border-color: red; /* set border to red*/    -fx-border-width: 3; /* border width 3px */}

And resulting image with comments

This is just the most simple example, it is good to know color functions and other color specifications.
Most (I would rather say all) of JavaFX controls are made of multiple backgrounds.

Lets see how to make multiple backgrounds.

Adding multiple backgrounds is easy just specify multiple colors in -fx-background-color and add some insets CSS ref says for insets : “A series of size values or sets of four size values, separated by commas. A single size value means all insets are the same. Otherwise, the four values for each inset are given in the order top, right, bottom, left. Each comma-separated value or set of values in the series applies to the corresponding background color.”

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%), derive(skyblue, 50%), derive(skyblue, 75%);    -fx-background-insets: 20, 40, 60, 80;    -fx-border-color: red; /* set border to red*/    -fx-border-width: 3; /* border width 3px */

result is this :

for example adding negative values to insets means insets “go” to other way try: -fx-background-insets: -20, 40, 60, 80;

first rectangle (one with not derived skyblue color will go outside of border), most of javaFX controls are made like this but with low insets values

will show how to accomplish that later.

Also we can add multiple borders to custom node, if we want to accomplish that each “skyblue” rectangle has border then border insets have to be added too

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%), derive(skyblue, 50%), derive(skyblue, 75%); /* set background to skyblue */    -fx-background-insets: 20, 40, 60, 80;    -fx-border-color: red, yellow, green, blue; /* set border to red*/    -fx-border-insets: 20, 40, 60, 80;    -fx-border-width: 3; /* border width 3px */}

Nothing much to explain only to say if border insets is not same as background insets result won’t be as expected.

Well lets try a little bit more complex (I mean complex for beginners) example here is what i want to accomplish.
tips
1. example: use -fx-border-radius and -fx-background-radius -> see Region css properties
2. example: use border color
3. example: use insets

Answers:
1. Example:

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%), derive(skyblue, 50%), derive(skyblue, 75%); /* set background to skyblue */    -fx-background-insets: 20, 40, 60, 80;    -fx-background-radius: 20 0 0 0, 0 20 0 0, 0 0 20 0, 0 0 0 20;    -fx-border-color: red, yellow, green, blue; /* set border to red*/    -fx-border-insets: 20, 40, 60, 80;    -fx-border-radius: 20 0 0 0, 0 20 0 0, 0 0 20 0, 0 0 0 20;    -fx-border-width: 3; /* border width 3px */}

2. Example:

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%), derive(skyblue, 50%), derive(skyblue, 75%); /* set background to skyblue */    -fx-background-insets: 20, 40, 60, 80;/*    -fx-background-radius: 20 0 0 0, 0 20 0 0, 0 0 20 0, 0 0 0 20;*/    -fx-border-color: red transparent red transparent, yellow, transparent green transparent green, blue transparent blue transparent; /* set border to red*/    -fx-border-insets: 20, 40, 60, 80;/*    -fx-border-radius: 20 0 0 0, 0 20 0 0, 0 0 20 0, 0 0 0 20;*/    -fx-border-width: 3; /* border width 3px */}

3. Example:

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%), derive(skyblue, 50%), derive(skyblue, 75%); /* set background to skyblue */    -fx-background-insets: 20, 40 40 20 40, 40 60 60 60, 80 80 60 80;/*    -fx-background-radius: 20 0 0 0, 0 20 0 0, 0 0 20 0, 0 0 0 20;*/    -fx-border-color: red, yellow yellow transparent yellow, transparent green green green, blue blue transparent blue;    -fx-border-insets: 20, 40 40 20 40, 40 60 60 60, 80 80 60 80;/*    -fx-border-radius: 20 0 0 0, 0 20 0 0, 0 0 20 0, 0 0 0 20;*/    -fx-border-width: 3; /* border width 3px */}

Well with CSS a lot of stuff can be accomplished easy like shaping see -fx-shape property. To shape our node to circle we can use high background-radius values.
I my last post about ListComboBox you can see how is arrow/triangle made with -fx-shape, for shapes i use paint, illustrator or similar tools to get x/y coordinates or to get SVG path.

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%), derive(skyblue, 50%), derive(skyblue, 75%); /* set background to skyblue */    -fx-background-insets: 0, 60, 80, 100;    -fx-background-radius: 20.0em, 0, 0, 0; /* 1.0em == 12 px*/ /* 20.0em == 240px*/    -fx-border-color: red, yellow, green, blue;    -fx-border-insets: 0, 60, 80, 100;    -fx-border-radius: 20.0em, 0, 0, 0;    -fx-border-width: 3; /* border width 3px */}

If you like to play with CSS you will see how easy you can make a lot of stuff with it.
Example:

I will explain segments function later in some other post you will see how similar it is to one of shape properties
For derive function see color functions in CSS Reference

.custom-node {    -fx-background-color: skyblue, derive(skyblue, 25%); /* set background to skyblue */    -fx-background-insets: 0, 20;    -fx-background-radius: 10, 0;    -fx-border-color: blue, red;    -fx-border-style: segments(40, 4, 20, 4) phase 12 line-cap butt, segments(26, 4, 26, 4) phase 4 line-cap round;    -fx-border-insets: 0, 20 22 20 22;    -fx-border-radius: 10, 0;    -fx-border-width: 5, 5 0 5 0;}

Well that is it for the first part of the CSS Styling series, fee free to leave come some comments, critics, … If i forgot to cover something feel free to contact me.
Next part continues Region CSS properties for image backgrounds, image borders. You can found it here (Currently in progress).

In javaFX 2.0 there is ChoiceBox control it is practically made of 2 items, one is label and other on is ContextMenu. Somewhere I saw somebody said something like “use ChoiceBox with low amount of data and ListView for high amount of data” but he did not said why. I was curious  why not is there some limit or what, so i tried what would happened if I insert at least 100 items in Choice Box.

Lets try code example:

<br />ChoiceBox<String> cb = new ChoiceBox<String>(FXCollections.observableArrayList(Font.getFamilies())); // just add to scene root 

I need nice ChoiceBox with font list in application that am currently developing so I decided to take a little test.

When I saw result i know why someone said use it only with low data 10-20 items maximum. You can see result by yourself or on down image.

That really disappointed me i need ChoiceBox with more then 150 fonts (all system fonts + custom fonts) and i don’t want to show big ListView or huge context menu because it looks ugly. After little bit of digging testing and experimenting. I have decided to create my own ComboBox.

Some nice features of ComboBox that i want to see:

  • having scrolling ListView (we all know that ListView can have CellFactory) that is great because i want to see font preview in list
  • some nice animation when showing or hiding ListView
  • setting size of visible ListView
  • getting selectedItem from ComboBox
Here is code for ListComboBox Control with some useful public methods, it has pseudo class showing:
<br />package playground.control;</p><p>import java.util.List;<br />import javafx.beans.property.BooleanProperty;<br />import javafx.beans.property.DoubleProperty;<br />import javafx.beans.value.InvalidationListener;<br />import javafx.beans.value.ObservableValue;<br />import javafx.collections.ObservableList;<br />import javafx.scene.control.Control;<br />import javafx.scene.control.ListCell;<br />import javafx.scene.control.ListView;<br />import javafx.scene.control.MultipleSelectionModel;<br />import javafx.util.Callback;</p><p>/**<br /> *<br /> * @author jojorabbit<br /> *<br /> */<br />public class ListComboBox<T> extends Control {</p><p>    private static final String LIST_COMBO_BOX_STYLE_CLASS = "list-combo-box";<br />    protected static final String PSEUDO_CLASS_SHOWING = "showing";<br />    protected ListView<T> listView;<br />    protected DoubleProperty visibleHeight = new DoubleProperty(0.0d);<br />    private BooleanProperty showing = new BooleanProperty() {</p><p>        @Override<br />        protected void invalidated() {<br />            ListComboBox.this.impl_pseudoClassStateChanged(PSEUDO_CLASS_SHOWING);<br />        }<br />    };</p><p>    public ListComboBox() {<br />        init();<br />        initListeners();<br />        visibleHeight.set(200.0d); // set default height to 200.0<br />    }</p><p>    public boolean isShowing() {<br />        return showing.get();<br />    }</p><p>    public BooleanProperty showingProperty() {<br />        return showing;<br />    }</p><p>    @Override<br />    public void impl_getPseudoClassState(List<String> list) {<br />        super.impl_getPseudoClassState(list);<br />        if(isShowing()) {<br />            list.add(PSEUDO_CLASS_SHOWING);<br />        }<br />    }</p><p>    private void init() {<br />        getStyleClass().add(LIST_COMBO_BOX_STYLE_CLASS);<br />        listView = new ListView<T>();<br />        listView.getStyleClass().add("list");<br />    }</p><p>    public void setItems(ObservableList<T> items) {<br />        listView.setItems(items);<br />    }</p><p>    /**<br />     * Sets visible size of wrapped ListView<br />     * @param value<br />     */<br />    public void setVisibleHeight(double value) {<br />        visibleHeight.set(value);<br />    }</p><p>    /**<br />     * Add cell factory to listView<br />     * @param value cellFactory<br />     */<br />    public void setCellFactory(Callback<ListView<T>, ListCell<T>> value) {<br />        listView.setCellFactory(value);<br />    }</p><p>    /**<br />     * Get listView selections Model<br />     * @return selection model<br />     */<br />    public MultipleSelectionModel<T> getSelectionModel() {<br />        return listView.getSelectionModel();<br />    }</p><p>    /**<br />     * Get selected item<br />     * @return selected item<br />     */<br />    public T getSelectedItem() {        return listView.getSelectionModel().getSelectedItem();    }    /**     * Get selected index     * @return selected index     */    public int getSelectedIndex() {        return listView.getSelectionModel().getSelectedIndex();    }    private void initListeners() {        visibleHeight.addListener(new InvalidationListener<Number>() {            @Override            public void invalidated(ObservableValue<? extends Number> observable) {                listView.setPrefHeight(visibleHeight.get());                listView.setMinHeight(visibleHeight.get());                listView.setMaxHeight(visibleHeight.get());            }        });    }}
All methods are described in comments.
Other things i want to see in my ComboBox are nice label and some arrow pointing down, that can be done with -fx-shape region css property and a little bit of SVG knowledge.
At begging i came out with these skin variables :
<br />private ListView<T> listView;<br />private Timeline timeline; // timeline for some animations<br />private Label selectedLabel; // label<br />private StackPane arrow; // "holder" for arrow shape<br />private StackPane line; // "holder" for line shape<br />private static final Duration ANIMATION_DURATION = Duration.valueOf(300.0d); // default animation duration<br />
The main problem with custom Regions is layoutChildren method, used to layout nodes in custom Region I would say that is “must to override” method if you want to accomplish nice custom layout.
There are two important and useful methods in Region class, i will just mention them for better understanding consult API documentation. Methods are layoutInArea and positionInArea.
After few hours of playing and calculating, then adding some CSS i came out with screenshot below.
Sources can be found in github repository.
Hope you like it and feel free to comment/criticize/request features/request custom nodes/report bugs, etc.

EDIT:  28.06.2011.

I have added some changes to current comboBox, some of them are removing that ugly line, adding new StackPane that holds arrow (styleclass = arrow-container), making line with css -fx-background property, removed line from layout calculations, added arrowContainer to layout calculations, adding mouse event handler only on arrow Stack, so clicking only and only on arrow stack will result “listView – pop-up” to appear.
Here are some changes added to CSS:
<br />.list-combo-box .arrow-container {<br />    -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;    -fx-background-insets: 0 0 0 0, 0, 1, 2;<br />    -fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0; /* top right and bottom right corners are rounded*/    -fx-alignment: CENTER;<br />    -fx-padding: 10;<br />}<br />

Some of changes in source :

<br /> StackPane arrow = new StackPane();<br /> arrow.getStyleClass().add("arrow");</p><p> arrowContainer = new StackPane();<br /> arrowContainer.getStyleClass().add("arrow-container");<br /> arrowContainer.getChildren().add(arrow); 
And some images of control in action, well it looks much better then before with that line. If you see some <p> < br> tags in post i will correct them later.
Hope you like it.
EDIT: 07.07.2011.
Here are some new modifications to combobox it looks and works nice code is git is updated to current version. All comments and critics are welcome and if you find any other useful features fell free to contact me.
Some other features that i want to make are only focus traversing for now maybe i will find something else later.
Here are some images of control with comments.
Hope you like it.

I experiment a lot with everything including new javaFX. Few weeks ago I had to make some “Container” that can change nodes with fade animation. I was inspired by DeckNode from here.

After making it i noticed that i miss some nice features, so i made new Control with some better features like Scale between node changes etc.

Well DeckPane is simple control that shows only one node at a time. Animations between changes of current visibleNode are FADE, SCALE, PERSPECTIVE defined in enum AnimationMode, animation duration can be set via setAnimationDuration(Duration value), default is 500ms, animation duration represents duration of “in” and “out” part,  so the real duration is 1000ms. Setting current visible node is done with setVisibleNodeID(String nodeId) method. The code is still in debug phase you will see a lot of printouts in console, some parts of code are not so clean.

Here is demo video with  that show functionality of Deck Pane Control. Source can be found in git repository.

Hope you like it and feel free to comment/criticize/request features/report bugs, etc.

Here are the pictures of how project looks in netbeans in case you get any errors.


Source : jojorabbitjavafxblog[dot]wordpress[dot]com

2 comments:

  1. Have you been thinking about the power sources and the tiles whom use blocks I wanted to thank you for this great read!! I definitely enjoyed every little bit of it and I have you bookmarked to check out the new stuff you post
    Selenium training in Chennai
    Selenium training in Bangalore
    Selenium training in Pune
    Selenium Online training

    ReplyDelete
  2. Thanks for the informative article. This is one of the best resources I have found in quite some time. Nicely written and great info. I really cannot thank you enough for sharing.



    devops training in chennai | devops training in anna nagar | devops training in omr | devops training in porur | devops training in tambaram | devops training in velachery



    ReplyDelete