I am trying to create a grid of rectangles that will be contained inside a pane using JavaFX. I want these rectangles to be automatically resized to occupy the space available based on the size of the Pane, with a small gap between the rectangles.
The code I am using right now is this, the createVisual function is called to generate the group containing the pane :
public class Visualizer extends NodePart {
private VBox m_vbox;
private AnchorPane m_pane;
public static class ResizableRectanble extends javafx.scene.shape.Rectangle {
public ResizableRectangle(double w, double h) {
super(w,h);
}
#Override
public boolean isResizable() {
return true;
}
#Override
public void resize(double width, double height) {
setWidth(width);
setHeight(height);
}
}
#Override
protected Group createVisual() {
final Group group = new Group() {
#Override
public boolean isResizable() {
return true;
}
#Override
public void resize(double w, double h) {
m_pane.setPrefSize(w,h);
}
};
m_pane = new AnchorPane();
m_pane.setStyle("-fx-background-color : lightcyan; -fx-border-color: silver; -fx-border-width: 3;");
m_pane.setPrefSize(100, 100);
m_pane.setMouseTransparent(false);
m_vbox = new VBox(3.0);
m_vbox.setFillWidth(true);
m_vbox.setMouseTransparent(false);
for(int i=0; i<16; i++) {
HBox hbox = new HBox(3.0);
hbox.setAlignment(Pos.CENTER);
for(int j=0; j<4; j++) {
Rectangle rect = new ResizableRectangle(50.0,50.0);
rect.setStyle("-fx-fill: lime; -fx-border-color: red; -fx-border-width: 3;");
hbox.setHgrow(rect, Priority.ALWAYS);
hbox.getChildren().add(rect);
hbox.setFillHeight(true);
}
}
m_vbox.setVGrow(hbox, Priority.ALWAYS);
m_pane.setTopAnchor(m_vbox, 5.0);
m_pane.setBottomAnchor(m_vbox, 5.0);
m_pane.getChildren().add(m_vbox);
group.getChildren().add(m_pane);
return group;
}
}
This does not really work as the width of the rectangle is not changed from the initial value when the group size change. The height of the rectangles is also very small and there is a lot more than 3.0 pixel between them.
I have also tried to set up left and right anchor on m_vbox, but it does not seem to work as the rectangles get resized to less than half the size of the pane with that.
I have tried using a GridPane and constraints on the columns and row, but it does not work either as there is overlap. I would prefer to use the VBox and HBox combination if possible.
This function is called by Eclipse GEF (Graphical Editing Framework) to create a node in a graph. I am using GEF version 5 in Eclipse Neon.
I am a beginner with JavaFX, any help would be much appreciated !
Edit, the code I have tried using GridPane :
public class Visualizer extends NodePart {
private GridPane m_gridPane;
public static class ResizableRectanble extends javafx.scene.shape.Rectangle {
public ResizableRectangle(double w, double h) {
super(w,h);
}
#Override
public boolean isResizable() {
return true;
}
#Override
public void resize(double width, double height) {
setWidth(width);
setHeight(height);
}
}
#Override
protected Group createVisual() {
final Group group = new Group() {
#Override
public boolean isResizable() {
return true;
}
#Override
public void resize(double w, double h) {
m_gridPane.setPrefSize(w,h);
}
};
m_gridPane = new GridPane();
m_gridPane.setStyle("-fx-background-color : lightcyan; -fx-border-color: silver; -fx-border-width: 3;");
m_gridPane.setPrefSize(100, 100);
m_gridPane.setMouseTransparent(false);
for(int i=0; i<16; i++) {
for(int j=0; j<4; j++) {
if(i == 0) {
ColumnConstraints cc = new ColumnConstraints();
cc.setFillWidth(true);
cc.setHgrow(Priority.ALWAYS);
m_gridPane.getColumnConstraints().add(cc);
}
Rectangle rect = new ResizableRectangle(50.0,50.0);
rect.setStyle("-fx-fill: lime; -fx-border-color: red; -fx-border-width: 3;");
m_gridPane.add(rect, j, i);
}
RowConstraints rc = new RowConstraints();
rc.setFillHeight(true);
rc.setVgrow(Priority.ALWAYS);
m_gridPane.getRowConstraints().add(rc);
}
group.getChildren().add(m_gridPane);
return group;
}
}
Edit 2 : The code using GridPane works and the nodes are not overlapping. However, the borders of the rectangles are not shown, which led me to believe that there was overlapping.
Related
I create Shape and I need to position on a Canvas
Class Square to draw a square and insert into a canvas position
public class Square{
//calculate the position of the rand column to
//draw and insert in the position of the canvas
public void drawSquare(int posX, int posY, GraphicsContext gc) {
//Square Shadow
//gc.rect(posX, posY, w, h);
gc.rect(posX + 1, posY + 53, 50, 50);
gc.fill();
gc.beginPath();
//Square
gc.beginPath();
gc.setFill(Color.WHITE);
gc.setStroke(Color.BLACK);
gc.setLineWidth(2);
//gc.rect(posX, posY, w, h);
gc.rect(posX + 1, posY + 53, 48, 48);
gc.fill();
gc.stroke();
}
}
New Canvas instance with height = 450 and width = 600
Canvas canvas = new Canvas();
canvas.setHeight(450);
canvas.setWidth(600);
and GraphicsContext to draw square
GraphicsContext gc = canvas.getGraphicsContext2D();
with this loop, draw 4 rows and 6 columns with square in canvas,
and my doubt is how to calculate the position of the line and column to draw square and insert in the position of the canvas when I call pieces.drawSquare(i, j, gc);, and method drawSquare creates the shape but the doubt is how to position them if it is more than one shape
for (int i = 0; i < 4; i++) { //4 rows
for (int j = 0; j < 6; i++) { //6 columns
Piece pieces = new Piece();
pieces.drawSquare(i, j, gc);
}
this image is the example,
and the objective is to fill in 4 rows and 6 columns
I have already thought about dividing the size and width of Canvas with the size and width of the shape but it is not working, maybe can have another solution
I think this can get you jump starting
public class Main extends Application {
private SimpleIntegerProperty rowProperty = new SimpleIntegerProperty(4); //default
private SimpleIntegerProperty columnProperty = new SimpleIntegerProperty(6);//default
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
root.setPadding(new Insets(5));
HBox top;
TextField rowField = new TextField();
rowField.setMaxWidth(60);
rowField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
try{ rowProperty.setValue(Integer.valueOf(newValue));}catch(NumberFormatException e){}
}
});
TextField colField = new TextField();
colField.setMaxWidth(60);
colField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
try{ columnProperty.setValue(Integer.valueOf(newValue));}catch(NumberFormatException e){}
}
});
top = new HBox(10,new Label("ROW FIELD"),rowField, new Label("COLUMN FIELD"),colField);
top.setAlignment(Pos.CENTER);
root.setStyle("-fx-background-color: white;");
root.setTop(top);
////////////////////////////////////////////////////////////////////////////////////
Canvas canvas = new Canvas(500,400);
canvas.getGraphicsContext2D().setFill(Color.BLACK);
canvas.getGraphicsContext2D().setStroke(Color.GOLD);
ChangeListener<Number> chan = new ChangeListener<Number>() {
int space = 2;
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
///i will draw here
canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
int rectW = (int) canvas.getWidth();
rectW = rectW/columnProperty.intValue();
int rectH = (int) canvas.getHeight();
rectH = rectH/rowProperty.intValue();
System.out.println(rectW);
System.out.println(rectH);
for(int k = 0; k < canvas.getHeight()/rectH; k++){
for(int i =0; i< canvas.getWidth()/rectW; i++){
canvas.getGraphicsContext2D().fillRect((i*rectW) + (i*space),
(k*rectH) + (k*space),
rectW, rectH);
}
}
}
};
rowProperty.addListener(chan);
columnProperty.addListener(chan);
//////////////////////////////////////////////////////////////////////////////////////////
root.setCenter(canvas);
Label l = new Label("ENTER NUMBERS TO FIELDS TO SEE IT");
l.setStyle("-fx-background-color: blueviolet; -fx-text-fill: white;");
l.setPrefWidth(Double.MAX_VALUE);
l.setAlignment(Pos.CENTER);
root.setBottom(l);
Scene scene = new Scene(root,500,500);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) { e.printStackTrace(); }
}
public static void main(String[] args) {
launch(args);
}
}
I want to make a ScrollPane with a custom Pane inside, that has two Children. One that holds my objects and one just for the background. I want to make it so if I zoom out, and the content is smaller than the viewport, then the size of the content would expand, filling in the new place in the viewport. And if I zoom back then it would remain the same, and I have now a larger content in area. The new width of the content would be: originalWidth + viewportWidth - scaledWidth.
I have made the grid, and the zooming works, but I can't make it so that it resizes the content. I have tried to set the content size when zooming to the current viewport size, but it does not work.
Question:
What am I doing wrong?
The layout is defined in fxml. Another than ScrollPane content set to fill height and width nothing out of ordinary there.
CustomPane class:
public class CustomPane extends StackPane implements Initializable {
#FXML
StackPane view;
#FXML
AnchorPane objectPane;
#FXML
GriddedPane background;
private DoubleProperty zoomFactor = new SimpleDoubleProperty(1.5);
private BooleanProperty altStatus = new SimpleBooleanProperty(false);
public CustomPane() {
super();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getClassLoader().getResource("CustomCanvas.fxml"));
loader.setController(this);
loader.setRoot(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
objectPane.setStyle("-fx-background-color: transparent");
objectPane.prefWidthProperty().bind(prefWidthProperty());
objectPane.prefHeightProperty().bind(prefHeightProperty());
objectPane.getChildren().add(new Circle(512, 378, 20, Color.RED));
}
public void zoom(ScrollPane parent, Node node, double factor, double x, double y) {
Timeline timeline = new Timeline(60);
// determine scale
double oldScale = node.getScaleX();
double scale = oldScale * factor;
double f = (scale / oldScale) - 1;
// determine offset that we will have to move the node
Bounds bounds = node.localToScene(node.getBoundsInLocal());
double dx = (x - (bounds.getWidth() / 2 + bounds.getMinX()));
double dy = (y - (bounds.getHeight() / 2 + bounds.getMinY()));
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(100), new KeyValue(node.translateXProperty(), node.getTranslateX() - f * dx)),
new KeyFrame(Duration.millis(100), new KeyValue(node.translateYProperty(), node.getTranslateY() - f * dy)),
new KeyFrame(Duration.millis(100), new KeyValue(node.scaleXProperty(), scale)),
new KeyFrame(Duration.millis(100), new KeyValue(node.scaleYProperty(), scale))
);
timeline.play();
Bounds viewportBounds = parent.getViewportBounds();
if (bounds.getWidth() < viewportBounds.getWidth()) {
setMinWidth(viewportBounds.getWidth());
requestLayout();
}
if (getMinHeight() < viewportBounds.getHeight()) {
setMinHeight(viewportBounds.getHeight());
requestLayout();
}
}
public final Double getZoomFactor() {
return zoomFactor.get();
}
public final void setZoomFactor(Double zoomFactor) {
this.zoomFactor.set(zoomFactor);
}
public final DoubleProperty zoomFactorProperty() {
return zoomFactor;
}
public boolean getAltStatus() {
return altStatus.get();
}
public BooleanProperty altStatusProperty() {
return altStatus;
}
public void setAltStatus(boolean altStatus) {
this.altStatus.set(altStatus);
}
}
Controller class:
public class Controller implements Initializable {
public ScrollPane scrollPane;
public CustomPane customPane;
public AnchorPane anchorPane;
public Tab tab1;
#Override
public void initialize(URL location, ResourceBundle resources) {
scrollPane.viewportBoundsProperty().addListener((observable, oldValue, newValue) -> {
customPane.setMinSize(newValue.getWidth(), newValue.getHeight());
});
scrollPane.requestLayout();
tab1.getTabPane().addEventFilter(KeyEvent.KEY_PRESSED, event1 -> {
if (event1.getCode() == KeyCode.ALT)
customPane.setAltStatus(true);
});
tab1.getTabPane().addEventFilter(KeyEvent.KEY_RELEASED, event1 -> {
if (event1.getCode() == KeyCode.ALT)
customPane.setAltStatus(false);
});
scrollPane.setOnScroll(event -> {
double zoomFactor = 1.5;
if (event.getDeltaY() <= 0)
zoomFactor = 1 / zoomFactor;
customPane.setZoomFactor(zoomFactor);
if (customPane.getAltStatus())
customPane.zoom(scrollPane, customPane, customPane.getZoomFactor(), event.getSceneX(), event.getSceneY());
});
}
}
GriddedPane class:
public class GriddedPane extends Pane implements Initializable {
DoubleProperty gridWidth = new SimpleDoubleProperty(this, "gridWidth", 10);
DoubleProperty gridHeight = new SimpleDoubleProperty(this, "gridHeight", 10);
public GriddedPane() {
super();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
#Override
protected void layoutChildren() {
getChildren().clear();
setMouseTransparent(true);
toBack();
for (int i = 0; i < getHeight(); i += getGridWidth())
getChildren().add(makeLine(0, i, getWidth(), i, "x"));
for (int i = 0; i < getWidth(); i += getGridHeight())
getChildren().add(makeLine(i, 0, i, getHeight(), "y"));
}
public void redrawLines() {
for (Node n : getChildren()) {
Line l = (Line) n;
if (l.getUserData().equals("x")) {
l.setEndX(getWidth());
} else if (l.getUserData().equals("y")) {
l.setEndY(getHeight());
}
}
}
private Line makeLine(double sx, double sy, double ex, double ey, String data) {
final Line line = new Line(sx, sy, ex, ey);
if (ex % (getGridWidth() * 10) == 0.0) {
line.setStroke(Color.BLACK);
line.setStrokeWidth(0.3);
} else if (ey % (getGridHeight() * 10) == 0.0) {
line.setStroke(Color.BLACK);
line.setStrokeWidth(0.3);
} else {
line.setStroke(Color.GRAY);
line.setStrokeWidth(0.1);
}
line.setUserData(data);
return line;
}
public double getGridWidth() {
return gridWidth.get();
}
public DoubleProperty gridWidthProperty() {
return gridWidth;
}
public void setGridWidth(double gridWidth) {
this.gridWidth.set(gridWidth);
}
public double getGridHeight() {
return gridHeight.get();
}
public DoubleProperty gridHeightProperty() {
return gridHeight;
}
public void setGridHeight(double gridHeight) {
this.gridHeight.set(gridHeight);
}
}
Not really sure if I unterstood what you want to achieve. But if your goal is to make the content of the scrollPane never get smaller than the scrollPane's width, this does the job for me:
public class Zoom extends Application {
#Override
public void start(Stage primaryStage) {
ImageView imageView = new ImageView(new Image(someImage));
ScrollPane scrollPane = new ScrollPane(imageView);
StackPane root = new StackPane(scrollPane);
imageView.fitWidthProperty().bind(scrollPane.widthProperty());
imageView.fitHeightProperty().bind(scrollPane.heightProperty());
scrollPane.setOnScroll(evt -> {
boolean zoomOut = evt.getDeltaY() < 0;
double zoomFactor = zoomOut ? -0.2 : 0.2;
imageView.setScaleX(imageView.getScaleX() + zoomFactor);
imageView.setScaleY(imageView.getScaleY() + zoomFactor);
if (zoomOut) {
Bounds bounds = imageView.getBoundsInParent();
if (bounds.getWidth() < scrollPane.getWidth()) {
imageView.setScaleX(1);
imageView.setScaleY(1);
}
}
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I can not resize the two rectangles, but only the right one. What should I add to my code ?
I want also that when the lower edge of the left rectangle is moved, it also moves the upper edge of the right rectangle.
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class Resizing extends JPanel {
Rectangle rect = new Rectangle(100,100,150,150);
Rectangle rect2 = new Rectangle(300,100,150,150);
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(new Color(0, 0, 200));
g2.fill(rect);
g2.setColor(new Color(0, 0, 200));
g2.fill(rect2);
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Resizing essai = new Resizing();
Resizer1 rectangle = new Resizer1(essai);
essai.addMouseListener(rectangle);
essai.addMouseMotionListener(rectangle);
f.add(essai);
Resizing test2 = new Resizing();
Resizer2 rectangle2 = new Resizer2(test2);
test2.addMouseListener(rectangle2);
test2.addMouseMotionListener(rectangle2);
f.add(test2);
f.setSize(600,400);
f.setLocation(100,100);
f.setVisible(true);
}
}
class Resizer1 extends MouseAdapter {
Resizing component;
boolean dragging = false;
// Give user some leeway for selections.
final int PROX_DIST = 3;
public Resizer1(Resizing r) {
component = r;
}
#Override
public void mousePressed(MouseEvent e) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is set for resizing, allow dragging.
dragging = true;
}
}
#Override
public void mouseReleased(MouseEvent e) {
dragging = false;
}
#Override
public void mouseDragged(MouseEvent e) {
if(dragging){
Point p = e.getPoint();
Rectangle r = component.rect;
int type = component.getCursor().getType();
int dy = p.y - r.y;
switch(type) {
case Cursor.N_RESIZE_CURSOR:
int height = r.height - dy;
r.setRect(r.x, r.y+dy, r.width, height);
break;
case Cursor.S_RESIZE_CURSOR:
height = dy;
r.setRect(r.x, r.y, r.width, height);
break;
default:
System.out.println("unexpected type: " + type);
}
component.repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if(!isOverRect(p)) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is not over rect reset it to the default.
component.setCursor(Cursor.getDefaultCursor());
}
return;
}
// Locate cursor relative to center of rect.
int outcode = getOutcode(p);
Rectangle r = component.rect;
switch(outcode) {
case Rectangle.OUT_TOP:
if(Math.abs(p.y - r.y) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.N_RESIZE_CURSOR));
}
break;
case Rectangle.OUT_BOTTOM:
if(Math.abs(p.y - (r.y+r.height)) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.S_RESIZE_CURSOR));
}
break;
default: // center
component.setCursor(Cursor.getDefaultCursor());
}
}
/**
* Make a smaller Rectangle and use it to locate the
* cursor relative to the Rectangle center.
*/
private int getOutcode(Point p) {
Rectangle r = (Rectangle)component.rect.clone();
r.grow(-PROX_DIST, -PROX_DIST);
return r.outcode(p.x, p.y);
}
/**
* Make a larger Rectangle and check to see if the
* cursor is over it.
*/
private boolean isOverRect(Point p) {
Rectangle r = (Rectangle)component.rect.clone();
r.grow(PROX_DIST, PROX_DIST);
return r.contains(p);
}
}
class Resizer2 extends MouseAdapter {
Resizing component;
boolean dragging = false;
// Give user some leeway for selections.
final int PROX_DIST = 3;
public Resizer2(Resizing r) {
component = r;
}
#Override
public void mousePressed(MouseEvent e2) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is set for resizing, allow dragging.
dragging = true;
}
}
#Override
public void mouseReleased(MouseEvent e2) {
dragging = false;
}
#Override
public void mouseDragged(MouseEvent e2) {
if(dragging){
Point p = e2.getPoint();
Rectangle r = component.rect2;
int type = component.getCursor().getType();
int dy = p.y - r.y;
switch(type) {
case Cursor.N_RESIZE_CURSOR:
int height = r.height - dy;
r.setRect(r.x, r.y+dy, r.width, height);
break;
case Cursor.S_RESIZE_CURSOR:
height = dy;
r.setRect(r.x, r.y, r.width, height);
break;
default:
System.out.println("unexpected type: " + type);
}
component.repaint();
}
}
#Override
public void mouseMoved(MouseEvent e2) {
Point p = e2.getPoint();
if(!isOverRect(p)) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is not over rect reset it to the default.
component.setCursor(Cursor.getDefaultCursor());
}
return;
}
// Locate cursor relative to center of rect.
int outcode = getOutcode(p);
Rectangle r = component.rect2;
switch(outcode) {
case Rectangle.OUT_TOP:
if(Math.abs(p.y - r.y) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.N_RESIZE_CURSOR));
}
break;
case Rectangle.OUT_BOTTOM:
if(Math.abs(p.y - (r.y+r.height)) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.S_RESIZE_CURSOR));
}
break;
default: // center
component.setCursor(Cursor.getDefaultCursor());
}
}
/**
* Make a smaller Rectangle and use it to locate the
* cursor relative to the Rectangle center.
*/
private int getOutcode(Point p) {
Rectangle r = (Rectangle)component.rect2.clone();
r.grow(-PROX_DIST, -PROX_DIST);
return r.outcode(p.x, p.y);
}
/**
* Make a larger Rectangle and check to see if the
* cursor is over it.
*/
private boolean isOverRect(Point p) {
Rectangle r = (Rectangle)component.rect2.clone();
r.grow(PROX_DIST, PROX_DIST);
return r.contains(p);
}
}
Problems I see on inspection of your code:
You are adding 2 JPanels to your JFrame, but only one will show since they're being added in a default fashion to a BorderLayout-using container. Edit: I see now why you're doing this, but as explained below, you should not be doing this. Only create one Resizing object and add it once to the JFrame. The one Resizing will show both rectangles, and the single MouseAdapter should be coded to allow you to interact with both rectangles.
You are not making your Rectangle fields private and are allowing outside classes (namely Resizer1) to directly access and minipulate your fields. You'd be better off using public methods that allow other classes to selectively query your class for its state or to change your class's state.
Don't use two Resizer classes, Resizer1 and Resizer2, and it is this as well as your adding two Resizing objects to the JFrame that are in fact the main reason for your problems. Instead, use just one Resizer class, and use it as a single MouseAdapter added to a single Resizing object. Then in this single class, allow both rectangles to be changed.
Please post comments if you have any questions.
You ask:
Thank you, but how can I allow both rectangles to be changed ? In class Resizer, there is only one component (r) ?
There is only one component, but it holds two rectangles, and it knows the difference between the two rectangles since it has two rectangle variables.
Edit 2
Consider:
Editing your original question and adding your new code to the bottom, deleting your redundant new question.
Creating a non-GUI object, say called MyRectangle, that holds a Rectangle object.
This new class can have methods that allow your to pass in a Point or an x and y positions, and return information to let the calling code know if the mouse is over the top or bottom edge (your code already does this, so this should be no problem for you).
This new class will have mutator (setter) methods that allow outside classes set its rectangle y position and height.
The new class will have a fill method that accepts a Graphics2D parameter and uses it to fill the Rectangle that it holds.
Then give your Resizer class a List<MyRectangle>, actually an ArrayList of them, say called myRectangleList, and you can add two MyRectangle objects to it
Then give Resizer a getMyRectangleList method that returns the list.
Then in your MouseAdapter, iterate through the List to see if the mouse is over an edge
etc...
e.g.,
class MyRectangle {
private Rectangle rect;
private String name;
public MyRectangle(int x, int y, int width, int height, String name) {
rect = new Rectangle(x, y, width, height);
this.name = name;
}
public void fill(Graphics2D g2) {
g2.fill(rect);
}
public int getOutcode(Point p) {
// return ... do what you need to figure tihs out
}
public boolean isOverRect(Point p) {
// return ... do what you need to figure tihs out
}
public String getName() {
return name;
}
#Override
public String toString() {
return name + " " + rect.toString();
}
}
And then something like:
public class Resizing2 extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = 400;
private static final Color RECT_COLOR = Color.blue;
private List<MyRectangle> rectangleList = new ArrayList<>();
public Resizing2() {
rectangleList.add(new MyRectangle(100, 100, 150, 150, "Rect 1"));
rectangleList.add(new MyRectangle(300, 100, 150, 150, "Rect 2"));
}
public List<MyRectangle> getRectangleList() {
return rectangleList;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(RECT_COLOR);
for (MyRectangle rect : rectangleList) {
rect.fill(g2);
}
}
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
// ..... etc...
I am making a GUI component to represent something like a Chess board in a window. Normally it will be a grid of 8x8 squares, although some variants require a 10x8 board etc. The first step is to make a panel that contains a grid of 8x8 components.
The class Board extends JPanel and uses a GridLayout to model a grid of 8x8 components. In an effort to get something done these are simply of class Square which extends JButton. The trouble is that they're not squares!
The Board has been added to a freshly instantiated JFrame, packed and rendered on the screen. Of course, right now the board takes up the entire frame as it is resized by the user. The grid scales with the board and this distorts the squares into rectangles.
This is not entirely undesired behaviour. I would like the board to scale with the frame. However, I would like to ensure that the squares remain square at all times. The board could be rectangular (10x8) but should maintain a fixed proportion.
How do I get square squares?
You can choose to use a LayoutManager that honors the preferred size of the cells instead. GridLayout will provide a equal amount of the available space to each cell, which doesn't appear to be quite what you want.
For example, something like GridBagLayout
public class TestChessBoard {
public static void main(String[] args) {
new TestChessBoard();
}
public TestChessBoard() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ChessBoardPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ChessBoardPane extends JPanel {
public ChessBoardPane() {
int index = 0;
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
Color color = index % 2 == 0 ? Color.BLACK : Color.WHITE;
gbc.gridx = col;
gbc.gridy = row;
add(new Cell(color), gbc);
index++;
}
index++;
}
}
}
public class Cell extends JButton {
public Cell(Color background) {
setContentAreaFilled(false);
setBorderPainted(false);
setBackground(background);
setOpaque(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(25, 25);
}
}
}
Updated with proportional example
Now, if you want to do a proportional layout (so that each cell of the grid remains proportional to the other regardless of the available space), things begin to get ... fun ...
public class TestChessBoard {
public static void main(String[] args) {
new TestChessBoard();
}
public TestChessBoard() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestChessBoard.ChessBoardPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ChessBoardPane extends JPanel {
public ChessBoardPane() {
int index = 0;
setLayout(new ChessBoardLayoutManager());
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
Color color = index % 2 == 0 ? Color.BLACK : Color.WHITE;
add(new TestChessBoard.Cell(color), new Point(col, row));
index++;
}
index++;
}
}
}
public class Cell extends JButton {
public Cell(Color background) {
setContentAreaFilled(false);
setBorderPainted(false);
setBackground(background);
setOpaque(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(25, 25);
}
}
public class ChessBoardLayoutManager implements LayoutManager2 {
private Map<Point, Component> mapComps;
public ChessBoardLayoutManager() {
mapComps = new HashMap<>(25);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof Point) {
mapComps.put((Point) constraints, comp);
} else {
throw new IllegalArgumentException("ChessBoard constraints must be a Point");
}
}
#Override
public Dimension maximumLayoutSize(Container target) {
return preferredLayoutSize(target);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
Point[] keys = mapComps.keySet().toArray(new Point[mapComps.size()]);
for (Point p : keys) {
if (mapComps.get(p).equals(comp)) {
mapComps.remove(p);
break;
}
}
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return new CellGrid(mapComps).getPreferredSize();
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
int gridSize = Math.min(width, height);
CellGrid grid = new CellGrid(mapComps);
int rowCount = grid.getRowCount();
int columnCount = grid.getColumnCount();
int cellSize = gridSize / Math.max(rowCount, columnCount);
int xOffset = (width - (cellSize * columnCount)) / 2;
int yOffset = (height - (cellSize * rowCount)) / 2;
Map<Integer, List<CellGrid.Cell>> cellRows = grid.getCellRows();
for (Integer row : cellRows.keySet()) {
List<CellGrid.Cell> rows = cellRows.get(row);
for (CellGrid.Cell cell : rows) {
Point p = cell.getPoint();
Component comp = cell.getComponent();
int x = xOffset + (p.x * cellSize);
int y = yOffset + (p.y * cellSize);
comp.setLocation(x, y);
comp.setSize(cellSize, cellSize);
}
}
}
public class CellGrid {
private Dimension prefSize;
private int cellWidth;
private int cellHeight;
private Map<Integer, List<Cell>> mapRows;
private Map<Integer, List<Cell>> mapCols;
public CellGrid(Map<Point, Component> mapComps) {
mapRows = new HashMap<>(25);
mapCols = new HashMap<>(25);
for (Point p : mapComps.keySet()) {
int row = p.y;
int col = p.x;
List<Cell> rows = mapRows.get(row);
List<Cell> cols = mapCols.get(col);
if (rows == null) {
rows = new ArrayList<>(25);
mapRows.put(row, rows);
}
if (cols == null) {
cols = new ArrayList<>(25);
mapCols.put(col, cols);
}
Cell cell = new Cell(p, mapComps.get(p));
rows.add(cell);
cols.add(cell);
}
int rowCount = mapRows.size();
int colCount = mapCols.size();
cellWidth = 0;
cellHeight = 0;
for (List<Cell> comps : mapRows.values()) {
for (Cell cell : comps) {
Component comp = cell.getComponent();
cellWidth = Math.max(cellWidth, comp.getPreferredSize().width);
cellHeight = Math.max(cellHeight, comp.getPreferredSize().height);
}
}
int cellSize = Math.max(cellHeight, cellWidth);
prefSize = new Dimension(cellSize * colCount, cellSize * rowCount);
System.out.println(prefSize);
}
public int getRowCount() {
return getCellRows().size();
}
public int getColumnCount() {
return getCellColumns().size();
}
public Map<Integer, List<Cell>> getCellColumns() {
return mapCols;
}
public Map<Integer, List<Cell>> getCellRows() {
return mapRows;
}
public Dimension getPreferredSize() {
return prefSize;
}
public int getCellHeight() {
return cellHeight;
}
public int getCellWidth() {
return cellWidth;
}
public class Cell {
private Point point;
private Component component;
public Cell(Point p, Component comp) {
this.point = p;
this.component = comp;
}
public Point getPoint() {
return point;
}
public Component getComponent() {
return component;
}
}
}
}
}
This got a bit long, so here is the quick answer: You can't maintain a square board with square squares given your board dimensions (8x8, 10x8) and fully fill the screen if the user can resize it. You should limit the size of the board so that it maintains the aspect ratio even if that means you have some blank space in your frame. OK, read on for the long-winded explanation...
There are two ways you can make this work. Either you can limit the possible sizes of the JFrame, or you can limit the size of your Board so it doesn't always fill the frame. Limiting the size of the board is the more common method, so let's start with that.
Option 1: Limiting the Board
If you are working with a fixed set of board dimensions (8x8, 10x8, and a couple others maybe), and assuming each square has some minimum size (1 pixel squares on a chess board don't sound too practical), there are only so many frame dimensions that the board can fully fill. If your frame is 80pixels by 80pixels, your 8x8 board fits perfectly. But as soon as the user resizes to something like 85x80 you're stuck because you can't fully fill that while maintaining squares with the board dimensions you gave.
In this case you want to leave 5 pixels empty, whether it's 5 above or below, or 2.5 above and below, or whatever, doesn't matter. This should sound familiar - it's an aspect ratio problem and basically why you can get black bars on the edges of your TV depending on TV vs. movie dimensions.
Option 2: Limiting the Frame
If you want the board to always fully fill the frame, probably not what you want, then you have to adjust the size of the frame after a user resizes it. Say you are using a 10x8 board, and the user sets the frame to 107x75. That's not too bad, and with a little math you can figure out 100x80 is your closest aspect ratio that works, and fix the window. It will probably a bit frustrating for the user if the window keeps jumping around on them though, especially if they tried to make it something way off like 50x200.
Last thoughts / Example
Limiting the board is most likely the correct solution. Everything from games to desktop apps follows that principle. Take the ribbon in MS Office products for example. As you make the window larger, the ribbon will expand (maintaining its proportions) until it hits it max size, and then you just get more space for your document. When you make the window smaller the ribbon gets smaller (again maintaining its proportions) until it hits a minimum size and then you start losing parts of it (remember, don't want 1x1 squares on your board).
On the other hand you can prevent the user from resizing the window at all. I'm pretty sure this is how MineSweeper works (don't have it on this computer to double check), and may be a better/easier solution for what you need.
I'm making a program that has an image that you scroll around on, and I can't figure out how to update the image if a button is pressed (For example: Adds a Green Ellipse to the image.) It already draws the image into the JScrollPane and you can scroll around, but when you click a button it doesn't refresh the image. (more details in code)
Here is the code:
public class PegMaster extends JPanel implements ActionListener {
//Note: not complete code
public PegBox[] pegbox = new PegBox[9];
public static Dimension size = new Dimension(520, 500);
public BufferedImage canvas;
public Graphics2D g2d;
public JScrollPane scroller;
JPanel panel;
private Canvas window;
JScrollPane pictureScrollPane;
public PegMaster() {
JButton button = new JButton("test");
button.addActionListener(this);
add(button);
canvas = new BufferedImage((int)size.getWidth()-30, 75 * GUESSES, BufferedImage.TYPE_INT_RGB);
g2d = canvas.createGraphics();
for(int i = 0;i<=pegbox.length-1;i++) {
pegbox[i] = new PegBox(i, g2d);
}
window = new Canvas(new ImageIcon(toImage(canvas)), 1);
//Class Canvas is a Scrollable JLabel to draw to (the image)
pictureScrollPane = new JScrollPane(window);
pictureScrollPane.setPreferredSize(new Dimension((int)size.getWidth()-10, (int)size.getHeight()-20));
pictureScrollPane.setViewportBorder(BorderFactory.createLineBorder(Color.black));
add(pictureScrollPane);
//adds the scrollpane, but can't update the image in it
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createGUI();
//just adds the scrollpane
}
});
}
public void paint(Graphics g) {
super.paint(g);
for(int i = 0;i<=pegbox.length-1;i++) {
//pegbox[i] = new PegBox(i);
pegbox[i].draw(g2d);
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//tried re-making the scrollpane, didn't work.
//window = new Canvas(new ImageIcon(toImage(canvas)), 1);
//pictureScrollPane = new JScrollPane(window);
//pictureScrollPane.setPreferredSize(new Dimension((int)size.getWidth()-10 (int)size.getHeight()-20));
//pictureScrollPane.setViewportBorder(BorderFactory.createLineBorder(Color.black));
//tried imageupdate: pictureScrollPane.imageUpdate(canvas, 0, 0, 0 (int)size.getWidth()-10, (int)size.getHeight()-20);
//remove(pictureScrollPane);
//tried this: pictureScrollPane.revalidate();
repaint();
}
}
Firstly, don't use Canvas it's a heavy weight component, it will only cause you issues in the long run, use either JComponent or JPanel
Secondly, don't override paint, use paintComponent instead. paint does a lot of work, including painting things like the border and child components. It's better if you use paintComponent as it's at the right layer within the paint hierarchy for what you want do to.
Thirdly, NEVER call something like Thread.sleep while in the Event Dispatching Thread. This will cause the event queue to pause and stop responding to events, making you program look like it's stalled.
Fourthly, NEVER call repaint (invalidate, revalidate or any method that might cause a repaint request to occur) within a paint method. You will simply end up maxing out your CPU and you will be forced to kill the process.
Fifthly, you didn't provide the actionPerformed method, which is probably where all the action (and problems) are. I'd imagin you need to call window.repaint() and possibly window.invalidate() (in reverse order), but since you didn't provided use with this code, that's simply speculation...
Try this class which displays an Image. This can be added to a JScrollPane
public class ImagePanel extends JPanel {
public Image img;
public ImagePanel(Image img){
this.img = img;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(img, 0, 0, this);
}
}
Now add this class to the JScrollPane. To update it, just change the image reference and call the repaint() method on the component
The above solution didn't solved my purpose so I researched and found this.
Please follow the link for whole example. I have added the code to refer in case the link changes.
public class ScrollablePicture extends JLabel
implements Scrollable,
MouseMotionListener {
private int maxUnitIncrement = 1;
private boolean missingPicture = false;
public ScrollablePicture(ImageIcon i, int m) {
super(i);
if (i == null) {
missingPicture = true;
setText("No picture found.");
setHorizontalAlignment(CENTER);
setOpaque(true);
setBackground(Color.white);
}
maxUnitIncrement = m;
//Let the user scroll by dragging to outside the window.
setAutoscrolls(true); //enable synthetic drag events
addMouseMotionListener(this); //handle mouse drags
}
//Methods required by the MouseMotionListener interface:
public void mouseMoved(MouseEvent e) { }
public void mouseDragged(MouseEvent e) {
//The user is dragging us, so scroll!
Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
scrollRectToVisible(r);
}
public Dimension getPreferredSize() {
if (missingPicture) {
return new Dimension(320, 480);
} else {
return super.getPreferredSize();
}
}
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
public int getScrollableUnitIncrement(Rectangle visibleRect,
int orientation,
int direction) {
//Get the current position.
int currentPosition = 0;
if (orientation == SwingConstants.HORIZONTAL) {
currentPosition = visibleRect.x;
} else {
currentPosition = visibleRect.y;
}
//Return the number of pixels between currentPosition
//and the nearest tick mark in the indicated direction.
if (direction < 0) {
int newPosition = currentPosition -
(currentPosition / maxUnitIncrement)
* maxUnitIncrement;
return (newPosition == 0) ? maxUnitIncrement : newPosition;
} else {
return ((currentPosition / maxUnitIncrement) + 1)
* maxUnitIncrement
- currentPosition;
}
}
public int getScrollableBlockIncrement(Rectangle visibleRect,
int orientation,
int direction) {
if (orientation == SwingConstants.HORIZONTAL) {
return visibleRect.width - maxUnitIncrement;
} else {
return visibleRect.height - maxUnitIncrement;
}
}
public boolean getScrollableTracksViewportWidth() {
return false;
}
public boolean getScrollableTracksViewportHeight() {
return false;
}
public void setMaxUnitIncrement(int pixels) {
maxUnitIncrement = pixels;
}