how to add imageView to javafx region element? - imageview

I would like to know how I can add ImageView Elements to a Region Element in JavaFx 2.1.
Maybe I get the usage od this element wrong, but AFAIK it is a container for child elements as well.
The Background is that I need an area of defined size which should display image elements independent of the viewport on the region, so I can't use the Group element as a container.

Use a Pane or a Pane subclass.
Panes are Regions to which you can add children using the getChildren() api. Pane is very similar to a Group; e.g. has a simple api for adding children and does not explicitly layout the location of the children. It also has the aspects of a Region; e.g. css styleable, resize capable, etc. Region's only have an unmodifiable list of children via their public API, meaning that the only way to add children to them is to subclass them (as Pane does for you already). The Region class itself is really just a building block class for control creators and not something you would instantiate during normal development.
Here is an example of adding ImageView nodes to a Pane.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.*;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class RegionSample extends Application {
public static void main(String[] args) throws Exception { launch(args); }
public void start(Stage stage) throws Exception {
Pane pane = new Pane();
pane.setStyle("-fx-background-color: linear-gradient(to bottom right, derive(goldenrod, 20%), derive(goldenrod, -40%));");
ImageView iv1 = new ImageView(new Image("http://icons.iconarchive.com/icons/kidaubis-design/cool-heroes/128/Ironman-icon.png")); // Creative commons with attribution license for icons: No commercial usage without authorization. All rights reserved. Design (c) 2008 - Kidaubis Design http://kidaubis.deviantart.com/ http://www.kidcomic.net/ All Rights of depicted characters belong to their respective owners.
ImageView iv2 = new ImageView(new Image("http://icons.iconarchive.com/icons/kidaubis-design/cool-heroes/128/Starwars-Stormtrooper-icon.png"));
iv1.relocate(10, 10);
iv2.relocate(80, 60);
pane.getChildren().addAll(iv1, iv2);
stage.setScene(new Scene(pane));
stage.show();
}
}

Related

Why strange painting behavior in JavaFX

