How do I make a Vbox use the entire space - java

This is javafx8, I have to write that here since i do not have enough rep to use the tag.
Simple example:
public class Test extends Application
{
public static void main(String args[]) throws Exception { launch(args); }
private Canvas canvas;
#Override
public void start(Stage primaryStage)
{
VBox box = new VBox();
Button dummyButton = new Button("some text");
canvas = new Canvas();
canvas.heightProperty().addListener(observable -> draw());
canvas.widthProperty().addListener(observable -> draw());
VBox.setVgrow(canvas, Priority.ALWAYS);
box.getChildren().addAll(dummyButton, canvas);
Scene scene = new Scene(box, 1300, 800);
primaryStage.setScene(scene);
primaryStage.show();
}
private void draw()
{
final double width = canvas.getWidth();
final double height = canvas.getHeight();
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
}
I probably misread the documentation of vbox, at the bottom it says that
For example, if a vbox needs the ListView to be allocated all extra space
and then does this VBox.grow thing, just as I did in my example. But the canvas is never changing his size. It always has a width and height of 0, while I expected the canvas to grow and shrink as I resize the window.
How can I get my canvas ( and I guess with that also my vbox ) to use the entire vertical space available.
Please do not suggest the use of the primary stage, in my real application I am so many layers away from that with many other elements in between. Primary stage is only here to have a runnable program.

Your draw method is a bit messed up.
private void draw() {
final double width = canvas.getWidth();
final double height = canvas.getHeight();
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
If we look at your method, you are doing a few things that can cause problems.
But first, as you noticed, the statement:
VBox.setVgrow(canvas, Priority.ALWAYS);
is causing you trouble. So let's just get rid of it and use your current structure.
If we just change your listeners from canvas to box:
canvas.heightProperty().addListener(observable -> draw());
canvas.widthProperty().addListener(observable -> draw());
to
box.heightProperty().addListener(observable -> draw());
box.widthProperty().addListener(observable -> draw());
We will now dynamically update things as your Vbox size changes. This means that your method draw will not be called when we resize, it wasn't doing that before so that's good!
So now back to the draw method. First of all, you are asking for the canvas width each time, when really we care about getting the canvas to match the box, so let's change that from canvas to box (You'll declare your Vbox as a field now).
private void draw() {
final double width = box.getWidth();
final double height = box.getHeight();
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
But this still won't work very well. That's because we never update the canvas size! So let's add a setWidth and a setHeight in
private void draw() {
final double width = box.getWidth();
final double height = box.getHeight();
canvas.setWidth(width);
canvas.setHeight(height);
System.out.println("w, h: " + width + "; " + height);
final GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.GREENYELLOW);
gc.fillRect(0, 0, width, height);
}
With that, we'll notice that everything seems to be looking pretty good, with one exception. The button is now blocking the top of the screen and will make it so that you can't actually fill that top part. That's because the button was drawn first onto the screen and gets priority (If we switch it to be added at index 1 via a different add method call, we see the button disappears). I can't think of a way to fix that right now, but hopefully that solves part of your problem!

Related

Rectangle size binding to Scene size - JavaFX

I'm trying to write a simple JavaFX application that contains a graphical representation of an NxM matrix (default 100 x 100). I would like to bind the matrix size to the Scene size, so when I resize the app window, the matrix follows it and keeps the aspect ratio. The binding works seamlessly for small matrices (for example 10x10), but when the matrix gets bigger (for example 50x50) and rectangles get smaller so they can fit my screen, the binding process becomes discontinuous. The matrix sometimes even gets bigger than window size (can't see all cells/rectangles) and matrix size seems like it's switching between integer values.
What I did is created a StackPane as a root Node inside the Scene and a GridPane as a child Node to the StackPane. I filled the GridPane with an NxM Rectangle matrix.
The code follows:
public class GridPaneExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
int n = 100, m = 100; // matrix size
double rectw = 5, recth = 5; // size of each rectangle
StackPane root = new StackPane();
Scene scene = new Scene(root, (m + 2) * rectw, (n + 2) * recth, Color.DARKOLIVEGREEN); // creating a scene with
// a frame around matrix
primaryStage.setScene(scene);
primaryStage.sizeToScene();
GridPane gp = new GridPane();
root.getChildren().add(gp);
Rectangle[][] rects = new Rectangle[n][m];
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) {
rects[i][j] = new Rectangle();
rects[i][j].setWidth(rectw);
rects[i][j].setHeight(recth);
rects[i][j].setFill(Color.ANTIQUEWHITE);
rects[i][j].setStrokeType(StrokeType.INSIDE);
rects[i][j].setStrokeWidth(0.2);
rects[i][j].setStroke(Color.GREY);
rects[i][j].widthProperty().bind(gp.widthProperty().divide(m));
rects[i][j].heightProperty().bind(gp.heightProperty().divide(n));
GridPane.setRowIndex(rects[i][j], i);
GridPane.setColumnIndex(rects[i][j], j);
gp.getChildren().add(rects[i][j]);
}
rects[0][0].setFill(Color.RED);
rects[0][m - 1].setFill(Color.RED);
rects[n - 1][0].setFill(Color.RED);
rects[n - 1][m - 1].setFill(Color.RED);
StackPane.setAlignment(gp, Pos.TOP_CENTER);
gp.minHeightProperty().bind(scene.heightProperty().subtract(2 * recth));
gp.minWidthProperty().bind(scene.widthProperty().subtract(2 * rectw));
gp.maxHeightProperty().bind(scene.heightProperty().subtract(2 * recth));
gp.maxWidthProperty().bind(scene.widthProperty().subtract(2 * rectw));
root.setLayoutY(recth);
primaryStage.show();
primaryStage.setMinHeight(primaryStage.getHeight());
primaryStage.setMinWidth(primaryStage.getWidth());
}
}
Here are some screenshots of the program showing different sized matrices.
A 10 x 10 matrix app when started (rectangle size is 20x20):
https://i.imgur.com/I2vMLSD.png
A 10 x 10 matrix app when stretched:
https://i.imgur.com/9rR3e0g.png
A 100 x 100 matrix app when started (rectangle size is 5x5):
https://i.imgur.com/1KCKg6W.png
A 100 x 100 matrix app when stretched a bit, see how the matrix gets out of bounds:
https://i.imgur.com/pG8DxXA.png
So, is there a way to make this binding experience smooth and consistent, maybe by using something else other than Rectangles?
Edit: A weird thing happens. When I remove the rectangle binding lines from code and launch the app with 100x100 matrix, the matrix looks as it should. The program prints out the size of the GridPane and red rectangles. They are 500x500 and 5x5 respectively, as expected. However, when I include rectangle binding (as shown in the code), the matrix gets out of bounds, as in third photo attached, but the program still prints out the same dimensions of GridPane and rectangles.
As commented earlier, this problem is fixable by calling Node#setSnapToPixel(false) on the GridPane.
Explanation
This problem occurs when Nodes become so small that a single pixel becomes too big for accurate display.
JavaFX uses a 'pixel snapping' feature by default to ensure a 'clear/clean' look. This would be mainly vissible with borders getting 'fussy/blurry' when not used.
In the backend JavaFX has defined the methods snapSpace, snapSize and snapPosition, which correspond respectively to Math.round, Math.ceil and Math.round.
This means that if a Node has a width of 2.5 with a 1 pixel border, it should have the size of (1 + 2.5 + 1) = 4.5, wich ceiled wil result in 5.0. If you have 100 elements getting a 0.5 width bonus it will result in 50 pixels overflow!
Setting 'snapToPixel' to false means it wil ignore the mechanism above, and use mixed colors per pixel. If you would look closely this 'blurr' effect is noticable on borders. But it is worth the tradeoff for the perfect layout IMO.
Here is a sample app I wrote a long time ago which does a similar kind of thing. It uses resizable nodes within a GridPane with a listener on the layoutBoundsProperty to pick the size of the resizable nodes. Not sure if it is really what you are interested in (the approach you have in your question, with some fixes or tweaks, is probably fine for what you wish to accomplish), but it might be worth taking a look at for an alternate approach if needed.
Relevant code is for the swatch node in the ColorChooser constructor from the sample code below (sorry it's a bit long, it was written for a different purpose but seems close enough that it might be worth posting in its entirety):
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
/**
* Sample application for using the color chooser
*/
public class ColorChooserSample extends Application {
public void start(final Stage stage) throws Exception {
// initialize the stage.
stage.setTitle("Color Chooser");
stage.initStyle(StageStyle.UTILITY);
// create a new color chooser sized to the stage.
final String[][] smallPalette = {
{"aliceblue", "#f0f8ff"}, {"antiquewhite", "#faebd7"}, {"aqua", "#00ffff"}, {"aquamarine", "#7fffd4"},
{"azure", "#f0ffff"}, {"beige", "#f5f5dc"}, {"bisque", "#ffe4c4"}, {"black", "#000000"},
{"blanchedalmond", "#ffebcd"}, {"blue", "#0000ff"}, {"blueviolet", "#8a2be2"}, {"brown", "#a52a2a"},
{"burlywood", "#deb887"}, {"cadetblue", "#5f9ea0"}, {"chartreuse", "#7fff00"}, {"chocolate", "#d2691e"},
{"coral", "#ff7f50"}, {"cornflowerblue", "#6495ed"}, {"cornsilk", "#fff8dc"}, {"crimson", "#dc143c"},
{"cyan", "#00ffff"}, {"darkblue", "#00008b"}, {"darkcyan", "#008b8b"}, {"darkgoldenrod", "#b8860b"},
};
final ColorChooser colorChooser = new ColorChooser(smallPalette);
// to use the full web palette, just use the default constructor.
// final ColorChooser colorChooser = new ColorChooser();
final Scene scene = new Scene(colorChooser, 600, 500);
// show the stage.
stage.setScene(scene);
stage.show();
// monitor the color chooser's chosen color and respond to it.
colorChooser.chosenColorProperty().addListener((observableValue, oldColor, newColor) ->
System.out.println("Chose: " + colorChooser.getChosenColorName() + " " + colorChooser.getChosenColor())
);
}
public static void main(String[] args) {
launch(args);
}
}
/**
* A Color Chooser Component - allows the user to select a color from a palette.
*/
class ColorChooser extends VBox {
private final double GOLDEN_RATIO = 1.618;
private final double MIN_TILE_SIZE = 5;
private final double nColumns;
private final double nRows;
/**
* The color the user has selected or the default initial color (the first color in the palette)
*/
private final ReadOnlyObjectWrapper<Color> chosenColor = new ReadOnlyObjectWrapper<Color>();
public Color getChosenColor() {
return chosenColor.get();
}
public ReadOnlyObjectProperty<Color> chosenColorProperty() {
return chosenColor.getReadOnlyProperty();
}
/**
* Friendly name for the chosen color
*/
private final ReadOnlyObjectWrapper<String> chosenColorName = new ReadOnlyObjectWrapper<String>();
public String getChosenColorName() {
return chosenColorName.get();
}
/**
* Preferred size for a web palette tile
*/
private DoubleProperty prefTileSize = new SimpleDoubleProperty(MIN_TILE_SIZE);
/**
* A palette of colors from http://docs.oracle.com/javafx/2.0/api/javafx/scene/doc-files/cssref.html#typecolor
*/
private static final String[][] webPalette = {
{"aliceblue", "#f0f8ff"}, {"antiquewhite", "#faebd7"}, {"aqua", "#00ffff"}, {"aquamarine", "#7fffd4"},
{"azure", "#f0ffff"}, {"beige", "#f5f5dc"}, {"bisque", "#ffe4c4"}, {"black", "#000000"},
{"blanchedalmond", "#ffebcd"}, {"blue", "#0000ff"}, {"blueviolet", "#8a2be2"}, {"brown", "#a52a2a"},
{"burlywood", "#deb887"}, {"cadetblue", "#5f9ea0"}, {"chartreuse", "#7fff00"}, {"chocolate", "#d2691e"},
{"coral", "#ff7f50"}, {"cornflowerblue", "#6495ed"}, {"cornsilk", "#fff8dc"}, {"crimson", "#dc143c"},
{"cyan", "#00ffff"}, {"darkblue", "#00008b"}, {"darkcyan", "#008b8b"}, {"darkgoldenrod", "#b8860b"},
{"darkgray", "#a9a9a9"}, {"darkgreen", "#006400"}, {"darkgrey", "#a9a9a9"}, {"darkkhaki", "#bdb76b"},
{"darkmagenta", "#8b008b"}, {"darkolivegreen", "#556b2f"}, {"darkorange", "#ff8c00"}, {"darkorchid", "#9932cc"},
{"darkred", "#8b0000"}, {"darksalmon", "#e9967a"}, {"darkseagreen", "#8fbc8f"}, {"darkslateblue", "#483d8b"},
{"darkslategray", "#2f4f4f"}, {"darkslategrey", "#2f4f4f"}, {"darkturquoise", "#00ced1"}, {"darkviolet", "#9400d3"},
{"deeppink", "#ff1493"}, {"deepskyblue", "#00bfff"}, {"dimgray", "#696969"}, {"dimgrey", "#696969"},
{"dodgerblue", "#1e90ff"}, {"firebrick", "#b22222"}, {"floralwhite", "#fffaf0"}, {"forestgreen", "#228b22"},
{"fuchsia", "#ff00ff"}, {"gainsboro", "#dcdcdc"}, {"ghostwhite", "#f8f8ff"}, {"gold", "#ffd700"},
{"goldenrod", "#daa520"}, {"gray", "#808080"}, {"green", "#008000"}, {"greenyellow", "#adff2f"},
{"grey", "#808080"}, {"honeydew", "#f0fff0"}, {"hotpink", "#ff69b4"}, {"indianred", "#cd5c5c"},
{"indigo", "#4b0082"}, {"ivory", "#fffff0"}, {"khaki", "#f0e68c"}, {"lavender", "#e6e6fa"},
{"lavenderblush", "#fff0f5"}, {"lawngreen", "#7cfc00"}, {"lemonchiffon", "#fffacd"}, {"lightblue", "#add8e6"},
{"lightcoral", "#f08080"}, {"lightcyan", "#e0ffff"}, {"lightgoldenrodyellow", "#fafad2"}, {"lightgray", "#d3d3d3"},
{"lightgreen", "#90ee90"}, {"lightgrey", "#d3d3d3"}, {"lightpink", "#ffb6c1"}, {"lightsalmon", "#ffa07a"},
{"lightseagreen", "#20b2aa"}, {"lightskyblue", "#87cefa"}, {"lightslategray", "#778899"}, {"lightslategrey", "#778899"},
{"lightsteelblue", "#b0c4de"}, {"lightyellow", "#ffffe0"}, {"lime", "#00ff00"}, {"limegreen", "#32cd32"},
{"linen", "#faf0e6"}, {"magenta", "#ff00ff"}, {"maroon", "#800000"}, {"mediumaquamarine", "#66cdaa"},
{"mediumblue", "#0000cd"}, {"mediumorchid", "#ba55d3"}, {"mediumpurple", "#9370db"}, {"mediumseagreen", "#3cb371"},
{"mediumslateblue", "#7b68ee"}, {"mediumspringgreen", "#00fa9a"}, {"mediumturquoise", "#48d1cc"}, {"mediumvioletred", "#c71585"},
{"midnightblue", "#191970"}, {"mintcream", "#f5fffa"}, {"mistyrose", "#ffe4e1"}, {"moccasin", "#ffe4b5"},
{"navajowhite", "#ffdead"}, {"navy", "#000080"}, {"oldlace", "#fdf5e6"}, {"olive", "#808000"},
{"olivedrab", "#6b8e23"}, {"orange", "#ffa500"}, {"orangered", "#ff4500"}, {"orchid", "#da70d6"},
{"palegoldenrod", "#eee8aa"}, {"palegreen", "#98fb98"}, {"paleturquoise", "#afeeee"}, {"palevioletred", "#db7093"},
{"papayawhip", "#ffefd5"}, {"peachpuff", "#ffdab9"}, {"peru", "#cd853f"}, {"pink", "#ffc0cb"},
{"plum", "#dda0dd"}, {"powderblue", "#b0e0e6"}, {"purple", "#800080"}, {"red", "#ff0000"},
{"rosybrown", "#bc8f8f"}, {"royalblue", "#4169e1"}, {"saddlebrown", "#8b4513"}, {"salmon", "#fa8072"},
{"sandybrown", "#f4a460"}, {"seagreen", "#2e8b57"}, {"seashell", "#fff5ee"}, {"sienna", "#a0522d"},
{"silver", "#c0c0c0"}, {"skyblue", "#87ceeb"}, {"slateblue", "#6a5acd"}, {"slategray", "#708090"},
{"slategrey", "#708090"}, {"snow", "#fffafa"}, {"springgreen", "#00ff7f"}, {"steelblue", "#4682b4"},
{"tan", "#d2b48c"}, {"teal", "#008080"}, {"thistle", "#d8bfd8"}, {"tomato", "#ff6347"},
{"turquoise", "#40e0d0"}, {"violet", "#ee82ee"}, {"wheat", "#f5deb3"}, {"white", "#ffffff"},
{"whitesmoke", "#f5f5f5"}, {"yellow", "#ffff00"}, {"yellowgreen", "#9acd32"}
};
public ColorChooser() {
this(webPalette);
}
public ColorChooser(String[][] colors) {
super();
// create a pane for showing info on the chosen color.
final HBox colorInfo = new HBox();
final Label selectedColorName = new Label();
HBox.setMargin(selectedColorName, new Insets(2, 0, 2, 10));
colorInfo.getChildren().addAll(selectedColorName);
chosenColorName.addListener((observableValue, oldName, newName) -> {
if (newName != null) {
colorInfo.setStyle("-fx-background-color: " + newName + ";");
selectedColorName.setText(newName);
chosenColor.set(Color.web(newName));
}
});
// create a color swatch.
final GridPane swatch = new GridPane();
swatch.setSnapToPixel(false);
// calculate the number of columns and rows based on the number of colors and a golden ratio for layout.
nColumns = Math.floor(Math.sqrt(colors.length) * 2 / GOLDEN_RATIO);
nRows = Math.ceil(colors.length / nColumns);
// create a bunch of button controls for color selection.
int i = 0;
for (String[] namedColor : colors) {
final String colorName = namedColor[0];
final String colorHex = namedColor[1];
// create a button for choosing a color.
final Button colorChoice = new Button();
colorChoice.setUserData(colorName);
// position the button in the grid.
GridPane.setRowIndex(colorChoice, i / (int) nColumns);
GridPane.setColumnIndex(colorChoice, i % (int) nColumns);
colorChoice.setMinSize(MIN_TILE_SIZE, MIN_TILE_SIZE);
colorChoice.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
// add a mouseover tooltip to display more info on the colour being examined.
// todo it would be nice to be able to have the tooltip appear immediately on mouseover, but there is no easy way to do this, (file jira feature request?)
final Tooltip tooltip = new Tooltip(colorName);
tooltip.setStyle("-fx-font-size: 14");
tooltip.setContentDisplay(ContentDisplay.BOTTOM);
final Rectangle graphic = new Rectangle(30, 30, Color.web(colorHex));
graphic.widthProperty().bind(prefTileSize.multiply(1.5));
graphic.heightProperty().bind(prefTileSize.multiply(1.5));
tooltip.setGraphic(graphic);
colorChoice.setTooltip(tooltip);
// color the button appropriately and change it's hover functionality (doing some of this in a css sheet would be better).
final String backgroundStyle = "-fx-background-color: " + colorHex + "; -fx-background-insets: 0; -fx-background-radius: 0;";
colorChoice.setStyle(backgroundStyle);
colorChoice.setOnMouseEntered(mouseEvent -> {
final String borderStyle = "-fx-border-color: ladder(" + colorHex + ", whitesmoke 49%, darkslategrey 50%); -fx-border-width: 2;";
colorChoice.setStyle(backgroundStyle + borderStyle);
});
colorChoice.setOnMouseExited(mouseEvent -> {
final String borderStyle = "-fx-border-width: 0; -fx-border-insets: 2;";
colorChoice.setStyle(backgroundStyle + borderStyle);
});
// choose the color when the button is clicked.
colorChoice.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent actionEvent) {
chosenColorName.set((String) colorChoice.getUserData());
}
});
// add the color choice to the swatch selection.
swatch.getChildren().add(colorChoice);
i++;
}
// select the first color in the chooser.
((Button) swatch.getChildren().get(0)).fire();
// layout the color picker.
getChildren().addAll(swatch, colorInfo);
VBox.setVgrow(swatch, Priority.ALWAYS);
setStyle("-fx-background-color: black; -fx-font-size: 16;");
swatch.layoutBoundsProperty().addListener((observableValue, oldBounds, newBounds) -> {
prefTileSize.set(Math.max(MIN_TILE_SIZE, Math.min(newBounds.getWidth() / nColumns, newBounds.getHeight() / nRows)));
for (Node child : swatch.getChildrenUnmodifiable()) {
Control tile = (Control) child;
final double margin = prefTileSize.get() / 10;
tile.setPrefSize(prefTileSize.get() - 2 * margin, prefTileSize.get() - 2 * margin);
GridPane.setMargin(child, new Insets(margin));
}
});
}
}

