Possible to Drag Shapes Within a GridPane with JavaFX - java

Am I correct in thinking it is not possible to drag Shapes within a GridPane? Here's a link to some code that allows the user to drag shapes around the screen: Drag and Drop Shapes
I want my shapes to have the same behavior as above, but I want them in a GridPane (eventually I'd like their movement to be locked to the X or Y axis to be moved on to adjacent shapes).
I added the below code to the "start" method. It creates circles using the same method as the example code but instead adds them to a grid. Surprisingly, this removed the ability for them to be dragged around.
GridPane grid = new GridPane();
grid.setLayoutX(300);
grid.setLayoutY(100);
int n = 3;
int m = 3;
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
Circle circle = createCircle(100, 50, 30, Color.BLACK);
grid.add(circle, c, r);
}
}
root.getChildren().add(grid);
If you want to test this just add the above code to the "start" method of the example code, just above these lines:
primaryStage.setScene(scene);
primaryStage.show();
My theory is the GridPane, because it locks the circles to certain positions, doesn't allow this dragging behavior.
Any input on how I can achieve movement of the circles along the X and Y axis when dragged?

In general, layout panes such as GridPane manage the placement of their content. Changing the layout coordinates will not affect nodes that are placed in these panes. You may find it better to use a plain Pane and manage the layout yourself for functionality such as this.
If you do want to use a GridPane, transformations (such as translations, etc) are applied after layout coordinates are computed, so you can use a translation (e.g. the one built-in with the translateX and translateY properties) to manage dragging in a layout pane.
So you can do:
circle.setOnMouseDragged((t) -> {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
// No idea why they are doing this. c is just circle
Circle c = (Circle) (t.getSource());
c.setTranslateX(c.getTranslateX() + offsetX);
c.setTranslateY(c.getTranslateY() + offsetY);
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
});

Related

Why does javaFX circle.getCenterX not work in this case?

Right now I'm trying to code a simple abacus with JavaFX. There are balls on horizontal rails arranged in a gridpane, and they move on mouseClick from side to side. I'm trying to add labels to each ball from 10-1, but it seems that by simply adding the circles into a gridpane they don't get a CenterX/Y assigned. Since that's how I'm trying to position my text it doesn't show up at all. The relevant part of my code is:
// Stack Gridpane into root pane to organize circles on rails
GridPane grid = new GridPane();
// loop to instantiate all circles in a 5x10 grid, add to gridpane
for (int row = 0; row < 5; row++) {
for (int count = 10; count > 0; count--) {
Circle circle = new Circle() ;
circle.setRadius(20);
grid.add(circle,count,row);
// generate Labels and position on circle
Text label = new Text(Integer.toString(count));
label.getStyleClass().add("text");
label.setX(circle.getCenterX());
label.setY(circle.getCenterY());
//bind label to circle movement
label.translateXProperty().bind(circle.translateXProperty());
root.getChildren().add(label);
}
}
root.getChildren().add(grid);
When I add a System.out.println(label) I can see that the labels get generated properly, but the coordinates always stay at 0/0. The circles themselves get arranged in a grid the way they are supposed to though. Any help would be greatly appreciated!
There are some problems here that I see.
Firstly, setX() and setY() do not work when the node is inside some kind of Pane subclass that manages layout for you. This is because the layout manager will override this value when it's trying to layout its children.
Secondly, you are adding the label to root node, which is going to make all the Text gather at the same place.
This is what I would do, using StackPane, which allows you to stack multiple nodes at the same place. Furthermore, it automatically centers all its children.
// Stack Gridpane into root pane to organize circles on rails
GridPane grid = new GridPane();
// loop to instantiate all circles in a 5x10 grid, add to gridpane
for (int row = 0; row < 5; row++) {
for (int count = 10; count > 0; count--) {
StackPane sp = new StackPane();
grid.add(sp, count, row);
Circle circle = new Circle();
circle.setRadius(20);
// generate Labels and position on circle
Text label = new Text(Integer.toString(count));
label.getStyleClass().add("text");
label.setFill(Color.RED); // Added so it's visible
// Don't need these
//label.setX(circle.getCenterX());
//label.setY(circle.getCenterY());
//bind label to circle movement
//label.translateXProperty().bind(circle.translateXProperty());
//root.getChildren().add(label);
// Add circle and label to StackPane so it'll handle layout for you
sp.getChildren().addAll(circle, label);
}
}
root.getChildren().add(grid);
From what I see, you do have some translation intended. That would be a little tricky to do, depending on what exactly you need.