I have a simple FX example with a simple component.
package fxtest;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage stage) {
var bp = new BorderPane();
var r = new Rectangle(0, 0, 200, 200);
r.setFill(Color.GREEN);
var sp = new StackPane(r);
bp.setCenter(sp);
bp.setTop(new XPane());
bp.setBottom(new XPane());
bp.setLeft(new XPane());
bp.setRight(new XPane());
var scene = new Scene(bp, 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
package fxtest;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class XPane extends Region {
public XPane() {
setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
setMinSize(100, 100);
setPrefSize(100, 100);
widthProperty().addListener((o) -> {
populate();
});
heightProperty().addListener((o) -> {
populate();
});
populate();
}
private void populate() {
ObservableList<Node> children = getChildren();
Rectangle r = new Rectangle(getWidth(), getHeight());
r.setFill(Color.WHITE);
r.setStroke(Color.BLACK);
children.add(r);
Line line = new Line(0, 0, getWidth(), getHeight());
children.add(line);
line = new Line(0, getHeight(), getWidth(), 0);
children.add(line);
}
}
When run, it does what I expect:
When I grow the window, the X's grow.
But when I shrink the window, I get artifacts of the side panels.
I would have thought erasing the backgrounds would have fixed this, but I guess there's some ordering issue. But even still, when you drag the corner, all of the XPanes change size, and they all get repainted, but the artifacts remain.
I tried wrapping the XPanes in to a StackPane, but that didn't do anything (I didn't think it would, but tried it anyway).
How do I remedy this? This is JavaFX 13 on JDK 16 on macOS Big Sur.
Why you get artifacts
I think a different approach should be used rather than fixing the approach you have, but you could fix it if you want.
You are adding new rectangles and lines to your XPane in listeners. Every time the height or width changes, you add a new set of nodes, but the old set of nodes at the old height and widths remains. Eventually, if you resize enough, performance will drop or you will run out of memory or resources, making the program unusable.
A BorderPane paints its children (the center and the XPanes) in the order they were added without clipping, so these old lines will remain and the renderer will paint them over some panes as you resize. Similarly, some panes will paint over some lines because you are building up potentially lots of filled rectangles in the panes and they are partially overlapping lots of lines created.
To fix this, clear() the child node list in your populate() method before you add any new nodes.
private void populate() {
ObservableList<Node> children = getChildren();
children.clear();
// now you can add new nodes...
}
Alternate Solution
Change listeners on widths and heights aren't really the place to add content to a custom region, IMO.
I think that it is best to take advantage of the scene graph and let it handle the repainting and updating of existing nodes after you change the attributes of those nodes, instead of creating new nodes all the time.
Here is an example that subclasses Region and paints fine when a resize occurs.
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
public class XPane extends Region {
public XPane() {
super();
Rectangle border = new Rectangle();
Line topLeftToBottomRight = new Line();
Line bottomLeftToTopRight = new Line();
getChildren().addAll(
border,
topLeftToBottomRight,
bottomLeftToTopRight
);
border.setStroke(Color.BLACK);
border.setFill(Color.WHITE);
border.widthProperty().bind(
widthProperty()
);
border.heightProperty().bind(
heightProperty()
);
topLeftToBottomRight.endXProperty().bind(
widthProperty()
);
topLeftToBottomRight.endYProperty().bind(
heightProperty()
);
bottomLeftToTopRight.startYProperty().bind(
heightProperty()
);
bottomLeftToTopRight.endXProperty().bind(
widthProperty()
);
setMinSize(100, 100);
setPrefSize(100, 100);
}
}
On Region vs Pane
I'm not sure if you should be subclassing Pane or Region, the main difference between the two is that a Pane has a public accessor for a modifiable child list, but a Region does not. So it would depend on what you are trying to do. If it is just drawing X's like the example, then Region is appropriate.
On layoutChildren() vs binding
The Region documentation states:
By default a Region inherits the layout behavior of its superclass,
Parent, which means that it will resize any resizable child nodes to
their preferred size, but will not reposition them. If an application
needs more specific layout behavior, then it should use one of the
Region subclasses: StackPane, HBox, VBox, TilePane, FlowPane,
BorderPane, GridPane, or AnchorPane.
To implement a more custom layout, a Region subclass must override
computePrefWidth, computePrefHeight, and layoutChildren. Note that
layoutChildren is called automatically by the scene graph while
executing a top-down layout pass and it should not be invoked directly
by the region subclass.
Region subclasses which layout their children will position nodes by
setting layoutX/layoutY and do not alter translateX/translateY, which
are reserved for adjustments and animation.
I am not actually doing that here, instead, I am binding in the constructor rather than overriding layoutChildren(). You could implement an alternate solution that operates as the documentation discusses, overriding layoutChildren() rather than using binding, but it is more complicated and less well documented on how to do that.
It is uncommon to subclass Region and override layoutChildren(). Instead, usually, a combination of standard layout Panes will be used and constraints set on the panes and nodes to get the desired layout. This lets the layout engine do a lot of the work such as snapping to pixels, calculating margins and insets, respecting constraints, repositioning content, etc, a lot of which would need to be done manually for a layoutChildren() implementation.
One common approach is to bind the relevant geometric properties to the desired properties of the enclosing container. A related example is examined here, and others are collected here.
The variation below binds the vertices of several Shape instances to the Pane width and height properties. Resize the enclosing stage to see how the BorderPane children conform to entries in the BorderPane Resize Table. The example also adds a red Circle, which stays centered in each child, growing and shrinking in the center to fill the smaller of the width or height. The approach relies on the fluent arithmetic API available to properties that implement NumberExpression or methods defined in Bindings.
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2));
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
/**
* #see https://stackoverflow.com/q/70311488/230513
*/
public class App extends Application {
#Override
public void start(Stage stage) {
var bp = new BorderPane(new XPane(), new XPane(),
new XPane(), new XPane(), new XPane());
stage.setScene(new Scene(bp, 640, 480));
stage.show();
}
private static class XPane extends Pane {
private final Rectangle r = new Rectangle();
private final Circle c = new Circle(8, Color.RED);
private final Line line1 = new Line();
private final Line line2 = new Line();
public XPane() {
setPrefSize(100, 100);
r.widthProperty().bind(this.widthProperty());
r.heightProperty().bind(this.heightProperty());
r.setFill(Color.WHITE);
r.setStroke(Color.BLACK);
getChildren().add(r);
line1.endXProperty().bind(widthProperty());
line1.endYProperty().bind(heightProperty());
getChildren().add(line1);
line2.startXProperty().bind(widthProperty());
line2.endYProperty().bind(heightProperty());
getChildren().add(line2);
c.centerXProperty().bind(widthProperty().divide(2));
c.centerYProperty().bind(heightProperty().divide(2));
NumberBinding diameter = Bindings.min(widthProperty(), heightProperty());
c.radiusProperty().bind(diameter.divide(2));
getChildren().add(c);
}
}
public static void main(String[] args) {
launch();
}
}

javafx: How do I set the bounds of a Group so it shows a specific window of the many shapes inside, which may or may not exceed my intended bounds?