Moving a button to specified coordinates in JavaFX with a Path Transition using LineTo, MoveTo

I'm making an animation that involves moving buttons representing data travelling in a network. So far, I've been able to move the buttons to different locations at different stages of the animation by updating their LayoutX and LayoutY fields, and by using sequential transitions.
But now I'm trying to get the button, named blue, to move in a diagonal line from its position at a stage to "router 3," which is up and to the right of the button. The exact Layout coordinates of the destination are 426(x) and 364(y), and the Layout coordinates of the starting position are 309(x) and 585(y). I've been trying to use moveTo with LineTo to get the button to the aforementioned coordinates, but it's proving difficult for two reasons:
First, I use a transition to get to the starting coordinates, so the actual coordinates of the button by the time it reaches the starting position are LayoutX: 14, LayoutY: 445, TranslateX: 295, TranslateY: 140.
I try and rectify this with the code:
blue.setLayoutX(blue.getLayoutX() + blue.getTranslateX());
blue.setLayoutY(blue.getLayoutY() + blue.getTranslateY());
blue.setTranslateX(0);
blue.setTranslateY(0);
And then define the path for blue to follow as:
Path path = new Path();
MoveTo start = new MoveTo();
start.setX(blue.getTranslateX());
start.setY(blue.getTranslateY());
path.getElements().add(start);
path.getElements().add(new LineTo(125.0f, -220.0f));
PathTransition pathTransition = new PathTransition();
pathTransition.setDuration(Duration.millis(1000));
pathTransition.setPath(path);
pathTransition.setNode(blue);
pathTransition.setCycleCount((int) 1f);
pathTransition.setAutoReverse(false);
pathTransition.play();
But this seems like a cumbersome workaround. For instance, the next stage of this program has the buttons traversing a network of multiple "routers" according to Dijkstra's algorithm, and I'd like to be able to just have a line path defined that goes straight to the next router, without having to fiddle with Translation and Layout coordinates at every stage. Alternatively, Swing, for instance, can repaint a circle while its coordinates are updated pixel by pixel, so as to make a diagonal animation and to stop after reaching a certain position. Is this possible using JavaFX?
Secondly, Even when this animation as it is implemented begins, the button seems to "jump back" slightly, as though the start of the path to be traveled is a couple pixels down and to the left of where the button actually is, even though as far as I can tell I've specified the start of the path to be exactly where the button is before the animation begins. Is there a particular reason for this?
Thank you for any help related to my question; It's my first time using StackOverflow. I was pretty sure I'd scoured for an answer to this kind of question quite thoroughly but I'm sorry if this is a duplicate.
That's a known problem, you can read about it in the Create a path transition with absolute coordinates for a StackPane object thread.
Unfortunately you have to work around that problem, there is no solution inside JavaFX. Fortunately the solution is easy, just use a modified version of MoveTo and LineTo.
Here's a modified example code for you. Just click on a position in your scene and the node will move there without "jumping".
public class PathTransitionExample extends Application {
PathTransition transition;
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
// create movable object
Rectangle rect = new Rectangle(50, 50);
rect.setStroke(Color.BLUE);
rect.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.3));
rect.relocate(100, 80);
root.getChildren().add(rect);
Label label = new Label("Click on scene to set destination");
label.relocate(0, 0);
root.getChildren().add(label);
// init transition
transition = new PathTransition();
transition.setNode(rect);
transition.setDuration(Duration.seconds(2));
Scene scene = new Scene(root, 1024, 768);
scene.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<Event>() {
#Override
public void handle(Event event) {
transition.stop();
setPositionFixed( rect);
double toX = ((MouseEvent) event).getX();
double toY = ((MouseEvent) event).getY();
Path path = new Path();
path.getElements().add(new MoveToAbs(rect));
path.getElements().add(new LineToAbs(rect, toX, toY));
transition.setPath(path);
transition.play();
}
// change layout to current position, reset translate
private void setPositionFixed( Node node) {
double x = rect.getLayoutX() + rect.getTranslateX();
double y = rect.getLayoutY() + rect.getTranslateY();
rect.relocate(x, y);
rect.setTranslateX(0);
rect.setTranslateY(0);
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
public static class MoveToAbs extends MoveTo {
public MoveToAbs(Node node) {
super(node.getLayoutBounds().getWidth() / 2, node.getLayoutBounds().getHeight() / 2);
}
public MoveToAbs(Node node, double x, double y) {
super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
public static class LineToAbs extends LineTo {
public LineToAbs(Node node, double x, double y) {
super(x - node.getLayoutX() + node.getLayoutBounds().getWidth() / 2, y - node.getLayoutY() + node.getLayoutBounds().getHeight() / 2);
}
}
public static void main(String[] args) {
launch(args);
}
}

How do you embed a resizable JPanel inside of a JTextPane?

There are many questions of the converse, inserting a JTextPane into a JPanel. This is not my question. I need to be able to insert a custom JPanel (with drag and drop, drag, and mouse click listeners) into a JTextPane, which is then put into a JScrollPane, and finally put into a JFrame for displaying. The reason is because I need to have an image with support for resizing by dragging it within a chat client, which is itself primarily text.
Conveniently enough, there is a relevant method in JTextPane: insertComponent(Component c), but whenever I use it, my components end up being squished to exactly one line of text worth of space (even though they report having a larger size). This is perfect for plain buttons, but if I need anything larger, I'm out of luck. I can insert images by themselves just fine, with ImageIcons, but images wrapped inside a JPanel don't work at all (plus I can't add any listeners to ImageIcons, since they're not GUI elements; overriding one isn't an option).
Whenever a user drags an image into the chat client, this bit of code inserts the custom JPanel:
private void sendImage(BufferedImage im, int cl) throws IOException {
if(output == null) return;
//Send the image itself over to your friend
byte[] toSend = toBytes(im, cl);
sendString(nickname.hashCode() + "image"); //Header for image
output.writeInt(toSend.length); //Tells how many bytes to read.
output.write(toSend);
//Let the user know that the image was sent
float linmb = (float)(toSend.length / 1048576.0); //Size of file sent
addText("\n" + nickname + " sent an image! (" + linmb + " MB)\n", Color.RED.darker());
//Show the image itself
DraggerPanel d = new DraggerPanel(im, true);
text.insertComponent(d);
d.repaint();
//Spacer
addText("\n");
}
This is the source for DraggerPanel, the custom JPanel that holds an image:
public class DraggerPanel extends JPanel {
private BufferedImage image; //The image we're drawing
private Point startingPoint = null; //Starting point for resizing
private boolean first = true; //Is this the first drag?
private boolean lockedDrag; //If true, then lock x and y to be proportionally dragged.
public DraggerPanel(BufferedImage image, boolean lockedDrag) {
super();
this.image = image;
this.lockedDrag = lockedDrag;
//The listener for dragging events.
addMouseMotionListener(new MouseMotionListener() {
private int inWidth = 0, inHeight = 0; //Initial height and width values
private double ratio = 0; //Ratio of height to width for locked drag.
public void mouseDragged(MouseEvent m) {
if (first) { //If we're first, record initial position.
startingPoint = m.getPoint();
first = false;
inWidth = getWidth();
inHeight = getHeight();
ratio = (double)inHeight / inWidth;
} else { //Otherwise, change the size of the window.
if (!lockedDrag) {
int w = (int)startingPoint.getX() - m.getX();
int h = (int)startingPoint.getY() - m.getY();
setSize(Math.abs(inWidth - w), Math.abs(inHeight - h));
} else {
int w = (int)startingPoint.getX() - m.getX();
int h = (int)((double)ratio * w);
setSize(Math.abs(inWidth - w), Math.abs(inHeight - h));
}
}
repaint();
}
public void mouseMoved(MouseEvent m){
}
});
//Lets us know when you're not dragging anymore.
addMouseListener(new MouseAdapter(){public void mouseReleased(MouseEvent m){first = true;}});
//Set appropriate size.
if(image != null) setSize(image.getWidth(), image.getHeight());
else setSize(200,200);
//We're live, baby.
setVisible(true);
}
public void paint(Graphics g) {
if (image == null) super.paint(g);
else g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
}
}
Update 1: I followed #camickr 's advice, and updated the DraggerPanel to use setPreferredSize instead of setSize, as well as overrode paintComponent() instead of paint(). Now, the image has the proper height, but is stretched to the width of the JTextPane (which seems like what it was doing before). Furthermore, resizing doesn't seem to matter- the image doesn't change its size at all. Mouse events are definitely going through, but not affecting the size. It seems as though the original problem isn't fully resolved, since the JPanel's size isn't what I need it to be, and the solution to that will also lead to a solution to the resizing issue.
Update 2: I did it! I finally did it. To the future time travelers who have this issue, I basically yelled at the JTextPane by not only using setSize() in my overridden JPanel, but also setPreferredSize() and setMaximumSize(). The preferred one works well with height, and the maximum sets the width (God knows why). Thanks for your tips, #camickr!
my components end up being squished to exactly one line of text worth of space (even though they report having a larger size).
I would guess the size is not important.
I would think you need to override the getPreferredSize() method of your DraggerPanel to return the preferred size of the panel so the text pane can display the panel.
Also, custom painting is done by overriding the paintComponent(...) method NOT the paint() method.

pushMatrix to center of window

I'm looking to make a graphic in Processing that's centered in the middle of the window. I want to be able to change the size of the window and have the graphic remain centred no matter what, so I intend to do this through centering the matrix itself.
How would I go about doing this? Normally I would translate the matrix to the center of the window based on the size of the window itself, but if I'm changing the size then it won't work.
Suggestions?
here, I got this old code that kind of do this...
import processing.opengl.*;
int newCanvasWidth = MIN_WINDOW_WIDTH; // made global to use in draw
int newCanvasHeight = MIN_WINDOW_HEIGHT;
java.awt.Insets insets; //"An Insets object is a representation of the borders of a container"
//from http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/Insets.html
void setup()
{
size(200, 200); // always first line
frame.pack(); //frame.pack() no need for setResizable... plus insets
insets = frame.getInsets();
frame.setResizable(true);
/// for debuging, system depende`nt, at least screen is...
print("MIN_WINDOW_WIDTH = " + MIN_WINDOW_WIDTH);
print(" MIN_WINDOW_HEIGHT = " + MIN_WINDOW_HEIGHT);
print(" screenWidth = " + displayWidth);
println(" screenHeight = " + displayHeight);
}
void draw()
{
background(255);
ellipse(width/2, height/2, width/2, height/2);
}

Why jframe hides taskbar when maximized?

I'm using setUndecorated(true); and getRootPane().setWindowDecorationStyle(JRootPane.FRAME); in my jFrame. This works great but now when I maximized my frame it spreads all over the window even taskbar is not visible. What can I do to make frame not to hide taskbar?
Also when I maximize minimize my frame multiple times the cursor is changed to this <-> which is generally used change size of frame when cursor is on the border of frame. Is there anything I can do for this?
A small code then can reproduce the thing:
import javax.swing.JFrame;
import javax.swing.JRootPane;
public class Demo extends JFrame {
public Demo() {
setSize(250,125);
setUndecorated(true);
getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
setVisible(true);
}
public static void main(String[] args) {
new Demo();
}
}
This is a known bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4737788
Quote from this link:
A workaround is to subclass JFrame and
override the setExtendedState method,
catching any maximize events before
they happen and setting the maximum
bounds of the frame appropriately
before calling the superclass's
setExtendedState method.
import java.awt.*;
import javax.swing.*;
public class PFrame extends JFrame
{
private Rectangle maxBounds;
public PFrame()
{
super();
maxBounds = null;
}
//Full implementation has other JFrame constructors
public Rectangle getMaximizedBounds()
{
return(maxBounds);
}
public synchronized void setMaximizedBounds(Rectangle maxBounds)
{
this.maxBounds = maxBounds;
super.setMaximizedBounds(maxBounds);
}
public synchronized void setExtendedState(int state)
{
if (maxBounds == null &&
(state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH)
{
Insets screenInsets = getToolkit().getScreenInsets(getGraphicsConfiguration());
Rectangle screenSize = getGraphicsConfiguration().getBounds();
Rectangle maxBounds = new Rectangle(screenInsets.left + screenSize.x,
screenInsets.top + screenSize.y,
screenSize.x + screenSize.width - screenInsets.right - screenInsets.left,
screenSize.y + screenSize.height - screenInsets.bottom - screenInsets.top);
super.setMaximizedBounds(maxBounds);
}
super.setExtendedState(state);
}
}
Fortega answer worked however, some part is not needed (or no longer needed with Java 8):
The Rectangle does not need to be saved.
The code does not take into account dual screen configuration. In particular, the GraphicsConfiguration will change if the window change screen.
As far as I tested, the only required override is setExtendedState.
When factoring dual screen configuration, at least on Windows, the below code does not work as intended:
Rectangle maxBounds = new Rectangle(screenInsets.left + screenSize.x,
screenInsets.top + screenSize.y,
screenSize.x + screenSize.width - screenInsets.right - screenInsets.left,
screenSize.y + screenSize.height - screenInsets.bottom - screenInsets.top);
On the following dual screen set up:
Left screen 1920x1080 (not primary), position: -1920, 0
Right screen 1920x1080 (primary), position: 0, 0
The maxBounds will contains negative x (-1920) but the setMaximizedBounds is somehow expecting a coordinate in the screen space (where (x,y) starts at (0,0)) , not the virtual screen:
It will set to setMaximizedBounds(x=-1920,y=0,width=1920,height=1050)
Windows will see the window on the left screen (because I have one taskbar per screen showing only window on that screen) however the window won't be shown on the screen because it is off bounds.
If the resolution of the screen, or worse, its scale factor (with a laptop, Windows 10 will apply a scale factor, ex: 25%, making the screen "not so" 1920x1080), then the above code does not adapt. For example, if my configuration have 3 screens with the right most being the primary, the window will badly display on the left and middle screen. I don't think I fixed this in the below code.
The following code work on Windows, with dual screen:
#Override
public synchronized void setExtendedState(final int state) {
if ((state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) {
final GraphicsConfiguration cfg = getGraphicsConfiguration();
final Insets screenInsets = getToolkit().getScreenInsets(cfg);
final Rectangle screenBounds = cfg.getBounds();
final int x = screenInsets.left + screenBounds.x * 0;
final int y = screenInsets.top + screenBounds.y * 0;
final int w = screenBounds.width - screenInsets.right - screenInsets.left;
final int h = screenBounds.height - screenInsets.bottom - screenInsets.top;
final Rectangle maximizedBounds = new Rectangle(x, y, w, h);
System.out.println("cfg (" + cfg + ") screen.{bounds: " + screenBounds + ", insets: " + screenInsets + ", maxBounds: " + maximizedBounds);
super.setMaximizedBounds(maximizedBounds);
}
super.setExtendedState(state);
}
On a simple JFrame:
Maximizing on the left screen ("screen=0") will print cfg (D3DGraphicsConfig[dev=D3DGraphicsDevice[screen=0],pixfmt=0]) screen.{bounds: java.awt.Rectangle[x=-1920,y=0,width=1920,height=1080], insets: java.awt.Insets[top=0,left=0,bottom=30,right=0], maxBounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1050]
Maximizing on the right screen ("screen=1") will print cfg (D3DGraphicsConfig[dev=D3DGraphicsDevice[screen=1],pixfmt=0]) screen.{bounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1080], insets: java.awt.Insets[top=0,left=0,bottom=30,right=0], maxBounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1050]
Maybe you can set the maximum size of the jFrame and restrict it according to the screen size.
EDIT
Also check out setExtendedState
Starting from Fortega answer, you can make it work even with 125% screen sizi adding
Rectangle screenSize = getGraphicsConfiguration().getBounds();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
screenSize.setSize(new Dimension(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()));
......

Categories