Pre-set position of spawned node

I'm trying to make app that spawns new draggable nodes on pretty big pane(which is child of scrollpane), but this node should be spawned in the center of the screen.
Q is: Are there any methods to pre-set X,Y coordinates of these new imageviews?
For example:
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Bounds bounds = scrollPane.getBoundsInLocal();
Bounds screenBounds = scrollPane.localToScreen(bounds);
int mX = (int) screenBounds.getMinX();
int mY = (int) screenBounds.getMinY();
Rectangle2D primScreenBounds = Screen.getPrimary().getVisualBounds();
int x = (int) ((primScreenBounds.getWidth() - mX) /4);
int y = (int) ((primScreenBounds.getHeight() - mY) /4);
/*
System.out.println("X coords:" +x);
System.out.println("Y coords:" +y);
*/
pane.getChildren().addAll(new ImageView(imgvw.getImage()));
//somehow set coordinates of new ImageView
}
});
The way you'd set the position deĀ“pends on the layout you use. However assuming you use Pane, you could use
layoutX and layoutY or
translateX and translateY
ImageView iv = new ImageView(imgvw.getImage());
iv.setLayoutX(x);
iv.setLayoutY(y);
pane.getChildren().add(iv);
Other layouts, e.g. StackPane automatically set layoutX and layoutY. In this case you could set managed to false
iv.setManaged(false);
or use the appropriate parameters for the layout type.
This might depend on the parent the ImageViews are placed in, though in general you should be able to position Nodes with Region#positionInArea() (Region is superclass of Parent). Note that this is a protected method, meaning you might want to create your own parent (e.g. by extending StackPane for example).
That being said, there are plenty of Parent Nodes, each providing their own unique behavior. Try to make sure that the desired behavior cannot be achieved using any of those before creating your own implementation. (And since you dont specify any positioning behavior, its hard to make recommendations)

JavaFX pannable GridPane