I have a JavaFX Group that contains a lot of ImageViews and a few overlays. I want it to render a specific mathematical rectangle which usually does not match the bounding box of the combined children. For a simpler concrete example I have created the following app:
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.stage.*;
public class GroupWindow
extends Application
{
#Override
public void start(Stage stage)
throws Exception
{
Group gr = new Group();
Shape circle1 = new Circle(-300, 0, 50);
circle1.setFill(Color.GREEN);
gr.getChildren().add(circle1);
Shape circle2 = new Circle(0, 300, 50);
circle2.setFill(Color.GREEN);
gr.getChildren().add(circle2);
Shape LLrect = new Rectangle(-200, 0, 200,200);
LLrect.setFill(Color.BLUE);
gr.getChildren().add(LLrect);
VBox vbox = new VBox(gr);
stage.setScene(new Scene(vbox));
stage.show();
}
public static void main(String[] args)
{
Application.launch(args);
}
}
When I run the application I get a window that looks like
What I need is for it to look like
I could accomplish this if I knew the method call that would tell the Group to restrict its viewport to x in [-200..200] and y in [-200..200].
One comment suggests setting a clip. Adding a gr.setClip(new Rectangle(-200,-200, 400,400)); causes it to not draw the green circles, but the rendered window encompasses the same space as the first (undesirable) screenshot. So the clip does not affect how the Group decides its rendering window.
What technique should I use to specify this intent to javafx's layout engine?

How to position a button in JavaFX

I am using NetBeans IDE 8.2 for JavaFX. I already know that in order to change the position of a button I need to use setLayoutX/Y. I have tried this, and there is no effect on the buttons. Here is my code:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package javafxapplication2;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
*
* #author coolDawg1234
*/
public class JavaFXApplication2 extends Application {
#Override
public void start(Stage primaryStage) {
String x = "1";
String y = "0";
Button btn1 = new Button(x);
btn1.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.print(x);
}
});
Button btn2 = new Button(y);
btn2.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.print(y);
}
});
StackPane root = new StackPane();
root.getChildren().add(btn1);
btn1.setLayoutX(250);
btn1.setLayoutY(220);
root.getChildren().add(btn2);
btn1.setLayoutX(200);
btn1.setLayoutY(200);
Scene scene = new Scene(root, 1000, 1000);
primaryStage.setTitle("WHAT\'s GOOD MY MANS");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Netbeans gives me 0 errors for this, and everything other than the position of the buttons looks fine to me.
Please help me find my problem.
The container for your buttons is a StackPane. StackPane is an implementation of Pane such that it will, by default, layout its children at the center of itself. Therefore, whenever the scene needs to perform a layout, StackPane will set the layoutX and layoutY values (therefore overwriting whatever you had set) in its layoutChildren() method based on its own layout strategy. This behavior happens for most, if not all, subclasses of Pane.
If you need to manually position your child nodes, you need to use the generic Pane container. You can either choose to subclass it and provide your own layout strategy/logic, or simply set layoutX and layoutY values on the child nodes directly.
If you need the layout strategy provided by StackPane, but you would want it to be positioned slightly different from the default position, then you may be looking for translateXProperty() and translateYProperty().
30 root.getChildren().add(btn1);
31 btn1.setLayoutX(250);
32 btn1.setLayoutY(220);
33 root.getChildren().add(btn2);
34 btn1.setLayoutX(200);
35 btn1.setLayoutY(200);
Just take the right variable (consistent) btn1 gets two coordinates for the same direktion (250 for X in row 31, and 200 for X in row 34), so that button which has no directly set coordinates, has in this layout the coordinates (0,0).

JavaFX TabPane fails to render when inside ScrollPane and zoomed Group

