Is there a way to only "render" an component in Javafx if a condition is met?
I am interested in doing a user interface with different roles and just add
a component if the role allows it, also I would like to keep working with FXML.
I haven't read about anything like that.
Bind visibility
Bind the visible property of your component to a BooleanExpression representing the condition under which it should be visible.
FXML can do binding, but note that as of JavaFX 2.2 "Only simple expressions that resolve to property values or page variables are currently supported. Support for more complex expressions involving boolean or other operators may be added in the future."
If you don't bind in FXML, you can bind in Java code using the JavaFX API.
node.visibleProperty().bind(conditionProperty);
If you don't want the invisible property to take up layout space, also first bind the managed property to the visible property.
node.managedProperty().bind(node.visibleProperty());
Alternately change visibility in change listeners
Note that setting up a binding for a full role based system is pretty complex, so for such a task you are better off using change listeners and coding part of your logic in Java rather than trying to do everything in FXML. You will still be able to design your UI in FXML, but you can add some code in your controller to manage what items are visible for what role.
Sample role based display
Here is a role based solution using the Java API.
The solution shows different labeled pictures depending on the selected roles.
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.collections.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.*;
enum Role { father, son, mother, daughter, brother, sister }
class RoleManager {
private final Map<Node, List<Role>> nodeRoles = new HashMap<>();
private ObservableList<Role> activeRoles;
public final ListChangeListener<Role> ACTIVE_ROLE_LISTENER = new ListChangeListener<Role>() {
#Override
public void onChanged(Change<? extends Role> c) {
showActiveNodes();
}
};
public void setActiveRoles(ObservableList<Role> activeRoles) {
if (this.activeRoles != null) {
this.activeRoles.removeListener(ACTIVE_ROLE_LISTENER);
}
this.activeRoles = activeRoles;
this.activeRoles.addListener(ACTIVE_ROLE_LISTENER);
}
public void showActiveNodes() {
for (Node node : nodeRoles.keySet()) {
node.setVisible(isActive(node));
}
}
public void assignRole(Node node, Role... roles) {
nodeRoles.put(node, Arrays.asList(roles));
}
private boolean isActive(Node node) {
if (activeRoles == null) {
return false;
}
for (Role role: nodeRoles.get(node)) {
if (activeRoles.contains(role)) {
return true;
}
}
return false;
}
}
public class RoleVisibility extends Application {
private RoleManager roleManager = new RoleManager();
#Override
public void start(Stage stage) {
VBox layout = new VBox(10);
layout.getChildren().setAll(
getRoleChooser(),
createContent()
);
layout.setStyle("-fx-padding: 10px; -fx-background-color: cornsilk;");
roleManager.showActiveNodes();
stage.setTitle("Role Selector");
stage.setScene(new Scene(layout));
stage.show();
}
private Node getRoleChooser() {
ObservableList<Role> activeRoles = FXCollections.observableArrayList();
VBox roleChooser = new VBox(10);
for (final Role role: Role.values()) {
CheckBox checkBox = new CheckBox(role.toString());
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean wasSelected, Boolean isSelected) {
if (isSelected) {
activeRoles.add(role);
} else {
activeRoles.remove(role);
}
}
});
roleChooser.getChildren().add(checkBox);
}
roleManager.setActiveRoles(
activeRoles
);
return roleChooser;
}
private Pane createContent() {
HBox content = new HBox(10);
// icon license:
//License: Free for non-commercial use.
//Commercial usage: Not allowed
//The products or characters depicted in these icons are © by Lucasfilm Ltd.
content.getChildren().addAll(
createLabel("Darth Vader", "Vader-03-icon.png", Role.father),
createLabel("Queen Amidala", "Padme-Amidala-icon.png", Role.mother),
createLabel("Luke Skywalker", "Luke-Skywalker-01-icon.png", Role.brother, Role.son),
createLabel("Princess Leia", "Leia-icon.png", Role.daughter, Role.sister)
);
return content;
}
private Label createLabel(String text, String graphic, Role... roles) {
Label label = new Label(
text,
new ImageView(
new Image(
"http://icons.iconarchive.com/icons/jonathan-rey/star-wars-characters/128/" + graphic
)
)
);
label.setContentDisplay(ContentDisplay.TOP);
roleManager.assignRole(label, roles);
return label;
}
public static void main(String[] args) {
launch(args);
}
}
FXML Based Solution
I was interested in what it would take to get something like this to work with FXML, so I created a small framework to handle role based FXML UIs. It could perhaps have some performance enhancements and add some convenience definitions for defining role based controllers as well as shorthands for role definitions in the FXML, but it does seem to work in principle and demonstrate a basic approach.
Related
I used the custom switch button in the custom SwitchButton answer. Now I would like to animate the circle part when the user toggles between the 2 values (state). I used KeyValue and KeyFrame in order to do so.
The snippet of the animation that I added to the SwitchButton() method :
KeyValue start = new KeyValue(button.alignmentProperty(), Pos.CENTER_RIGHT);
KeyValue end = new KeyValue(button.alignmentProperty(), Pos.CENTER_LEFT);
KeyFrame frame = new KeyFrame(Duration.seconds(4), start, end);
Timeline timeline = new Timeline(frame);
timeline.play();
How can I make the animation?
You can use a TranslateTransition, to animate moving the knob for the switch on the track.
Although unrelated to the animation, this solution also modifies the original example to use style classes in a stylesheet, an on pseudo-class, and an exposed on state property for the custom control.
This answer is also based on a combination of ideas from previous questions:
Creating sliding on/off Switch button in javaFX
Change JavaFX style class based on model state
Related question:
One control many click listeners
Example Code
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.css.PseudoClass;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SwitchApp extends Application {
#Override
public void start(Stage stage) {
Switch lightSwitch = new Switch();
lightSwitch.onProperty().addListener((observable, wasOn, nowOn) -> {
System.out.println(nowOn ? "on" : "off");
});
StackPane layout = new StackPane(lightSwitch);
layout.setPadding(new Insets(30));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
class Switch extends StackPane {
private static final double TRACK_WIDTH = 30;
private static final double TRACK_HEIGHT = 10;
private static final double KNOB_DIAMETER = 15;
private static final Duration ANIMATION_DURATION = Duration.seconds(0.25);
public static final String CSS = "data:text/css," + // language=CSS
"""
.switch > .track {
-fx-fill: #ced5da;
}
.switch > .knob {
-fx-effect: dropshadow(
three-pass-box,
rgba(0,0,0,0.2),
0.2, 0.0, 0.0, 2
);
-fx-background-color: WHITE;
}
.switch:on > .track {
-fx-fill: #80C49E;
}
.switch:on > .knob {
-fx-background-color: #00893d;
}
""";
private final TranslateTransition onTransition;
private final TranslateTransition offTransition;
public Switch() {
// construct switch UI
getStylesheets().add(CSS);
getStyleClass().add("switch");
Rectangle track = new Rectangle(TRACK_WIDTH, TRACK_HEIGHT);
track.getStyleClass().add("track");
track.setArcHeight(track.getHeight());
track.setArcWidth(track.getHeight());
Button knob = new Button();
knob.getStyleClass().add("knob");
knob.setShape(new Circle(KNOB_DIAMETER / 2));
knob.setMaxSize(KNOB_DIAMETER, KNOB_DIAMETER);
knob.setMinSize(KNOB_DIAMETER, KNOB_DIAMETER);
knob.setFocusTraversable(false);
setAlignment(knob, Pos.CENTER_LEFT);
getChildren().addAll(track, knob);
setMinSize(TRACK_WIDTH, KNOB_DIAMETER);
// define animations
onTransition = new TranslateTransition(ANIMATION_DURATION, knob);
onTransition.setFromX(0);
onTransition.setToX(TRACK_WIDTH - KNOB_DIAMETER);
offTransition = new TranslateTransition(ANIMATION_DURATION, knob);
offTransition.setFromX(TRACK_WIDTH - KNOB_DIAMETER);
offTransition.setToX(0);
// add event handling
EventHandler<Event> click = e -> setOn(!isOn());
setOnMouseClicked(click);
knob.setOnMouseClicked(click);
onProperty().addListener((observable, wasOn, nowOn) -> updateState(nowOn));
updateState(isOn());
}
private void updateState(Boolean nowOn) {
onTransition.stop();
offTransition.stop();
if (nowOn != null && nowOn) {
onTransition.play();
} else {
offTransition.play();
}
}
public void setOn(boolean on) {
this.on.set(on);
}
public boolean isOn() {
return on.get();
}
public BooleanProperty onProperty() {
return on;
}
public BooleanProperty on =
new BooleanPropertyBase(false) {
#Override protected void invalidated() {
pseudoClassStateChanged(ON_PSEUDO_CLASS, get());
}
#Override public Object getBean() {
return Switch.this;
}
#Override public String getName() {
return "on";
}
};
private static final PseudoClass
ON_PSEUDO_CLASS = PseudoClass.getPseudoClass("on");
}
Suggested Further Improvements
There are some other enhancements that could be made to the control which are unrelated to the original question but would improve the quality and reusability of the control. Many applications won't need these enhancements, plus the enhancements can add additional complexity to the implementation which is unnecessary for these applications.
Control sizes are still hardcoded in this solution, but if preferred, you could modify the control to use a system based on em sizes (similar to the standard JavaFX controls).
Also, not provided in this solution, you could make use of JavaFX CSS looked up and derived colors to have the control match the color scheme defined for the default Java modena.css stylesheet, so that setting, for instance, the -fx-base color for the application to a different value will change the color scheme for this control as it does with the standard controls.
The in-built controls use a Skin abstraction to separate the public API from the internal implementation of the UI for the control so that users can assign custom skins to completely change the control's UI. Conceptually in operation, this switch is actually a kind of toggle button, so instead of having a custom control, it could be implemented as a custom skin that can be applied to the existing ToggleButton control. Or, less preferable, because it is more redundant, you could take the Switch implementation here and split it into a Switch class extending Control for the public API and a SwitchSkin class extending Skin for the UI and behavior implementation.
Alternative implementation
Before adopting either the other solutions linked in this answer or the solution in this answer, for a fairly common control like a switch, consider whether you would be better off using an off-the-shelf control either from the JavaFX framework core controls or a third-party library. Often those library-based controls will be of higher quality than something you create yourself or find in forum and Q&A posts.
For this particular animated switch control, the MaterialFX library has a particularly nice implementation, powered by additional bling :-)
I hope everyone is doing well.
I'm trying to move the drop down arrow in a TitledPane to be laid out on the right, instead of the left like it is by default. I'm using JavaFX 8, and many of the resources I've found don't seem to work.
I have found that I am able to move the arrow a specific amount, like 20 pixels shown below
.accordion .title > .arrow-button .arrow
{
-fx-translate-x: 20;
}
But I want something responsive. Is there some way that I can get the width of the titled pane, and then subtract some pixels so that so that the arrow appears to be laid out on the right when resizing? Is there a better way to it? I added the element using SceneBuilder2 if that matters.
Thanks so much for your time.
Edit: The following was added for clarification
Primarily, I want the arrow to be right justified, like below
Instead of just "to the right" of the arrow. I really appreciate all the assistance.
Unfortunately, there's no public API for moving the arrow to the right side of the TitledPane. This doesn't mean this can't be accomplished, however, we just have to translate the arrow dynamically, using bindings. In order for the rest of the title area to look correct we'll also have to translate the text, and graphic if present, to the left. The easiest way to do all this is by subclassing TitledPaneSkin and accessing the internals of the "title region".
Here's an example implementation. It lets you position the arrow on the left or right side via CSS. It's also responsive to resizing as well as alignment and graphic changes.
package com.example;
import static javafx.css.StyleConverter.getEnumConverter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.css.CssMetaData;
import javafx.css.SimpleStyleableObjectProperty;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.scene.control.TitledPane;
import javafx.scene.control.skin.TitledPaneSkin;
import javafx.scene.layout.Region;
import javafx.scene.text.Text;
public class CustomTitledPaneSkin extends TitledPaneSkin {
public enum ArrowSide {
LEFT, RIGHT
}
/* ********************************************************
* *
* Properties *
* *
**********************************************************/
private final StyleableObjectProperty<ArrowSide> arrowSide
= new SimpleStyleableObjectProperty<>(StyleableProperties.ARROW_SIDE, this, "arrowSide", ArrowSide.LEFT) {
#Override protected void invalidated() {
adjustTitleLayout();
}
};
public final void setArrowSide(ArrowSide arrowSide) { this.arrowSide.set(arrowSide); }
public final ArrowSide getArrowSide() { return arrowSide.get(); }
public final ObjectProperty<ArrowSide> arrowSideProperty() { return arrowSide; }
/* ********************************************************
* *
* Instance Fields *
* *
**********************************************************/
private final Region title;
private final Region arrow;
private final Text text;
private DoubleBinding arrowTranslateBinding;
private DoubleBinding textGraphicTranslateBinding;
private Node graphic;
/* ********************************************************
* *
* Constructors *
* *
**********************************************************/
public CustomTitledPaneSkin(TitledPane control) {
super(control);
title = (Region) Objects.requireNonNull(control.lookup(".title"));
arrow = (Region) Objects.requireNonNull(title.lookup(".arrow-button"));
text = (Text) Objects.requireNonNull(title.lookup(".text"));
registerChangeListener(control.graphicProperty(), ov -> adjustTitleLayout());
}
/* ********************************************************
* *
* Skin Stuff *
* *
**********************************************************/
private void adjustTitleLayout() {
clearBindings();
if (getArrowSide() != ArrowSide.RIGHT) {
// if arrow is on the left we don't need to translate anything
return;
}
arrowTranslateBinding = Bindings.createDoubleBinding(() -> {
double rightInset = title.getPadding().getRight();
return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
}, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty());
arrow.translateXProperty().bind(arrowTranslateBinding);
textGraphicTranslateBinding = Bindings.createDoubleBinding(() -> {
switch (getSkinnable().getAlignment()) {
case TOP_CENTER:
case CENTER:
case BOTTOM_CENTER:
case BASELINE_CENTER:
return 0.0;
default:
return -(arrow.getWidth());
}
}, getSkinnable().alignmentProperty(), arrow.widthProperty());
text.translateXProperty().bind(textGraphicTranslateBinding);
graphic = getSkinnable().getGraphic();
if (graphic != null) {
graphic.translateXProperty().bind(textGraphicTranslateBinding);
}
}
private void clearBindings() {
if (arrowTranslateBinding != null) {
arrow.translateXProperty().unbind();
arrow.setTranslateX(0);
arrowTranslateBinding.dispose();
arrowTranslateBinding = null;
}
if (textGraphicTranslateBinding != null) {
text.translateXProperty().unbind();
text.setTranslateX(0);
if (graphic != null) {
graphic.translateXProperty().unbind();
graphic.setTranslateX(0);
graphic = null;
}
textGraphicTranslateBinding.dispose();
textGraphicTranslateBinding = null;
}
}
#Override
public void dispose() {
clearBindings();
unregisterChangeListeners(getSkinnable().graphicProperty());
super.dispose();
}
/* ********************************************************
* *
* Stylesheet Handling *
* *
**********************************************************/
public static List<CssMetaData<?, ?>> getClassCssMetaData() {
return StyleableProperties.CSS_META_DATA;
}
#Override
public List<CssMetaData<?, ?>> getCssMetaData() {
return getClassCssMetaData();
}
private static class StyleableProperties {
private static final CssMetaData<TitledPane, ArrowSide> ARROW_SIDE
= new CssMetaData<>("-fx-arrow-side", getEnumConverter(ArrowSide.class), ArrowSide.LEFT) {
#Override
public boolean isSettable(TitledPane styleable) {
Property<?> prop = (Property<?>) getStyleableProperty(styleable);
return prop != null && !prop.isBound();
}
#Override
public StyleableProperty<ArrowSide> getStyleableProperty(TitledPane styleable) {
Skin<?> skin = styleable.getSkin();
if (skin instanceof CustomTitledPaneSkin) {
return ((CustomTitledPaneSkin) skin).arrowSide;
}
return null;
}
};
private static final List<CssMetaData<?, ?>> CSS_META_DATA;
static {
List<CssMetaData<?,?>> list = new ArrayList<>(TitledPane.getClassCssMetaData().size() + 1);
list.addAll(TitledPaneSkin.getClassCssMetaData());
list.add(ARROW_SIDE);
CSS_META_DATA = Collections.unmodifiableList(list);
}
}
}
You can then apply this skin to all TitledPanes in your application from CSS, like so:
.titled-pane {
-fx-skin: "com.example.CustomTitledPaneSkin";
-fx-arrow-side: right;
}
/*
* The arrow button has some right padding that's added
* by "modena.css". This simply puts the padding on the
* left since the arrow is positioned on the right.
*/
.titled-pane > .title > .arrow-button {
-fx-padding: 0.0em 0.0em 0.0em 0.583em;
}
Or you could target only certain TitledPanes by adding a style class and using said class instead of .titled-pane.
The above works with JavaFX 11 and likely JavaFX 10 and 9 as well. To get it to compile on JavaFX 8 you need to change some things:
Import com.sun.javafx.scene.control.skin.TitledPaneSkin instead.
The skin classes were made public in JavaFX 9.
Remove the calls to registerChangeListener(...) and unregisterChangeListeners(...). I believe replacing them with the following is correct:
#Override
protected void handleControlPropertyChange(String p) {
super.handleControlPropertyChange(p);
if ("GRAPHIC".equals(p)) {
adjustTitleLayout();
}
}
Use new SimpleStyleableObjectProperty<ArrowSide>(...) {...} and new CssMetaData<TitledPane, ArrowSide>(...) {...}.
Type inference was improved in later versions of Java.
Use (StyleConverter<?, ArrowSide>) getEnumConverter(ArrowSide.class).
There was a bug in the generic signature of getEnumConverter that was fixed in a later version. Using the cast works around the problem. You may wish to #SuppressWarnings("unchecked") the cast.
Issue: Even with the above changes there's a problem in JavaFX 8—the arrow is only translated once the TitledPane is focused. This doesn't appear to be a problem with the above code as even changing the alignment property does not cause the TitledPane to update until it has focus (even when not using the above skin, but rather just the default skin). I've been unable to find a workaround to this problem (while using the custom skin) but maybe you or someone else can. I was using Java 1.8.0_202 when testing for JavaFX 8.
If you don't want to use a custom skin, or you're on JavaFX 8 (this will cause the arrow to be translated without needing to focus the TitledPane first), you can extract the necessary code, with some modifications, into a utility method:
public static void putArrowOnRight(TitledPane pane) {
Region title = (Region) pane.lookup(".title");
Region arrow = (Region) title.lookup(".arrow-button");
Text text = (Text) title.lookup(".text");
arrow.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
double rightInset = title.getPadding().getRight();
return title.getWidth() - arrow.getLayoutX() - arrow.getWidth() - rightInset;
}, title.paddingProperty(), title.widthProperty(), arrow.widthProperty(), arrow.layoutXProperty()));
arrow.setStyle("-fx-padding: 0.0em 0.0em 0.0em 0.583em;");
DoubleBinding textGraphicBinding = Bindings.createDoubleBinding(() -> {
switch (pane.getAlignment()) {
case TOP_CENTER:
case CENTER:
case BOTTOM_CENTER:
case BASELINE_CENTER:
return 0.0;
default:
return -(arrow.getWidth());
}
}, arrow.widthProperty(), pane.alignmentProperty());
text.translateXProperty().bind(textGraphicBinding);
pane.graphicProperty().addListener((observable, oldGraphic, newGraphic) -> {
if (oldGraphic != null) {
oldGraphic.translateXProperty().unbind();
oldGraphic.setTranslateX(0);
}
if (newGraphic != null) {
newGraphic.translateXProperty().bind(textGraphicBinding);
}
});
if (pane.getGraphic() != null) {
pane.getGraphic().translateXProperty().bind(textGraphicBinding);
}
}
Note: While this puts the arrow on the right without having to focus the TitledPane first, the TitledPane still suffers from the issue noted above. For instance, changing the alignment property doesn't update the TitledPane until it's focused. I'm guessing this is simply a bug in JavaFX 8.
This way of doing things is not as "easy" as the skin approach and requires two things:
The TitledPane must be using the default TitledPaneSkin.
The TitledPane must have been displayed in a Window (window was showing) before calling the utility method.
Due to the lazy nature of JavaFX controls, the skin and the associated nodes will not have been created until the control has been displayed in a window. Calling the utility method before the control was displayed will result in a NullPointerException being thrown since the lookup calls will return null.
If using FXML, note that the initialize method is called during a call to FXMLLoader.load (any of the overloads). This means, under normal circumstances, it's not possible for the created nodes to be part of a Scene yet, let alone a showing Window. You must wait for the TitledPane to be displayed first, then call the utility method.
Waiting for the TitledPane to be displayed can be achieved by listening to the Node.scene property, the Scene.window property, and the Window.showing property (or you could listen for WindowEvent.WINDOW_SHOWN events). However, if you immediately put the loaded nodes into a showing Window, then you can forgo observing the properties; call the utility method inside a Platform.runLater call from inside initialize.
When using the skin approach, the whole wait-for-showing-window hassle is avoided.
Usual Warning: This answer relies on the internal structure of TitledPane which may change in a future release. Be cautious when changing JavaFX versions. I only (somewhat) tested this on JavaFX 8u202 and JavaFX 11.0.2.
This isn’t exactly the same, visually, but you can hide the arrow button and create a graphic that acts like an arrow button. TitledPane extends Labeled, so you have control over the placement of the graphic relative to the text, via the contentDisplay property.
First, hide the arrow button in the stylesheet:
.accordion .title > .arrow-button
{
visibility: hidden;
}
In the code, you can create a Label to act as a fake button and set it as the TitledPane’s graphic. The entire title line is sensitive to the mouse, so an interactive control (like a Button) is not needed.
Label collapseButton = new Label();
collapseButton.textProperty().bind(
Bindings.when(titledPane.expandedProperty())
.then("\u25bc").otherwise("\u25b6"));
titledPane.setGraphic(collapseButton);
titledPane.setContentDisplay(ContentDisplay.RIGHT);
In FXML you can just add nodeOrientation="RIGHT_TO_LEFT"
or use yourNode.setNodeOrientation((NodeOrientation orientation)
https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/Node.html#setNodeOrientation(javafx.geometry.NodeOrientation)
I am working on a TreeView which represents a robot controlling program, each TreeCell represents a statement, and a TreeCell can be nested in an other one. Like in programming, statements can be nested in if or for statements.
Here I have created a simple demo, filled with some random blocks.
Demo Screenshot
To customize the rendering of TreeCell, I have create a class extending TreeCell:
public class TreeDataCell extends TreeCell<TreeData> {
public void updateItem(TreeData item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (item == null || empty) {
setGraphic(null);
} else {
setGraphic(getCellGraphic(item));
}
}
private Group getCellGraphic(TreeData data) {
Group grp = new Group();
VBox vbox = new VBox();
vbox.setMinWidth(100);
vbox.setMaxWidth(200);
vbox.setBorder(new Border(new BorderStroke(
Color.LIGHTGRAY.darker(),
BorderStrokeStyle.SOLID,
new CornerRadii(10.0),
new BorderWidths(2.0))));
vbox.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, new CornerRadii(10.0), null)));
vbox.setEffect(new DropShadow(2.0, 3.0, 3.0, Color.DIMGRAY));
Region header = new Region();
header.setPrefHeight(5.0);
Region footer = new Region();
footer.setPrefHeight(5.0);
Label labTitle = new Label();
labTitle.setFont(new Font("San Serif", 20));
labTitle.setText(data.getTitle());
Label labDesc = null;
if (data.getDescription() != null) {
labDesc = new Label();
labDesc.setWrapText(true);
labDesc.setText(data.getDescription());
}
vbox.getChildren().addAll(header, labTitle);
if (labDesc != null) {
vbox.getChildren().add(labDesc);
}
vbox.getChildren().add(footer);
grp.getChildren().add(vbox);
return grp;
}
}
The TreeData is a simple class containing 2 Strings:
public class TreeData {
private String title;
private String desc;
/* getters + setters */
}
As you can see, the indentation between two levels are too small, and we can barely see statement nesting.
I am hard coding all the styles in Java, since I haven't learnt FXML+CSS yet.
I'd like to know if it is possible to set the size of indentation in Java? I cannot find any API for this purpose. In addition, is it possible to draw lines between parent node and its children like JTree in Swing ?
Thank you.
Regarding having lines like in JTree, there is no built in way to do that as of JavaFX 11. There is a feature request (JDK-8090579) but there doesn't seem to be any plans to implement it. You may be able to implement it yourself but I'm not sure how.
As to modifying the indent of the TreeCells, the easiest way is by using CSS.
As documented in the JavaFX CSS Reference Guide, TreeCell has a CSS property named -fx-indent whose value is a <size>. You can set this property by using a stylesheet or inline it via the style property. An example using inline styles:
public class TreeDataCell extends TreeCell<TreeData> {
public TreeDataCell() {
setStyle("-fx-indent: <size>;");
}
}
However, since you are currently not using CSS or FXML, there is another option that is purely code: Modifying the indent property of TreeCellSkin. This class became public API in JavaFX 9. There may be equivalent internal API in JavaFX 8 but I'm not sure.
By default, the Skin of a TreeCell will be an instance of TreeCellSkin. This means you can get this skin and set the indent value as needed. You have to be careful, though, as the skin is lazily created; it won't necessarily be available until the TreeView is actually part of a showing window.
If you only want to set the property once, one way is to intercept the skin inside createDefaultSkin():
public class TreeDataCell extends TreeCell<TreeData> {
#Override
protected Skin<?> createDefaultSkin() {
TreeCellSkin<?> skin = (TreeCellSkin<?>) super.createDefaultSkin();
skin.setIndent(/* your value */);
return skin;
}
}
You could also extend TreeCellSkin and customize it. Just remember to override createDefaultSkin() and return you custom skin implementation.
I have a tableview which have two columns.First column simply populated by observableList and Second column have choiceboxes in each cell.
My problem is that when I select value from choicebox and scroll down to select value from another choicebox and again scroll up then previous selected values of choiceboxe resets.
Below are my code:
#FXML
private TableColumn<FileHeaders, ChoiceBox<String>> fileHeaders;
public void setFileHeaders(ObservableList<String> fileHeadersObservableList) {
fileHeaders.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<FileHeaders, ChoiceBox<String>>, ObservableValue<ChoiceBox<String>>>() {
#Override
public ObservableValue<ChoiceBox<String>> call(TableColumn.CellDataFeatures<FileHeaders, ChoiceBox<String>> rawUdrsList) {
ChoiceBox<String> choiceBox = new ChoiceBox<>();
System.out.println(choiceBox);//this value print again and again when I scroll the tableview
choiceBox.setItems(fileHeadersObservableList);
choiceBox.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ov, String t, String valueFromChoiceBox) {
//write some code stuff
}
});
return new SimpleObjectProperty<ChoiceBox<String>>(choiceBox);
}
});
}
As far as I think that call method invoke everytime when i scroll the tableview,so new choicebox created and set into the tableview.
How can i solve this problem,
I really appreciate ifhelps from you guys.
Thanks
Your Error
A cell value factory should not return a node, it should just return the relevant data property backing the cell. Instead, the nodes should be returned in as part of the table cell implementation supplied by a cell factory.
Defining Cell Factories
The makery tutorials provide a nice example which shows the difference in usage between a cell value factory and a cell factory. Usually, the two are used in combination when you require a custom rendering of cell data.
However, JavaFX has predefined helper classes: look at How do you create a table cell factory in JavaFX to display a ChoiceBox?, which uses a ChoiceBoxTableCell. Perhaps your question just works out as a duplicate of that.
Background
To understand how ChoiceBoxTableCell works from first principles, you can look at its source code. A table cell is a Labeled. A Labeled can have both text and a graphic. The text label is used to display the chosen value when the cell is not being edited. Whenever the user double clicks on the cell to edit it, then the text is set to null and a graphic node for the label is displayed instead (in this case a choice box allowing the user to choose a new value for the cell being edited). Once the editing is complete, the new value for the choice is saved to the underlying backing data value property, the display switches back to plain text and the graphic for the cell is set back to null so that it no longer displays. For your use case, it will be easier just to use the pre-defined class rather than to write a custom implementation of the same functionality.
Sample
Output of the sample app below, after the user has twice clicked on a user state which is backed by a choice box, the first click highlighting the row and the second click bringing up the edit choice box for the item:
Sample code demoing use of a ChoiceBoxTableCell:
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.stage.Stage;
public class TableChoices extends Application {
#Override
public void start(final Stage stage) throws Exception {
ObservableList<User> data = createTestData();
TableColumn<User, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<User, UserState> stateCol = new TableColumn<>("State");
stateCol.setCellValueFactory(new PropertyValueFactory<>("state"));
stateCol.setCellFactory(
ChoiceBoxTableCell.forTableColumn(UserState.values())
);
stateCol.setEditable(true);
stateCol.setPrefWidth(100);
stateCol.setOnEditCommit(
(TableColumn.CellEditEvent<User, UserState> t) ->
t.getTableView()
.getItems()
.get(t.getTablePosition().getRow())
.setState(t.getNewValue())
);
TableView<User> tableView = new TableView<>(data);
//noinspection unchecked
tableView.getColumns().addAll(
nameCol,
stateCol
);
tableView.setPrefSize(170, 150);
tableView.setEditable(true);
stage.setScene(new Scene(tableView));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
public enum UserState {
ACTIVE,
LOCKED,
DELETED
}
public static class User {
private StringProperty name;
private ObjectProperty<UserState> state;
public User(String name, UserState state) {
this.name = new SimpleStringProperty(name);
this.state = new SimpleObjectProperty<>(state);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public UserState getState() {
return state.get();
}
public ObjectProperty<UserState> stateProperty() {
return state;
}
public void setState(UserState state) {
this.state.set(state);
}
}
private ObservableList<User> createTestData() {
return FXCollections.observableArrayList(
new User("Jack", UserState.ACTIVE),
new User("Jill", UserState.LOCKED),
new User("Tom", UserState.DELETED),
new User("Harry", UserState.ACTIVE)
);
}
}
I advise you to look over the sample code closely and note the use of a setOnEditCommit handler for the table column, which updates the backing data to reflect the edited value. Without code such as this, then the edit will not be committed back to the backing data (at least in Java 8, the JavaFX Table Tutorial notes that future JavaFX versions may make the implementation a bit less clunky).
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
RESUME
There is a time I have been studying the new JavaFX technology, and I've been faced with some barriers in creating custom controls. I learned how to use CSS to customize my controls, and then I came across the case of customizing controls using Skin and SkinBase.
Seen such resources, it was easy to initiate and complete the creation of new controls with visual and specific functionalities. However, personalization, that is, the visual and functional editing of existing controls on JavaFX library becomes somewhat more complicated. In many cases the programmer is forced to use resources that are only available in private packages from Oracle (com.sun ...), which would become a bad practice, resulting in the production of software not maintainable.
Imagine the example where we want to customize the ScrollBar control. It is possible to change its appearance completely using CSS. However, the desire of adding new behaviors to such control involves creating a new Theme from ZERO, without any reuse of ScrollBarSkin, because it is in the private Oracle package. This forces the programmer having to reimplement the logics that have already been implemented, such as the positioning of thumb, the update of values, what happens when you click the track, among many other things. In stubbornness to create a subtype of ScrollBarSkin it is seen that there are many important methods that have been encapsulated as not being overwritten, leaving you to have to compulsorily reimplement the existing logic.
What appears, at least, is that many important components for customizing a control are caged, causing you to have to use a single path to reach them (still limited).
EXAMPLE (from theory to practice)
To illustrate what I mean by this and emphasize in the conclusion of this question in this community, we will briefly try to customize the ScrollBar existing in JavaFX package. My intention is to create a scrollbar to look like this:
LINK1 & LINK2
With regard to the behavior of the scrollbar, while clicking on it’s arrows, they should move a little, returning to their positions when the mouse button is released. When you pass the mouse over the thumb and arrows, they should light up. By clicking in the track or pushing any of our scrollbar arrows, the thumb must moves smoothly, in animated form and not abruptly.
So let's start with our experiment. First, let's create a CSS file that will serve as a definition of some appearances:
.scroll-bar {
-fx-skin: "packageA.packageB.ScrollBarSkin2";
-fx-background-color: rgb(66,64,64);
-fx-border-color: rgb(96,96,98);
-fx-border-width: 1px;
}
.scroll-bar > .thumb {
-fx-background-insets: 5 0 5 0;
-fx-shape: "M0 0c3,0 7,0 10,0l0 6c-3,0 -7,0 -10,0l0 -6z";
}
.scroll-bar > .increment-button > .increment-arrow ,
.scroll-bar > .decrement-button > .decrement-arrow ,
.scroll-bar > .thumb {
-fx-background-color: rgb(254,254,254);
}
/* ------------------------------------------------------------ BUTTONS */
.scroll-bar:horizontal > .increment-button ,
.scroll-bar:horizontal > .decrement-button ,
.scroll-bar:vertical > .increment-button ,
.scroll-bar:vertical > .decrement-button {
-fx-padding: 4px;
}
.scroll-bar:horizontal > .increment-button:hover ,
.scroll-bar:horizontal > .decrement-button:hover ,
.scroll-bar:vertical > .increment-button:hover ,
.scroll-bar:vertical > .decrement-button:hover {
-fx-background-color: null;
}
/* ------------------------------------------------------------ BUTTONS SHAPES */
.scroll-bar:horizontal > .increment-button > .increment-arrow {
-fx-shape: "m -745.01097,-1519.0664 -156.95606,90.6186 -156.95607,90.6186 0,-181.2372 0,-181.2372 156.95608,90.6186 z";
}
.scroll-bar:horizontal > .decrement-button > .decrement-arrow {
-fx-shape: "m -1455.5694,-1550.495 153.1056,-88.3956 153.1056,-88.3955 0,176.7911 0,176.7911 -153.1056,-88.3955 z";
}
.scroll-bar:vertical > .increment-button > .increment-arrow {
-fx-shape: "m -1334.2856,-2204.9669 85.446,147.9968 85.446,147.9968 -170.892,0 -170.8921,0 85.446,-147.9968 z";
}
.scroll-bar:vertical > .decrement-button > .decrement-arrow {
-fx-shape: "m -1234.2856,-1096.134 -94.0582,-162.9135 -94.0582,-162.9136 188.1164,0 188.1163,0 -94.0582,162.9136 z";
}
Observing our CSS file, we can see that we chose one of the 3 existing ways to connect our control to our skin, and we use the definition of -fx-skin property in our CSS file. Now we need to link our CSS file created with our control. This is done in Java code where we just have to set the CSS style sheet our control:
scrollBar.getStylesheets().setAll(this.getClass().getResource("scroll-bar-style.css").toExternalForm());
Note: To run the example, you need to have a test class with a main
method to create and place a ScrollBar in a scene graph.
We already have our control linked to our Skin, but we have not really created the Skin. Creating or editing controls takes into account that the controls themselves, ie, the objects that extend Control, are considered part of the model of the MVC pattern, existing in JavaFX. The part of the control and visualization is originally divided (do not know for what reason) into two parts. One, called the Skin, and the other, called Behavior. Both are existing interfaces in JavaFX with Skin representing the Visualization part, and Behavior being the part of Control. Unfortunately (do not know why!) Behavior is considered a private part of the JavaFX package, so the users developers of JavaFX are pushed to treat the part of Visualization and Control inside the Skin, which would be the part initially set for Visualization only. Having said all these strange things, let's create our Skin class:
// Public packages.
import javafx.animation.Animation;
import javafx.animation.TranslateTransition;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.control.ScrollBar;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
// ... And here we have a private package.
import com.sun.javafx.scene.control.skin.ScrollBarSkin;
public class ScrollBarSkin2 extends ScrollBarSkin
{
// #########################################################################################################
// INSTANCES
// #########################################################################################################
// SUBSTRUCTURES
private StackPane thumb;
private StackPane track;
private Region incButton;
private Region decButton;
private Region incArrow;
private Region decArrow;
// EFFECTS
private DropShadow drop_thumb;
private DropShadow drop_inc;
private DropShadow drop_dec;
// ANIMATIONS
private BindableTransition aniTran_thumbDrop;
private BindableTransition aniTran_incDrop;
private BindableTransition aniTran_decDrop;
private TranslateTransition aniTran_setaInc;
private TranslateTransition aniTran_setaDec;
// #########################################################################################################
// CONSTRUCTORS
// #########################################################################################################
public ScrollBarSkin2(ScrollBar scrollbar)
{
super(scrollbar);
this.thumb = (StackPane) this.getSkinnable().lookup(".thumb");
this.track = (StackPane) this.getSkinnable().lookup(".track");
this.incButton = (Region) this.getSkinnable().lookup(".increment-button");
this.decButton = (Region) this.getSkinnable().lookup(".decrement-button");
this.incArrow = (Region) this.getSkinnable().lookup(".increment-arrow");
this.decArrow = (Region) this.getSkinnable().lookup(".decrement-arrow");
this.configureSubstructures();
this.addEvents();
}
/** Sets the substructures obtained.*/
protected void configureSubstructures()
{
// ####################
// THUMB
// ####################
this.drop_thumb = new DropShadow();
this.drop_thumb.setRadius(0);
this.drop_thumb.setColor(Color.WHITE);
this.thumb.setEffect(this.drop_thumb);
this.aniTran_thumbDrop = new BindableTransition(Duration.millis(250));
// ####################
// INCREMENT BUTTON
// ####################
this.drop_inc = new DropShadow();
this.drop_inc.setRadius(0);
this.drop_inc.setColor(Color.WHITE);
this.incArrow.setEffect(this.drop_inc);
this.aniTran_incDrop = new BindableTransition(Duration.millis(250));
// ####################
// DECREMENT BUTTON
// ####################
this.drop_dec = new DropShadow();
this.drop_dec.setRadius(0);
this.drop_dec.setColor(Color.WHITE);
this.decArrow.setEffect(this.drop_dec);
this.aniTran_decDrop = new BindableTransition(Duration.millis(250));
// ####################
// ARROWS
// ####################
this.aniTran_setaInc = new TranslateTransition(Duration.millis(100) , this.incArrow);
this.aniTran_setaDec = new TranslateTransition(Duration.millis(100) , this.decArrow);
if(this.getSkinnable().getOrientation() == Orientation.HORIZONTAL)
{
this.aniTran_setaInc.setFromX(this.incArrow.getLayoutX());
this.aniTran_setaInc.setToX(this.incArrow.getLayoutX() + 2);
this.aniTran_setaDec.setFromX(this.incArrow.getLayoutX());
this.aniTran_setaDec.setToX(this.incArrow.getLayoutX() - 2);
}
else if(this.getSkinnable().getOrientation() == Orientation.VERTICAL)
{
this.aniTran_setaInc.setFromY(this.incArrow.getLayoutY());
this.aniTran_setaInc.setToY(this.incArrow.getLayoutY() - 2);
this.aniTran_setaDec.setFromY(this.incArrow.getLayoutY());
this.aniTran_setaDec.setToY(this.incArrow.getLayoutY() + 2);
}
}
/** Adds events animations. Here we also have the logic part.*/
protected void addEvents()
{
// ####################
// THUMB
// ####################
thumb.addEventHandler(MouseEvent.MOUSE_ENTERED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
if(aniTran_thumbDrop.getStatus() != Animation.Status.RUNNING)
{
aniTran_thumbDrop.setRate(1);
aniTran_thumbDrop.play();
}
else
{
aniTran_thumbDrop.setRate(1);
}
}
});
thumb.addEventHandler(MouseEvent.MOUSE_EXITED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
if(aniTran_thumbDrop.getStatus() != Animation.Status.RUNNING)
{
aniTran_thumbDrop.setRate(-1);
aniTran_thumbDrop.play();
}
else
{
aniTran_thumbDrop.setRate(-1);
}
}
});
this.aniTran_thumbDrop.fractionProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue)
{
drop_thumb.setRadius(4 * newValue.doubleValue());
}
});
// ####################
// INCREMENT BUTTON
// ####################
incButton.addEventHandler(MouseEvent.MOUSE_ENTERED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
if(aniTran_incDrop.getStatus() != Animation.Status.RUNNING)
{
aniTran_incDrop.setRate(1);
aniTran_incDrop.play();
}
else
{
aniTran_incDrop.setRate(1);
}
}
});
incButton.addEventHandler(MouseEvent.MOUSE_EXITED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
if(aniTran_incDrop.getStatus() != Animation.Status.RUNNING)
{
aniTran_incDrop.setRate(-1);
aniTran_incDrop.play();
}
else
{
aniTran_incDrop.setRate(-1);
}
}
});
this.aniTran_incDrop.fractionProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue)
{
drop_inc.setRadius(4 * newValue.doubleValue());
}
});
// ####################
// DECREMENT BUTTON
// ####################
decButton.addEventHandler(MouseEvent.MOUSE_ENTERED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
if(aniTran_decDrop.getStatus() != Animation.Status.RUNNING)
{
aniTran_decDrop.setRate(1);
aniTran_decDrop.play();
}
else
{
aniTran_decDrop.setRate(1);
}
}
});
decButton.addEventHandler(MouseEvent.MOUSE_EXITED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent e)
{
if(aniTran_decDrop.getStatus() != Animation.Status.RUNNING)
{
aniTran_decDrop.setRate(-1);
aniTran_decDrop.play();
}
else
{
aniTran_decDrop.setRate(-1);
}
}
});
this.aniTran_decDrop.fractionProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue)
{
drop_dec.setRadius(4 * newValue.doubleValue());
}
});
// ####################
// INCREMENT ARROW
// ####################
this.incButton.addEventHandler(MouseEvent.MOUSE_PRESSED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent event)
{
if(aniTran_setaInc.getStatus() != Animation.Status.RUNNING)
{
aniTran_setaInc.setRate(1);
aniTran_setaInc.play();
}
else
{
aniTran_setaInc.setRate(1);
}
}
});
this.incButton.addEventHandler(MouseEvent.MOUSE_RELEASED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent event)
{
if(aniTran_setaInc.getStatus() != Animation.Status.RUNNING)
{
aniTran_setaInc.setRate(-1);
aniTran_setaInc.play();
}
else
{
aniTran_setaInc.setRate(-1);
}
}
});
// ####################
// DECREMENT ARROW
// ####################
this.decButton.addEventHandler(MouseEvent.MOUSE_PRESSED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent event)
{
if(aniTran_setaDec.getStatus() != Animation.Status.RUNNING)
{
aniTran_setaDec.setRate(1);
aniTran_setaDec.play();
}
else
{
aniTran_setaDec.setRate(1);
}
}
});
this.decButton.addEventHandler(MouseEvent.MOUSE_RELEASED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent event)
{
if(aniTran_setaDec.getStatus() != Animation.Status.RUNNING)
{
aniTran_setaDec.setRate(-1);
aniTran_setaDec.play();
}
else
{
aniTran_setaDec.setRate(-1);
}
}
});
// ####################
// TRACK
// ####################
this.getSkinnable().valueProperty().addListener(new InvalidationListener()
{
#Override public void invalidated(Observable observable)
{
System.out.println("ScrollBar value is invalid: " + getSkinnable().getValue());
}
});
this.getSkinnable().valueProperty().addListener(new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable , Number oldValue , Number newValue)
{
System.out.printf("ScrollBar value changed! - [OLD: %f , NEW: %f] %n" ,
oldValue.doubleValue() , newValue.doubleValue() );
}
});
this.track.addEventHandler(MouseEvent.MOUSE_PRESSED , new EventHandler<MouseEvent>()
{
#Override public void handle(MouseEvent event)
{
System.out.println("Track pressed!");
}
});
ChangeListener<Number> listenerThumb = new ChangeListener<Number>()
{
#Override public void changed(ObservableValue<? extends Number> observable , Number oldValue, Number newValue)
{
System.out.println("Thumb moved!");
}
};
thumb.layoutXProperty().addListener(listenerThumb);
thumb.layoutYProperty().addListener(listenerThumb);
thumb.translateXProperty().addListener(listenerThumb);
thumb.translateYProperty().addListener(listenerThumb);
}
#Override protected void handleControlPropertyChanged(String p)
{
System.out.println("Beginning.: " + p);
super.handleControlPropertyChanged(p);
System.out.println("End: " + p);
}
}
As you can see, we insist on creating a subtype of ScrollBarSkin, a private implementation of JavaFX (com.sun ...) package. I also want to make clear that I borrowed the BindableTransition class:
import javafx.animation.Transition;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.util.Duration;
/**
* A simple Transition thats fraction property can be bound to any other
* properties.
*
* #author hendrikebbers
*
*/
public class BindableTransition extends Transition {
private DoubleProperty fraction;
public BindableTransition(Duration duration) {
fraction = new SimpleDoubleProperty();
setCycleDuration(duration);
}
#Override
protected final void interpolate(double frac) {
fraction.set(frac);
}
public ReadOnlyDoubleProperty fractionProperty() {
return fraction;
}
}
This is a class that I took from AquaFX, so I have no credit for its creation. The original author, as written in the class itself, is hendrikebbers. I want to thank him/her and the AquaFX team for making available the source code, without which I'd be lost.
As you can see, ScrollBarSkin2 has the addEvents method, which is tasked to add certain events to certain components from skin. Largely, the animations are treated there. The heaviest problem of this code appears when I try to make the thumb to move smoothly once the user click on the track, or the arrow buttons. I just could not implement such behavior, because I have no idea how to do it. I've tried to override the handleControlPropertyChanged method (from BehaviorSkinBase), trying to create a proper positionThumb method. Unfortunately this was not possible because I need some ScrollBarSkin properties in order to properly position the thumb. Such properties would be, for example, trackPos and trackLenght, reserved to be calculated and used only in ScrollBarSkin.
CONCLUSION
I can then conclude that I do not know what else to do. It is very annoying that Oracle provide JavaFX technology limiting its use (at least this is what appears). Appears to be no documentation on the study site of JavaFX about more advanced customization. The documents on editing and creating custom controls in JavaFX existing on internet talk about the simple customization via CSS, not mentioning how we can implement more advanced features (such as animation of their substructures) within the skin. It makes me so frustrated, after all I see great potential in this technology (incomparable to Swing) and still not have the ability to use this feature.
At the most, I felt myself powerless not knowing what to do. I wish someone responsible for developing JavaFX and documentation take notion of certain limitations that are appearing to us. The existing books on the subject are completely outdated, and if not, they do not say anything more than the existing content here (also).
QUESTION
I do not know where to turn to talk about these kinds of things, but I would like someone to tell me that I'm customizing my controls in the wrong way, and that I must correct some lines of code for any reason whatsoever. After all, JavaFX 8 will be fully launched on March 18. Someone please tell me what I'm doing wrong. If I am not doing anything wrong, what needs to be done for this to be notified to the developers of JavaFX?
Thank you for your attention.
EDIT
Some people do not really understand what I wanted to show here. I wanted to demonstrate that there is apparently some need for that part of the JavaFX API becomes public. I said that because I cannot create a simple custom scroll bar. So I'm guessing for the other side. Is there really a need to leave part of the API public? It was then that I finally could ask:
Am I doing the customization of controls in a wrong way? If I am, can anyone correct me?
Simple as that. The huge text post here was proof that I wanted to show, A plus B, that I had to come here to ask this, possibly something that for some people this may be a silly thing. In other words ... I demonstrated how I'm customizing my scroll bar (the source code of my problem). And then I asked: "Hey, can anyone help me on this? Thank you". I have come not to teach how to customize components, came to show how I'm doing (which apparently seems to be the wrong way or not).
Despite having already accepted the answer of a user, my doubts still exist. Anyway, I put the same question in the Oracle JavaFX community, as sugested. And unfortunately things take time to work there (I know that the vast majority is busy working).
You asked:
If I am not doing anything wrong, what needs to be done for this to be notified to the developers of JavaFX?
From the community section on the JavaFX home page (emphasis mine):
The OTN JavaFX Forum is a great place to post, answer, and review
issues related to JavaFX.
The Jira bug tracking system is the place where you want to report
issues with JavaFX, or file a feature request. Instructions on how to
submit bug reports or feature requests are available in the FAQ section.
You asked:
What Oracle expects of us?
I'm presuming they expect you to go through their traditional contact channels, which the developers do read and consider.
Your post is well-written (er... probably... at least it's well-formatted), but you may wish to divide it into smaller, more bite-sized issues or summarize and provide details as-requested in order to make it easier to parse and respond to your concerns.