For my current project, I need a large even grid with a transparent background that one can zoom and pan across. I initially tried to use a ScrollPane with its setPannable(true), but soon discovered that a scrollpane background will remain opague even when you call setStyle("-fx-background-color: transparent") or setStyle("-fx-background: transparent"). So with that easy option eliminated, I need to make the GridPane in question directly pannable.
I've tried several things already, including trying to bind the mouse position to the GridPane's Translate property(which quite simply didn't work), and I failed to get the more promising alternative to work properly:
GridPane gridView = new GridPane();
int x;
int y;
gridView.setOnDragEntered( (event) -> {
x = event.getX();
y = event.getY();
});
gridView.setOnDragDetected( (event) -> {
gridView.setTranslateX(X - event.getX());
gridView.setTranslateY(Y - event.getY());
});
However, this only makes the map jump up and to the left, rather than to make it slowly pan with the mouse as intended.
Managed to mostly figure it out. The one remaining problem is that the GridPane is rather jittery when it is panned, but it's at an acceptable level for now.
int x = 0;
int y = 0;
gridView.setOnMouseDragged((event) -> {
if(x != 0){ //prevent from panning before values are initialized
gridView.setTranslateX( gridView.getTranslateX() + limit(event.getX() - x, 25));
gridView.setTranslateY( gridView.getTranslateY() + limit(event.getY() - y, 25));
}
x = (int)event.getX();
y = (int)event.getY();
});
Where the Limit(double num, double limit) method limits the input num to the range [-limit, limit]. Reducing the limit decreases the jitter, but it does so at the expense of decreasing responsiveness as well.

Generating an N x N grid

What is the most painless way to create an N x N grid in a JavaFX application?
The only requirements I'm looking for is that the size of the grid will always take up the same amount of space, so more squares = smaller squares. I can set colors for the squares, and I can hover over each square individually and be able to show some for each square.
I won't know 'N' until the program runs and parses through some data to figure out how many total squares I need which is when I calculate the smallest NxN grid I can use.
From what I can tell my options are:
GridPane - Using the column constraints and row constraints to generate size and possibly add properties for hovering?
TableView - A lot more options for being able to give each cell data to show when hovered over but still difficult to just add rows and columns to start with.
Rectangles - Just generate and draw each rectangle while calculating the x and y coordinates for each square. This will make it easy to do the colors and hovering but I can't see how resizing would work but I'm ok with having a specific size for my application. I'll also have to calculate the best size to make each square to fill up the grids area.
I'm not necessarily looking for someone to code a solution, but if someone has dealt with this and knows a good way I'd like to hear about it.
Don't stray away from the original ideas. Why are you looking for "painless" ways when all the methods you've given are all viable? Here's one using your rectangles. The GridMaker.SCREEN_SIZE refers to the size of the screen you must have.
public static Pane makeGrid(int n){
double width = GridMaker.SCREEN_SIZE/n;
Pane p = new Pane();
Rectangle [][] rec = new Rectangle [n][n];
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
rec[i][j] = new Rectangle();
rec[i][j].setX(i * width);
rec[i][j].setY(j * width);
rec[i][j].setWidth(width);
rec[i][j].setHeight(width);
rec[i][j].setFill(null);
rec[i][j].setStroke(Color.BLACK);
p.getChildren().add(rec[i][j]);
}
}
return p;
}
Then simply add the mouse listener to the pane if you wish to make it change color.
p.setOnMouseClicked(new EventHandler <MouseEvent> (){
#Override
public void handle(MouseEvent me){
double posX = me.getX();
double posY = me.getY();
int colX = (int)(posX / width);
int colY = (int) (posY / width);
rec[colX][colY].setFill(Color.RED);
}
});
-- Edit
1)
2) For Hover, what kind of hover effects are you looking for? You can add Hover effects onto each rectangles, if you want me to show you how, I can definitely code it for you.

JUNG2: How to draw a circle?

I need to draw a circle around a vertex in JUNG. The circle is defined by the vertex as center and a given radius r.
Something like this, I guess. This will give you points for circle with given radius. To adjust resolution of points change x+=0.01 to a bigger/smaller value as needed. To move circle centre to an arbitrary point (p,q), just add it to (x,y), that is plot(x+p,y+q);.
double radius = 3;
for (double x = -radius; x <= radius; x += 0.01) {
double y = Math.sqrt(radius * radius - x * x);
plot(x, y);//top half of the circle
plot(x, -y);//bottom half of the circle
}
EDIT: It appears that JUNG is not really an XY-plot but a network/graph framework. So all you need is to layout your points in a circle using one of provided layouts. CircleLayout and KKLayout seem to do the trick, though CircleLayout gives strange results for when there are many nodes. Here's complete sample code:
//Graph holder
Graph<Integer, String> graph = new SparseMultigraph<Integer, String>();
//Create graph with this many nodes and edges
int nodes = 30;
for (int i = 1; i <= nodes; i++) {
graph.addVertex(i);
//connect this vertext to vertex+1 to create an edge between them.
//Last vertex is connected to the first one, hence the i%nodes
graph.addEdge("Edge-" + i, i, (i % nodes) + 1);
}
//This will automatically layout nodes into a circle.
//You can also try CircleLayout class
Layout<Integer, String> layout = new KKLayout<Integer, String>(graph);
layout.setSize(new Dimension(300, 300));
//Thing that draws the graph onto JFrame
BasicVisualizationServer<Integer, String> vv = new BasicVisualizationServer<Integer, String>(layout);
vv.setPreferredSize(new Dimension(350, 350)); // Set graph dimensions
JFrame frame = new JFrame("Circle Graph");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(vv);
frame.pack();
frame.setVisible(true);
I have picked SparseMultiGraph because that's what was in JUNG tutorial. There are other types of graphs, but I am not sure what the difference is.
You could also use a StaticLayout that can take (x,y) vertices, then use my original code to plot the points, but that would not be as elegant for JUNG framework. Depends on what your requirements are, however.

Categories