I am now learning Javafx and Scalafx, I have got a class called Room, which holds name:String, width:Long, length:Long and a position:Insets and in this class there is a method render which returns a scalafx.scene.Node
def render()={
val stack=new StackPane
val vbox=new VBox
vbox.getChildren.addAll(new Text(name))
stack.getChildren.addAll(new Rectangle(width,length),vbox)
Node.sfxNode2jfx(stack)
}
and I have a class called house which holds a list of Room
val room_list:Seq[Room]
and also I have a render method:
override def render=
{
val ap=new AnchorPane
for(i <- 0 until room_list.length)
{
AnchorPane.setLeftAnchor(room_list(i).render,room_list(i).position.left)
AnchorPane.setRightAnchor(room_list(i).render,room_list(i).position.right)
AnchorPane.setTopAnchor(room_list(i).render,room_list(i).position.top)
AnchorPane.setBottomAnchor(room_list(i).render,room_list(i).position.bottom)
}
}
at last I want to draw this AnchorPane in the main class, but actually what I got is that, all those rooms are setteled on the top left corner, which means the for loop for AnchorPane is compeletly useless, why?
Related
As the title basically. I have a node in JavaFX which I want to be displayed in front of all other nodes according to certain CSS rules. I do not want this to change the ordering of the nodes in my VBox which .toFront() appears to do. See this question.
Is this even possible?
EDIT: To clarify. The situation is the following. I have a VBox containing a bunch of tightly packed ImageViews. When I hover over one I want it to grow slightly to give it the feel that the image is being lifted off of the screen. But since the ImageViews are so tightly packed only the top edge grows (visibly). The bottom edge grows but is below the following image and cannot be seen.
EDIT 2: Upon request here is a screenshot of what I am doing.
The different colour gradients are ImageViews and as I hover over one it should grow as the top edge of the top gradient has in this image (look closely at the top right corner next to the X). However as is also visible in this image the bottom edge of this ImageView has become hidden by the next gradient in this VBox and the grow is not visible.
This sounds like the perfect situation for using the viewOrder property of Node added in Java 9. The viewOrder controls how Nodes are drawn in relation to other Nodes of the same Parent without changing the order of the Nodes in the child list. Here's the Javadoc:
Defines the rendering and picking order of this Node within its parent.
This property is used to alter the rendering and picking order of a
node within its parent without reordering the parent's children list.
For example, this can be used as a more efficient way to implement
transparency sorting. To do this, an application can assign the
viewOrder value of each node to the computed distance between that
node and the viewer.
The parent will traverse its children in decreasing viewOrder order.
This means that a child with a lower viewOrder will be in front of a
child with a higher viewOrder. If two children have the same
viewOrder, the parent will traverse them in the order they appear in
the parent's children list.
However, viewOrder does not alter the layout and focus traversal order
of this Node within its parent. A parent always traverses its children
list in order when doing layout or focus traversal.
Here's an example using this property:
import javafx.animation.ScaleTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
var box = new HBox(createRectangles(Color.DARKBLUE, Color.FIREBRICK, 25));
box.setAlignment(Pos.CENTER);
box.setPadding(new Insets(50, 20, 50, 20));
primaryStage.setScene(new Scene(box));
primaryStage.show();
}
private Rectangle[] createRectangles(Color start, Color end, int count) {
var list = new ArrayList<Rectangle>(count);
for (double i = 0; i < count; i++) {
var rect = new Rectangle(30, 60, start.interpolate(end, i / count));
var scaleTrans = new ScaleTransition(Duration.millis(250), rect);
scaleTrans.setFromX(1.0);
scaleTrans.setFromY(1.0);
scaleTrans.setToX(1.2);
scaleTrans.setToY(1.2);
rect.setOnMouseEntered(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(1.0);
rect.setViewOrder(-1.0);
scaleTrans.play();
});
rect.setOnMouseExited(e -> {
scaleTrans.stop(); // <--- doesn't seem necessary*
scaleTrans.setRate(-1.0);
rect.setViewOrder(0.0);
scaleTrans.play();
});
// *the "stop()"'s don't seem to be necessary. When I commented
// them out the animation still worked. In fact, the animation
// actually seems smoother in the situation where you move the
// mouse over and then away quickly (before the zoom-in completes).
list.add(rect);
}
return list.toArray(new Rectangle[0]);
}
}
It uses Rectangles instead of ImageViews but the concept is the same. When the mouse hovers over a Rectangle it sets the view order to be lower than the others and then plays a ScaleTransition to make it bigger. When the mouse exits it resets the view order back to 0 and then reverses the ScaleTransition.
Note: I used the var keyword which was added in Java 10.
And here is a GIF of the example in action:
Edit: Since you brought up CSS I went and checked if the view order could be set from a stylesheet. And it appears it can. Looking at the CSS Reference Guide there is a CSS property defined for Node named -fx-view-order.
Here is one such solution, creating a new stage to show the zoomed in image.
I do not set the proper coordinates in this sample, but this works as a proof of concept.
In a nutshell: capture the onMouseEntered and onMouseExited events and hide or show the new stage.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
FlowPane root = new FlowPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 0; i < 25; i++) {
root.getChildren().add(getImagePane());
}
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private VBox getImagePane() {
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(50);
setHeight(50);
setStyle("-fx-border-color: black");
}};
ImageView img = new ImageView("sample/imageGallery/cerulean.png") {{
setFitWidth(50);
setFitHeight(50);
setPreserveRatio(true);
Stage stage = zoomedStage(pane, this);
setOnMouseEntered(mouseEvent -> stage.show());
setOnMouseExited(mouseEvent -> stage.hide());
}};
pane.getChildren().add(img);
return pane;
}
private Stage zoomedStage(VBox parent, ImageView img) {
Stage stage = new Stage();
stage.setWidth(110);
stage.setHeight(110);
VBox pane = new VBox() {{
setAlignment(Pos.CENTER);
setPadding(new Insets(5));
setWidth(110);
setHeight(110);
setStyle("-fx-border-color: black");
}};
pane.setPickOnBounds(false);
ImageView zoomedImage = new ImageView(img.getImage());
zoomedImage.setFitHeight(100);
zoomedImage.setFitWidth(100);
pane.getChildren().add(zoomedImage);
stage.setScene(new Scene(pane));
stage.initStyle(StageStyle.UNDECORATED);
return stage;
}
}
From here, it should just be a matter or fixing the stage's coordinates to be centered over the image and then remove the stage's decoration.
Known Issues:
You will also need to handle the issue with the mouse cursor being blocked by the new stage. This will lead to a loop where the mouse is constantly entering and exiting the thumbnail of the image, causing the zoomed in stage to flicker.
From my point of view, you have a VBox with spacing 0 so each ImageView is tightly packed to each other so the glow effect is not well visible in every image. In that case you could just add a margin each time you want to select an ImageView in order to 'help' the glowing effect to appear.
For Java 8 :
Sadly, this can't be happened from a CSS cause the ImageView does not provide any rule for setting margin or padding. So you are more or less (in my opinion) bound to write that behaviour through code.
:
private Node createImageView(String imageLink) {
// Setting the image view
ImageView imageView = new ImageView(imageLink);
// setting the fit width of the image view
imageView.setFitWidth(400);
// Setting the preserve ratio of the image view
imageView.setPreserveRatio(true);
// Instantiating the Glow class
Glow glow = new Glow();
imageView.setOnMouseEntered(e -> {
// setting level of the glow effect
glow.setLevel(0.9);
// Adding a margin on TOP and Bottom side just to make the
// glowing effect visible
VBox.setMargin(imageView, new Insets(2,0,2,0));
});
imageView.setOnMouseExited(e -> {
// remove the glow effect
glow.setLevel(0.0);
// remove the margins
VBox.setMargin(imageView, new Insets(0));
});
// Applying bloom effect to text
imageView.setEffect(glow);
return imageView;
}
I want to create an API to modify a Scene.
For sample, the default fxml had following Components:
With an API, i wan't to add some Components under the Connection Name:, for sample:
First, i had created an Controller for the FXML:
<BorderPane fx:controller="ConnectionController" [...]
and add an ID to the GridPane, that i wan't to modify:
<GridPane fx:id="form" [...]
On Controller's side, i had register the Component to speak with it:
import javafx.fxml.FXML;
import javafx.scene.layout.GridPane;
public class ConnectionController extends Controller {
#FXML GridPane form;
public void init(Main main) {
this.main = main;
}
public GridPane getForm() {
return form;
}
}
But when i try to get the Controller from the FXML to get the GridPane for adding some Components, nothing will be applied, for Sample:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui/" + file + ".fxml"));
Parent root1 = (Parent) fxmlLoader.load();
theController = fxmlLoader.getController(); // <<<< Here i get the Controller
[...] // Set the Stage, add to Scene and show the Stage...
On the Plugin's side:
Button button = new Button();
button.setText("click me!");
theController.getForm().add(button, 0, 1, 0, 0);
Can you tell me, what's wrong? I had not really experience with JavaFX - On "older Java techniques" with AWT/Swing components, i know, you must invalidate or **repaint* the components... It's here the same possibility?
EDIT:
When i get the GridPane with getForm() method, i can change the style properties like:
theController.getForm().setStyle("-fx-background-color: #FF0000");
But when i set the Background Color to red, add the Button and set after that the Background Color to green for example, the background will be not painted green:
theController.getForm().setStyle("-fx-background-color: #FF0000");
theController.getForm().add(button, 1, 1, 0, 0);
theController.getForm().setStyle("-fx-background-color: #0000FF");
This code is strange:
theController.getForm().add(button, 1, 1, 0, 0).
The GridPane API being called is:
add(Node child, int columnIndex, int rowIndex, int colspan, int rowspan).
So you are asking the node to span 0 rows and columns, which isn't really possible.
When you try to run this code on Java 8u144, I get an exception:
java.lang.IllegalArgumentException: rowSpan must be greater or equal to 1, but was 0.
My guess is that you are just swallowing the exception somewhere. That would explain why code after the add statement does not appear to execute (because the thrown exception changed the flow of control).
I have a requirement to display a Rectangle with an InnerShadow effect as the background of my JavaFX nodes. Each node needs to be resizable. I am implementing this with an abstract base class with a Region which has the Rectangle with an InnerShadow as one of its children and a Region provided by the concrete implementation of this class as its other child.
The problem occurs when I add one or more ComboBoxes to the child region. Clicking on the ComboBoxes has no effect, i.e. they do not display their drop-down list of items.
I have tried making the Rectangle smaller (i.e. 10x10 pixels) so that it does not overlap the ComboBoxes. This makes no difference.
private void createNodeWithBackground() {
pane = new Region() {
#Override
public void resize(double width, double height) {
super.resize(width, height);
backing = new Rectangle(width, height);
InnerShadow shadeEffect = new InnerShadow();
shadeEffect.setWidth(w/2);
shadeEffect.setHeight(h/2);
shadeEffect.setInput(new ColorAdjust(-0.1, 0.2, -0.1, 0.1));
backing.setEffect(shadeEffect);
getChildren().clear();
getChildren().addAll(backing, getBodyNode());
}
};
getChildren().add(pane);
}
/**
* The concrete class provides a node to be displayed on top of the
* Rectangle with the InnerShadow.
* This might be a VBox containing a ComboBox and other nodes.
*/
protected abstract Region getBodyNode();
Removing the Rectangle from the scene results in the ComboBoxes displaying drop-down lists as expected.
Using javafx.scene.control.ChoiceBox instead of javafx.scene.control.ComboBox resolves this issue, although I have no idea why.
I have in my javafx app an HBox with ImageViews, now I need some way to iterate through this HBox, but I can't find out an algorithm how to do it, I have tried to do something like this:
Object[] stack = stackWrapp.getChildren().toArray();
where stack is my HBox, but the ImageViews in this way will be duplicated, what I don't want. I don't know why So how can I do it..
You can do it like this:
for (Node child : stackWrapp.getChildren()) {
ImageView imgView = (ImageView) child;
...
}
To be on the save side you can also do a type check before the casting, just in case there are other Nodes in your HBox than only ImageView.
viewScroll.setContent(new ImageView(bigimg));
double w = viewScroll.getContent().getBoundsInLocal().getWidth();
double vw = viewScroll.getViewportBounds().getWidth();
viewScroll.getContent().setTranslateX((vw/2)-(w/2));
viewScroll.toFront();
I set an ImageView with some Image inside the ScrollPane but the ImageView always goes to the far left corner. Here I'm trying to manually offset the difference, but it doesn't work well. The ImageView goes too far to the right plus it only updates once because it's inside the eventhandler for a button.
Here is an example using a label without the need for listeners:
public void start(Stage primaryStage) throws Exception {
ScrollPane scrollPane = new ScrollPane();
Label label = new Label("Hello!");
label.translateXProperty().bind(scrollPane.widthProperty().subtract(label.widthProperty()).divide(2));
scrollPane.setContent(label);
Scene scene = new Scene(scrollPane);
primaryStage.setScene(scene);
primaryStage.setWidth(200);
primaryStage.setHeight(200);
primaryStage.show();
}
I am not sure if you are familiar with properties or not, but in the code above, when I bind the translateXProperty to the formula, every time one of the dependencies changes, such as the ScrollPane's widthProperty or the Label's widthProperty, the formula is recalculated and translateXProperty is set to the result of the formula.
I am not sure, but in your previous code, it appears that the calculation code would be in a resize listener. This is not required when dealing with properties as they update whenever dependencies changed (note the bind() and not set()).