why is my circle filled Black even though i haven't set it to be filled - javafx

I am currently working on an assignment where i must print a circle in the center of the primary stage and 4 buttons on the bottom center area of the primary stage which move the circle, up, down, left, and right when clicked. when i run my code, my circle is filled in with the color black. I have set the stroke of the circle to be black but i have not set the circle to be filled black. I know i can just set my circle to be filled white and somewhat solve the problem, but i am wondering if anyone knows why this is happening. Also, i cannot get the Circle and the buttons to print into the same window. I can get either the circle to print by setting the primaryStage to the scene or print the buttons by setting the scene to hBox and then setting the primaryStage to the scene. How should i best change my code so that the buttons and the circle are both displayed?
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.geometry.Pos;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;
public class Btest extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
// Create a border pane
BorderPane pane = new BorderPane();
// create Hbox, set to bottom center
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setAlignment(Pos.BOTTOM_CENTER);
Button btLeft = new Button("Left");
Button btDown = new Button("Down");
Button btUp = new Button("Up");
Button btRight = new Button("Right");
hBox.getChildren().addAll(btLeft, btDown, btUp, btRight);
// Lambda's
btLeft.setOnAction((e) -> {
System.out.println("Process Left");
});
btDown.setOnAction((e) -> {
System.out.println("Process Down");
});
btUp.setOnAction(e -> {
System.out.println("Process Up");
});
btRight.setOnAction((e) -> {
System.out.println("Process Right");
});
pane.setCenter(new CenteredCircle("Center"));
// Create a scene and place it in the stage
Scene scene = new Scene(pane, 300, 300);
//set stage and display
primaryStage.setTitle("ShowBorderPane"); // Set the stage title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
public static void main(String[] args) {
Application.launch(args);
}
}
// create custom class for circle
class CenteredCircle extends StackPane {
public CenteredCircle(String title) {
setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
Circle circle = new Circle();
circle.setStroke(Color.BLACK);
circle.setCenterX(50);
circle.setCenterY(50);
circle.setRadius(50);
getChildren().add(circle);
}
}

"Why is my circle filled Black even though i haven't set it to be filled?"
Because the default color is black. See the doc of Shape.setFill() method:
Defines parameters to fill the interior of an Shape using the settings
of the Paint context. The default value is Color.BLACK for all shapes
except Line, Polyline, and Path. The default value is null for those
shapes.
"... Also, i cannot get the Circle and the buttons to print into the same window."
Put the Hbox to the parent BorderPane, for instance into the bottom:
pane.setBottom( hBox );

Related

JavaFX - Overlapping panes