I need a bunch of widgets, including a TabPane, inside a scrollable and zoomable view, basically this:
ScrollPane[ Group[ widgets .. including TabPane ] ]
The ScrollPane is obviously needed for scrolling, and the Group holds all the widgets and supports zooming.
The initial problem with that approach is that the ScrollPane shows scroll bars based on the original size of the widgets, not based on the actual size.
In the screenshot, note how scrollbars are shown even though the tab pane is much smaller than the viewport, so no scrollbars are needed.
The web site https://pixelduke.wordpress.com/2012/09/16/zooming-inside-a-scrollpane explains how to solve that by adding another nested Group:
ScrollPane[ Group[ Group[ widgets .. including TabPane ] ] ]
The inner Group, as before, holds all the widgets and supports zooming.
The outer Group automatically gets the layout bounds of the zoomed
widgets in the inner group, allowing the ScrollPane to correctly configure the scroll bars.
.. but now the TabPane will fail to properly draw itself.
All you see is the red background of the TabPane:
The complete tab pane only shows up once it's somehow forced to refresh.
The example code toggles the 'side' property of the tab pane when you press 'SPACE'.
Now I have it all: Tab Pane draws OK, inner group can be zoomed, scroll bars appear as soon as the zoomed content no longer fits the viewport. But having to force the Tab Pane refresh is certainly a hack.
Is there a fault in my scene graph?
Is this a bug in the TabPane rendering?
The problem certainly seems limited to the TabPane. When I add other groups, rectangles, buttons, text nodes to the 'widgets' in the inner group, they all render fine. Only the TabPane refuses to show its tabs.
Tried this with both JDK 1.8.0_51 and 1.8.0_73, also tried on Windows, Linux and Mac OS X.
import javafx.application.Application;
import javafx.geometry.Side;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class TabDemo extends Application
{
#Override
public void start(final Stage stage)
{
// TabPane with some tabs
final TabPane tabs = new TabPane();
tabs.setStyle("-fx-background-color: red;");
for (int i=0; i<3; ++i)
{
final Rectangle rect = new Rectangle(i*100, 100, 10+i*100, 20+i*80);
rect.setFill(Color.BLUE);
final Pane content = new Pane(rect);
final Tab tab = new Tab("Tab " + (i+1), content);
tab.setClosable(false);
tabs.getTabs().add(tab);
}
tabs.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
tabs.setPrefSize(400, 300);
final Group widgets = new Group(tabs);
widgets.setScaleX(0.5);
widgets.setScaleY(0.5);
final Group scroll_content = new Group(widgets);
final ScrollPane scroll = new ScrollPane(scroll_content);
final Scene scene = new Scene(scroll);
stage.setTitle("Tab Demo");
stage.setScene(scene);
stage.show();
// Unfortunately, the setup of ScrollPane -> Group -> Group -> TabPane
// breaks the rendering of the TabPane.
// While the red background shows the area occupied by TabPane,
// the actual Tabs are missing..
System.out.println("See anything?");
scene.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) ->
{
if (event.getCode() == KeyCode.SPACE)
{ // .. until 'side' or 'tabMinWidth' or .. are twiddled to force a refresh
tabs.setSide(Side.BOTTOM);
tabs.setSide(Side.TOP);
System.out.println("See it now?");
}
});
}
public static void main(String[] args)
{
launch(args);
}
}

View of the application javafx

i was searching in google for hours and i still cant find the right answer, so i have a last chance to come here and ask.
i'm making school year JAVA FX project. I'm using NetBeans.
I have a point that i can see on the application i have. The problem is: I would like to have a big map (background) and I need to be able to move with my view. For example move by 50 to the right (x).
I have Application where I use Stage, Scene, StackPane.
I heard something about Dimensions in Java, but i can't use it in javafx application. Is there something similar, what can I use in my Application?
Thank you very much.
What I think you are asking for is a Scene with a map (represented as an Image) in the background and controls layered on top of the map to allow interaction with the map at certain positions. Your question is a little unclear, so I'm not exactly sure if that is what you are asking.
If so, here is some sample code to implement that.
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
/** Constructs a scene with a pannable Map background. */
public class PannableView extends Application {
private Image backgroundImage;
#Override public void init() {
backgroundImage = new Image("https://www.narniaweb.com/wp-content/uploads/2009/08/NarniaMap.jpg");
}
#Override public void start(Stage stage) {
stage.setTitle("Drag the mouse to pan the map");
// construct the scene contents over a stacked background.
StackPane layout = new StackPane();
layout.getChildren().setAll(
new ImageView(backgroundImage),
createKillButton()
);
// wrap the scene contents in a pannable scroll pane.
ScrollPane scroll = createScrollPane(layout);
// show the scene.
Scene scene = new Scene(scroll);
stage.setScene(scene);
stage.show();
// bind the preferred size of the scroll area to the size of the scene.
scroll.prefWidthProperty().bind(scene.widthProperty());
scroll.prefHeightProperty().bind(scene.widthProperty());
// center the scroll contents.
scroll.setHvalue(scroll.getHmin() + (scroll.getHmax() - scroll.getHmin()) / 2);
scroll.setVvalue(scroll.getVmin() + (scroll.getVmax() - scroll.getVmin()) / 2);
}
/** #return a control to place on the scene. */
private Button createKillButton() {
final Button killButton = new Button("Kill the evil witch");
killButton.setStyle("-fx-base: firebrick;");
killButton.setTranslateX(65);
killButton.setTranslateY(-130);
killButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
killButton.setStyle("-fx-base: forestgreen;");
killButton.setText("Ding-Dong! The Witch is Dead");
}
});
return killButton;
}
/** #return a ScrollPane which scrolls the layout. */
private ScrollPane createScrollPane(Pane layout) {
ScrollPane scroll = new ScrollPane();
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setPannable(true);
scroll.setPrefSize(800, 600);
scroll.setContent(layout);
return scroll;
}
public static void main(String[] args) { launch(args); }
}
For the example use the mouse (or probably touch commands or trackpad scroll gestures - though I haven't a touch screen or trackpad to test it) to drag the map around. Click on the button to "Kill the evil witch".
The solution works by:
Creating an ImageView to hold the background map.
Constructing the scene contents in a StackPane over the stacked background ImageView.
Wrapping the scene in a ScrollPane bound to the scene's size.
Setting properties on the ScrollPane to make it pannable.

Resources