Reputation: 126
Added children used to be visible in all my subclasses, like in the example here.
Due to some recent changes I made (in a big porting of very old Java SWING code) children are still visible in TextField
or TextArea
, for example, but not in Button
(ish) controls, even though additions are in the list.
Obviously, I don't understand the policy governing the addition of children. Any ideas?
public class QButton extends Button {
...
public QButton(String str) {
...
ObservableList<Node> children = getChildren();
System.out.println("Children: " + children);
Rectangle rect = new Rectangle(0., 0., 200., 200.);
rect.setStroke(Color.RED);
rect.setFill(Color.TRANSPARENT);
rect.setStrokeWidth(2);
getChildren().add(rect);
rect.setVisible(true);
children = getChildren();
System.out.println("Children: " + children);
}
...
Upvotes: 2
Views: 11595
Reputation: 36649
In JavaFX, UI Controls use the MVC (Model/View/Controller) pattern. The Control itself (e.g. a Button
) is the Model, and rendering is delegated to the View which is called Skin in JavaFX. The node graph of the Control is maintained by the View (the Skin), hence it is not possible to simply modify the children of the Control. See https://wiki.openjdk.java.net/display/OpenJFX/UI+Controls+Architecture for a good overview of the architecture.
One alternative might be to put the Button
and the Rectangle
into a Group
so that the Rectangle
overlays the Button
, and then use the Group
in your application's scene graph.
Otherwise, if you really want to subclass Button
and add the Rectangle
there, you need to create a custom skin for your Button
. The following example shows how this can be done - one drawback is that the base skin class for Buttons (ButtonSkin
) is not public, hence you need to extend a non-public class (which you normally should NEVER do, but I currently do not see an alternative besides duplicating a whole bunch of code from com.sun.javafx
). In Java 9, the skin classes will be made public in the javafx.scene.control.skin
package (see
http://download.java.net/jdk9/jfxdocs/javafx/scene/control/skin/ButtonSkin.html for the ButtonSkin
).
The following code shows how it could be done:
package com.example;
import javafx.scene.control.Button;
import javafx.scene.control.Skin;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
// DO NOT DO THIS - com.sun is not public!
// (However, it seems that there is currently no alternative besides duplicating the code)
// With Java 9, change to javafx.scene.control.skin.ButtonSkin
class QButtonSkin extends com.sun.javafx.scene.control.skin.ButtonSkin {
private Rectangle rect = null;
public QButtonSkin(Button button) {
super(button);
}
@Override
protected void updateChildren() {
super.updateChildren();
if (rect == null) {
rect = new Rectangle(0., 0., 200., 200.);
rect.setStroke(Color.RED);
rect.setFill(Color.TRANSPARENT);
rect.setStrokeWidth(2);
}
getChildren().add(rect);
}
}
public class QButton extends Button {
public QButton(String str) {
super(str);
}
@Override
protected Skin<?> createDefaultSkin() {
Skin<?> result = new QButtonSkin(this);
return result;
}
}
Essentially, QButton
creates a new default skin (you could also do that in CSS). The new Skin overrides updateChildren()
to add the rectangle to the control's child nodes. Here we need to consider the calling sequence - the super class's constructor calls updateChildren()
itself, so we can not create the Rectangle
in our Skin's constructor.
Depending on your use case, you might need to add some additional code like managing the bounds and geometry of QButtonSkin
.
Upvotes: 3