I'm trying to find a solution where a layer can overlap an other layer without pushing it to any direction. Similar like this: https://docs.oracle.com/javase/7/docs/api/javax/swing/JLayeredPane.html but in JavaFX.
It's seems like a very basic feature, but I can't really find a solution.
What I would like to achieve is something like the following:
I would like a root Node, let's say a BorderPane, which in its left there is a (settings) pane and in its center the main content. When the user clicks on a button in the center, the left pane is showing up without pushing the center pane to the right. And that is the problem, because the desired behavior would be to be OVER the centered content not next to it.
toFront and toBack functions at first glance seemed like a possible solution, but it only changes rendering order.
Unfortunately, I don't think the problem can be done with a BorderPane as it can't manage overlapping. But let's hope I'm wrong here. It's not mandatory to achieve this with a BorderPane. It's enough if it works similar that I mentioned in the above section.
Maybe it can be achieved with a SubScene, but I can't really know how.
SubScene documentation: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/SubScene.html
Any help is much appreciated.
Update: Example image
Same as #Nand & #LBald suggestion, I too think a StackPane could be a good choice in this case. Below is a quick demo to show the overlay node with a little fade effect.
import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.stream.Stream;
public class OverlayLayout_Demo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
StackPane root = new StackPane();
Scene scene = new Scene(root, 500, 500);
primaryStage.setScene(scene);
primaryStage.setTitle("Node Overlay Demo");
primaryStage.show();
HBox hBox = new HBox(new Button("One"), new Button("Two"));
hBox.setPadding(new Insets(10));
hBox.setSpacing(10);
StackPane hPane = new StackPane(hBox);
hPane.setMaxHeight(100);
hPane.setVisible(false);
hPane.setStyle("-fx-background-color:#55555550");
VBox vBox = new VBox(new Button("One"), new Button("Two"));
vBox.setPadding(new Insets(10));
vBox.setSpacing(10);
StackPane vPane = new StackPane(vBox);
vPane.setMaxWidth(100);
vPane.setVisible(false);
vPane.setStyle("-fx-background-color:#55555550");
Button left = new Button("Left");
Button top = new Button("Top");
Button right = new Button("Right");
Button bottom = new Button("Bottom");
VBox buttons = new VBox(left, top, right, bottom);
buttons.setStyle("-fx-border-width:2px;-fx-border-color:black;");
buttons.setSpacing(10);
buttons.setAlignment(Pos.CENTER);
StackPane.setMargin(buttons, new Insets(15));
StackPane content = new StackPane(buttons);
content.setOnMouseClicked(e -> {
Node node = vPane.isVisible() ? vPane : hPane;
FadeTransition ft = new FadeTransition(Duration.millis(300), node);
ft.setOnFinished(e1 -> node.setVisible(false));
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.play();
});
root.getChildren().addAll(content, hPane, vPane);
Stream.of(left, top, right, bottom).forEach(button -> {
button.setOnAction(e -> {
vPane.setVisible(false);
hPane.setVisible(false);
Node node;
switch (button.getText()) {
case "Left":
case "Right":
node = vPane;
StackPane.setAlignment(vPane, button.getText().equals("Left") ? Pos.CENTER_LEFT : Pos.CENTER_RIGHT);
break;
default:
node = hPane;
StackPane.setAlignment(hPane, button.getText().equals("Top") ? Pos.TOP_CENTER : Pos.BOTTOM_CENTER);
}
node.setVisible(true);
FadeTransition ft = new FadeTransition(Duration.millis(300), node);
ft.setFromValue(0.0);
ft.setToValue(1.0);
ft.play();
});
});
}
public static void main(String... args) {
Application.launch(args);
}
}
You could use a simple Pane to take care of the main content and the config overlapping pane, and then adds a listener in the main content that changes the visibility of the config pane.
Pane container = new Pane();
Pane mainContent = ... ;
// you main content pane stuff
Pane config = ... ;
// your config pane stuff
container.getChildren().addAll(mainContent, config); // in this order
mainContent.setOnMouseClicked(e -> config.setVisible( ! config.isVisible()) );

JavaFX HBox layout binding as Circle

I'm having a problem positioning JavaFX's HBox in a similar manner to Circle.
If using a circle shape it is possible to manually position it such that it is bound to a different node. This is what I've done until now, by having a Pane as the point of reference:
Pane node; //can be dragged around/resized
//...
Circle terminal = new Circle(10);
terminal.setStroke(Color.GREEN);
terminal.setFill(Color.GREEN);
terminal.centerXProperty().bind( node.layoutXProperty() );
terminal.centerYProperty().bind( node.layoutYProperty() );
The pane (node) functions as a graph node and can be dragged around and resized. The circle functions as a port/terminal for edge connections in the graph. Seeing that the node should have more than one the idea is to put the circles into an HBox that is attached/bound to the pane like the circle has until now. This makes it so that manual layout calculations are unnecessary when adding or removing ports, resizing the node, etc. So the code then used was:
Pane node; //can be dragged around/resized
//...
HBox terminalContainer = new HBox();
terminalContainer.layoutXProperty().bind( node.layoutXProperty() );
terminalContainer.layoutYProperty().bind( node.layoutYProperty() );
//... adding circles into HBox as scenegraph children
The only difference is swapping out the HBox for the Circle and using the layoutXProperty() as there is no centerXProperty(). But of course this fails, and the ports appear glued on to the top part of the containing frame, acting strangely. Is there a fix for this? I tried changing the parenting Pane to an anchorPane, this allowed to manually anchor down the HBox in the correct place, but caused issues with the resizing/dragging code.
Minimal example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main2 extends Application {
private AnchorPane component;
#Override
public void start(Stage primaryStage) {
component = new AnchorPane();
Scene scene = new Scene(component, 1024, 768);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
//This works, but is hard to maintain
Cell c1 = new Cell();
Cell c2 = new Cell();
Port p1 = new Port(c1);
Port p2 = new Port(c2);
component.getChildren().addAll(c1, c2, p1, p2);
c1.relocate(150, 150);
c2.relocate(550, 550);
//This does not work, even if unbinding circles, but is simpler
HBox pc1 = new HBox();
HBox pc2 = new HBox();
pc1.layoutXProperty().bind( c1.layoutXProperty() );
pc1.layoutYProperty().bind( c1.layoutYProperty() );
pc2.layoutXProperty().bind( c2.layoutXProperty() );
pc2.layoutYProperty().bind( c2.layoutYProperty() );
Port p3 = new Port(c1);
Port p4 = new Port(c2);
pc1.getChildren().add(p3);
pc2.getChildren().add(p4);
component.getChildren().addAll(pc1, pc2);
}
class Cell extends Pane {
public Cell() {
Rectangle view = new Rectangle(50,50);
view.setStroke(Color.DODGERBLUE);
view.setFill(Color.DODGERBLUE);
getChildren().add(view);
}
}
class Port extends Pane {
public Port(Cell owner) {
Circle view = new Circle(10);
view.setStroke(Color.GREEN);
view.setFill(Color.GREEN);
view.centerXProperty().bind( owner.layoutXProperty() );
view.centerYProperty().bind( owner.layoutYProperty() );
getChildren().add(view);
}
}
public static void main(String[] args) {
launch(args);
}
}
Got it to work, was a typo in the code binding the layoutXProperty twice instead of the layoutYProperty facepalm

JavaFX ScrollPane and Scaling of the Content

I would like to show a photo as an ImageView in a ScrollPane with an ZoomIn and ZoomOut Function. But if I reduce by means of scale the imageview, an undesirable empty edge is created in the ScrollPane. How can you make sure that the ScrollPane is always the size of the scaled ImageView?
See the following example. For simplicity, I replaced the ImageView with a rectangle.
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ScrollPaneDemo extends Application {
double scale;
Pane contPane = new Pane();
#Override
public void start(Stage primaryStage) {
BorderPane pane = new BorderPane();
ScrollPane sp = new ScrollPane();
sp.setContent(contPane);
sp.setVvalue(0.5);
sp.setHvalue(0.5);
Rectangle rec = new Rectangle(2820, 1240,Color.RED);
scale = 0.2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
contPane.getChildren().add(rec);
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
scale*=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
scale/=2;
contPane.setScaleX(scale);
contPane.setScaleY(scale);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
contPane scaled by using transform don't change its layoutBounds automatically. If you want not to make empty space in contPane, you'd better wrap the node in Group.
See this post. Layout using the transformed bounds
sp.setContent(new Group(contPane));
In addition, if you don't want to make empty space in ScrollPane, limit minimum scale to rate which width or height of the content fits viewport's one.
Button but1 = new Button("+");
but1.setOnAction((ActionEvent event) -> {
updateScale(scale * 2.0d);
});
Button but2 = new Button("-");
but2.setOnAction((ActionEvent event) -> {
updateScale(scale / 2.0d);
});
HBox buttons = new HBox(but1, but2);
pane.setTop(buttons);
pane.setCenter(sp);
Scene scene = new Scene(pane, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
updateScale(0.2d);
private void updateScale(double newScale) {
scale = Math.max(newScale, Math.max(sp.getViewportBounds().getWidth() / rec.getWidth(), sp.getViewportBounds().getHeight() / rec.getHeight()));
contPane.setScaleX(scale);
contPane.setScaleY(scale);
}
Consider a case of the image is smaller than ScrollPane's viewport. Because for showing no empty space, this code will stretch contents when it doesn't have enough size.
In a case of huge images, TravisF's comment helps you.

Disable Background stage javafx

I'm french, sorry for mistake.
I have an primary stage, and foreground an small second stage. I want to color in gray all the primary stage when the second stage is visible.
It's good, i cannot click in the primary stage with the line :
stage.initModality(Modality.WINDOW_MODAL);
But I want to put a color gray in the primary stage.
I try to disable all componant in the primary stage (every componant is gray and disable) but imageViews are not gray, it's a problem.
Help please.
Thanks.
You can add all to a Stackpane and make a Region as a veil (visible=true/false).
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class VeilDemo extends Application {
#Override
public void start(Stage primaryStage) {
// that is the veil
Region veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.3)");
veil.setVisible(false);
Button btn = new Button();
btn.setText("Open Dialog");
btn.setOnAction((ActionEvent event) -> {
Alert a = new Alert(Alert.AlertType.INFORMATION);
//veil is only visible when alert window is showing
veil.visibleProperty().bind(a.showingProperty());
a.setContentText("The main window should be decorated with a veil.");
a.setX(primaryStage.getX() + 200); // This is only for showing main window
a.showAndWait();
});
Image img = new Image("https://www.gnu.org/graphics/gnu-head-sm.png");
ImageView iv = new ImageView(img);
// this should be the normal root of window
BorderPane bp = new BorderPane(iv);
bp.setBottom(btn);
StackPane root = new StackPane();
root.getChildren().addAll(bp, veil);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
The main window will look like this:
and if the button was clicked, the info window opens and the veil is visibile on the main stage.

Getting a MouseEvent to target ImageViews inside a TilePane

I have many ImageViews inside a TilePane, that is inside a StackPane and then a ScrollPane. There is no border, padding, or margin between the children of the TilePane so there is no chance that I'm not clicking on an ImageView. When I click on an image, I want the target of the MouseEvent to be the ImageViews, but instead it is the TilePane.
How can I get the event chain to end on an ImageView instead of ending early on the TilePane?
Otherwise, is there a way I can get the ImageView using other information? Perhaps using the coordinates of the event?
The usual way I do this is just to register the mouse listener with the node in which I am interested; in your case this means register a mouse listener with each ImageView. It's easy then to have each mouse listener have a reference to the particular image view with which it's registered, or to other data (e.g. a filename) if you need.
One thing that might be happening: if your images have transparent pixels, then mouse clicks on that part of the image will by default "drop through" to the node below. You can change this behavior by calling imageView.setPickOnBounds(true); on the image views.
Some test code. If you run this you'll see some numbered images with different colored backgrounds. About 1 in 4 have transparent backgrounds (they appear white). If you click on these (but not on the actual text of the number), you'll see the mouse handlers registered with the scroll pane and stack pane have the tile pane as the target, and the handler registered with the ImageView is not even invoked. For those without the transparent background, the target is always the ImageView. If you select the check box, so pickOnBounds is true for all the ImageViews, both transparent and opaque images behave as you want.
import java.util.Random;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageViewClickTest extends Application {
private static final Random RNG = new Random();
#Override
public void start(Stage primaryStage) {
TilePane tilePane = new TilePane();
CheckBox pickOnBounds = new CheckBox("Pick on bounds");
pickOnBounds.setPadding(new Insets(16));
for (int i=1; i<=200; i++) {
ImageView imageView = createImageView(i);
imageView.pickOnBoundsProperty().bind(pickOnBounds.selectedProperty());
// mouse handler directly on image view:
// can access image-view specific data...
String message = "Clicked on Image "+i ;
imageView.setOnMouseClicked(e ->
System.out.println("From handler on ImageView: "+message));
tilePane.getChildren().add(imageView);
}
StackPane stack = new StackPane(tilePane);
stack.setOnMouseClicked(e -> {
// source will be the stack pane
// target will be the top-most node
// (i.e. the ImageView, in most cases)
System.out.println("From handler on stack pane: Source: "+e.getSource());
System.out.println("From handler on stack pane: Target: "+e.getTarget());
});
ScrollPane scroller = new ScrollPane(stack);
scroller.setFitToWidth(true);
scroller.setOnMouseClicked(e -> {
// source will be the scroll pane
// target will be the top-most node
// (i.e. the ImageView, in most cases)
System.out.println("From handler on scroller: Source: "+e.getSource());
System.out.println("From handler on scroller: Target: "+e.getTarget());
});
BorderPane root = new BorderPane(scroller, pickOnBounds, null, null, null);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private ImageView createImageView(int index) {
Label label = new Label(Integer.toString(index));
label.setAlignment(Pos.CENTER);
label.setMinSize(48, 48);
label.setStyle(randomStyle());
Image image = new Scene(label, Color.TRANSPARENT).snapshot(null);
ImageView imageView = new ImageView(image);
return imageView ;
}
private String randomStyle() {
StringBuilder style = new StringBuilder();
style.append("-fx-background-color: -fx-background;");
style.append("-fx-background: ");
if (RNG.nextDouble() < 0.25) {
style.append( "transparent;");
style.append(" -fx-text-fill: black;") ;
} else {
String bg = String.format("#%02x%02x%02x;",
RNG.nextInt(256), RNG.nextInt(256), RNG.nextInt(256));
style.append(bg);
}
return style.toString();
}
public static void main(String[] args) {
launch(args);
}
}

Resources