I've been googling for hours trying a million different subtle changes all to no avail, so I figure my best bet is to pick the brains of people more skilled than I.
I'm writing a class that loads a bunch of buttons from a database and the goal is to offer the user the ability to arrange the buttons to their liking, however, for some reason, I can't seem to get the frame to revalidate or repaint. The buttons will move around, but they will not rearrange as I have coded them to. The organization seems to be working correctly, ie the code involved in making the re arrangement when the mouse button is released, its just that the components will only stay where they are dragged and dropped, even though they are re ordered in their respective List.
The code is long, I didn't want to have to post the whole class as it may turn some people off but I don't know where I'm making the mistake so I think it would be in my best interest to post the whole thing. The main area of concern is mouseReleased(MouseEvent e) {...} and the repaint()/refresh() method, however, there could be something that I'm missing elsewhere.
tl;dr:
I'm basically just trying to perform a setBounds() after the user drags and drops the buttons in the order they want but the buttons stay in the same spot they are dragged and dropped, and won't revalidate() or repaint(). I can't even removeAll to clear the panel of components and reload.
Thank you in advanced. Here's my code:
public class AdminButtonEditor extends javax.swing.JFrame {
public AdminButtonEditor(OrderView parent) { ...
...
Component[] components = buttonsPanel.getComponents();
for (int i = 0; i < components.length; i++) {
Component c = components[i];
if (c instanceof JButton) {
JButton jb = (JButton) c;
jb.setFocusable(false);
buttons.add(new MyJButton(...));
}
}
for (int i = 0; i < buttons.size(); i++) {
buttons.get(i).addTo(editPanel);
buttons.get(i).orderIndex=modButtonList.get(i).menuModifier.getViewOrderValue();
buttons.get(i).idx=i;
}
EventHandler eh = new EventHandler();
addWindowListener(eh);
editPanel.addMouseMotionListener(eh);
editPanel.addMouseListener(eh);
contentPane.add(editPanel, BorderLayout.CENTER);
}
protected void refresh() {
if (!buttons.isEmpty() && buttons.get(0) != null) {
contentPane.remove(editPanel);
editPanel.removeAll();
for (int i = 0; i < buttons.size(); i++) {
MyJButton s = buttons.get(i);
s.addTo(editPanel);
}
contentPane.add(editPanel, BorderLayout.CENTER);
editPanel.repaint();
}
}
public void paint(Graphics g) {
refresh();
super.paint(g);
}
private int getSelectionIndex(int x, int y) {
int s=-1;
for (int i=buttons.size()-1; i>=0;i--){
if (buttons.get(i).contains(x, y)) {
s = i;
break;
}
}
return s;
}
private class EventHandler implements MouseInputListener,WindowListener, ActionListener {
private int selectionIndex, startX, startY, lastX, lastY;
private MyJButton selected;
private boolean moving=false;
....
....
public void mouseReleased(MouseEvent e) {
if (moving){
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
moving=false;
int dropX=e.getX();
int dropY=e.getY();
int row = dropY/selected.height;
int col = dropX/selected.width;
int idx=(row*3+col)-1;
int oldIdx=buttons.indexOf(selected);
insertIntoList(idx,oldIdx);
}
if (selected!=null){
selected.unHighLight();
selected=null;
}
Collections.sort(buttons);
for (int i=0;i<buttons.size();i++){
//modButtonList.get(buttons.get(i).idx).menuModifier.setViewOrderValue(buttons.get(i).orderIndex);
}
editPanel.validate();
repaint();
}
private void insertIntoList(int idx, int oldIdx) {
MyJButton temp = buttons.get(idx);
int tempid=buttons.get(idx).idx;
buttons.set(idx, new MyJButton(selected.text,selected.x,selected.y,selected.width,selected.height,selected.idx));
buttons.get(idx).orderIndex=temp.orderIndex;
if (idx<oldIdx){
int id;
for (int i=oldIdx;i>idx+1;i--){
id=buttons.get(i).orderIndex;
buttons.set(i, new MyJButton(buttons.get(i-1).text,buttons.get(i-1).x,buttons.get(i-1).y,buttons.get(i-1).width,buttons.get(i-1).height,buttons.get(i-1).idx));
buttons.get(i).orderIndex=id;
}
id = buttons.get(idx+1).orderIndex;
buttons.set(idx+1,new MyJButton(temp.text,temp.x,temp.y,temp.width,temp.height,temp.idx));
buttons.get(idx+1).orderIndex=id;
} else if (idx>oldIdx) {
int id;
for (int i=oldIdx;i<idx-1;i++){
id=buttons.get(i).orderIndex;
buttons.set(i, new MyJButton(buttons.get(i+1).text,buttons.get(i+1).x,buttons.get(i+1).y,buttons.get(i+1).width,buttons.get(i+1).height,buttons.get(i+1).idx));
buttons.get(i).orderIndex=id;
}
id = buttons.get(idx-1).orderIndex;
buttons.set(idx-1,new MyJButton(temp.text,temp.x,temp.y,temp.width,temp.height,temp.idx));
buttons.get(idx-1).orderIndex=id;;
} else {
buttons.get(idx).x=buttons.get(idx).originx;
buttons.get(idx).y=buttons.get(idx).originy;
}
repaint();
}
public void mouseDragged(MouseEvent e) {
if (moving) {
Graphics g = editPanel.getGraphics();
g.setColor(Color.black);
g.drawLine(selected.getXPos(), 0, selected.getXPos(),editPanel.getWidth());
g.drawLine(0, selected.getYPos(), editPanel.getHeight(), selected.getYPos());
selected.moveBy(e.getX()-lastX, e.getY()-lastY);
g.setXORMode(Color.black);
g.drawLine(selected.getXPos(), 0, selected.getXPos(), editPanel.getWidth());
g.drawLine(0, selected.getYPos(), editPanel.getHeight(), selected.getYPos());
lastX=e.getX();
lastY=e.getY();
repaint();
}
}
....
}
private class MyJButton extends JButton implements Comparable {
private int orderIndex,idx;
private int x, y, width, height,originx,originy;
private String text;
public Border DEFAULT_BORDER;// new SoftBevelBorder(BevelBorder.RAISED);
public Border SELECT_BORDER = BorderFactory.createLineBorder(Color.RED, 3, true);
public MyJButton(String text, int x, int y, int width, int height) {
....
setFocusable(false);
}
public MyJButton(String text, int x, int y, int width, int height,int idx) {....}
public void addTo(JPanel p) {
setBounds(x, y, width, height);
p.add(this);
}
#Override
public boolean contains(int x, int y) {
int x1 = x, y1 = y;
if (x1 >= this.x && y1 >= this.y && x1 <= this.x + width && y1 <= this.y + height) {
return true;
} else {
return false;
}
}
#Override
public void setSize(int w, int h) {
width = w;
height = h;
}
....
public void moveBy(int dx, int dy) {
x += dx;
y += dy;
}
#Override
public void resize(int newWidth, int newHeight) {
this.width = newWidth;
this.height = newHeight;
setBounds(x, y, width, height);
}
public int compareTo(Object o) {
MyJButton mjb = (MyJButton)o;
return this.idx-mjb.idx;
}
}
}
+1 to GagandeepBalis comment.
Okay so I found this very cool and decided to look into it more.
I came up with some logic which will be needed for it to work, maybe not the best but...:
1) We need to make our JButtons draggable ( thank you #camickr and his DragLayout) :)
2) When a JButtons is dragged and than dropped i.e on mouseReleased(..) we should check if a our dragged buttons collide with any others
3) we check whether a JButton collides with another by getting the JButton image and counting how many opaque pixels of the JButton, we are dragging, are covering another button.
4) sort the number of collisions and find the highest, this will be used so we can see where to insert the JButton we dragged. i.e it will be inserted by component with the most collisions.
5) sort the ArrayList which holds the buttons to match the changes
6) remove all buttons and re-add them using the Array (and thus they will be re-ordered).
Here is an example (Most code takes place in overridden ComponentMover mouseReleased(..) method):
Before dragging anything:
after dragging button 4 over button 1 and letting go of mouse button:
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DragButtons {
ArrayList<JButton> buttons = new ArrayList<>();
public DragButtons() {
initComponents();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DragButtons();
}
});
}
private void initComponents() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(new GridLayout(2, 2));
ComponentMover cm = new ComponentMover() {
#Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
HashMap<Integer, JButton> collisions = new HashMap<>();
JButton draggedButton = (JButton) e.getSource();
for (JButton btn : buttons) {//iterate through all buttons and get the number of collsions of each
if (btn != draggedButton) {//dont chck button we were dragging
int col = checkPerPixelCollision(draggedButton, btn);
System.out.println("Button " + btn.getText());
System.out.println(col);
collisions.put(col, btn);
}
}
//lets get the button which had most collisions
int maxCollisions = 0;
JButton intersectingButton = null;
for (Map.Entry<Integer, JButton> entry : collisions.entrySet()) {
Integer collisionCount = entry.getKey();
JButton button = entry.getValue();
if (collisionCount > maxCollisions) {
maxCollisions = collisionCount;
intersectingButton = button;
}
}
boolean reLayout = false;
if (maxCollisions > 0) {//check if there was any
System.out.println("Button " + draggedButton.getText() + " is intersecting more of Button " + intersectingButton.getText());
System.out.println("Collisions: " + maxCollisions);
reLayout = true;
} else {
System.out.println("No change made");
reLayout = false;
}
ArrayList<JButton> tmpButtons = (ArrayList<JButton>) buttons.clone();//create clone of buttons
if (reLayout) {//a button as moved and panel needs to be layed out
buttons.clear();//clear old buttons
for (JButton b : tmpButtons) {//re-order jbuttons
if (b == intersectingButton) {
buttons.add(draggedButton);
} else if (b == draggedButton) {
buttons.add(intersectingButton);
} else {
buttons.add(b);
}
}
panel.removeAll();//remove all buttons
for (JButton btn : buttons) {//iterate through all buttons and get the number of collsions of each
panel.add(btn);//re-add buttons according to arraylist
}
panel.revalidate();
panel.repaint();
//re-order the Array of buttons to fit
//remove all button and re add them using sorted array
}
}
};
for (int i = 0; i < 4; i++) {
JButton b = new JButton(String.valueOf(i + 1));
buttons.add(b);
panel.add(b);
cm.registerComponent(b);
}
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
public HashSet<String> getMask(JButton e) {
HashSet<String> mask = new HashSet<>();
int pixel, a;
BufferedImage bi = null;
try {
bi = componentToImage(e, e.getBounds()); //gets the current image being shown
} catch (IOException ex) {
Logger.getLogger(DragButtons.class.getName()).log(Level.SEVERE, null, ex);
}
for (int i = 0; i < bi.getWidth(); i++) { // for every (x,y) component in the given box,
for (int j = 0; j < bi.getHeight(); j++) {
pixel = bi.getRGB(i, j); // get the RGB value of the pixel
a = (pixel >> 24) & 0xff;
if (a != 0) { // if the alpha is not 0, it must be something other than transparent
mask.add((e.getX() + i) + "," + (e.getY() - j)); // add the absolute x and absolute y coordinates to our set
}
}
}
return mask; //return our set
}
public static BufferedImage componentToImage(Component component, Rectangle region) throws IOException {
BufferedImage img = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = img.getGraphics();
g.setColor(component.getForeground());
g.setFont(component.getFont());
component.paintAll(g);
ImageIO.write(img, "png", new File("c:/saved.png"));
return img;
}
// Returns true if there is a collision between object a and object b
public int checkPerPixelCollision(JButton b, JButton b2) {
// This method detects to see if the images overlap at all. If they do, collision is possible
int ax1 = (int) b2.getX();
int ay1 = (int) b2.getY();
int ax2 = ax1 + (int) b2.getWidth();
int ay2 = ay1 + (int) b2.getHeight();
int bx1 = (int) b.getX();
int by1 = (int) b.getY();
int bx2 = bx1 + (int) b.getWidth();
int by2 = by1 + (int) b.getHeight();
if (by2 < ay1 || ay2 < by1 || bx2 < ax1 || ax2 < bx1) {
return 0; // Collision is impossible.
} else { // Collision is possible.
// get the masks for both images
HashSet<String> maskPlayer1 = getMask(b2);
HashSet<String> maskPlayer2 = getMask(b);
maskPlayer1.retainAll(maskPlayer2); // Check to see if any pixels in maskPlayer2 are the same as those in maskPlayer1
if (maskPlayer1.size() > 0) { // if so, than there exists at least one pixel that is the same in both images, thus
return maskPlayer1.size();
}
}
return 0;
}
}
class ComponentMover extends MouseAdapter {
private Insets dragInsets = new Insets(0, 0, 0, 0);
private Dimension snapSize = new Dimension(1, 1);
private Insets edgeInsets = new Insets(0, 0, 0, 0);
private boolean changeCursor = true;
private boolean autoLayout = false;
private Class destinationClass;
private Component destinationComponent;
private Component destination;
private Component source;
private Point pressed;
private Point location;
private Cursor originalCursor;
private boolean autoscrolls;
private boolean potentialDrag;
/**
* Constructor for moving individual components. The components must be
* regisetered using the registerComponent() method.
*/
public ComponentMover() {
}
/**
* Constructor to specify a Class of Component that will be moved when drag
* events are generated on a registered child component. The events will be
* passed to the first ancestor of this specified class.
*
* #param destinationClass the Class of the ancestor component
* #param component the Components to be registered for forwarding drag
* events to the ancestor Component.
*/
public ComponentMover(Class destinationClass, Component... components) {
this.destinationClass = destinationClass;
registerComponent(components);
}
/**
* Constructor to specify a parent component that will be moved when drag
* events are generated on a registered child component.
*
* #param destinationComponent the component drage events should be
* forwareded to
* #param components the Components to be registered for forwarding drag
* events to the parent component to be moved
*/
public ComponentMover(Component destinationComponent, Component... components) {
this.destinationComponent = destinationComponent;
registerComponent(components);
}
/**
* Get the auto layout property
*
* #return the auto layout property
*/
public boolean isAutoLayout() {
return autoLayout;
}
/**
* Set the auto layout property
*
* #param autoLayout when true layout will be invoked on the parent
* container
*/
public void setAutoLayout(boolean autoLayout) {
this.autoLayout = autoLayout;
}
/**
* Get the change cursor property
*
* #return the change cursor property
*/
public boolean isChangeCursor() {
return changeCursor;
}
/**
* Set the change cursor property
*
* #param changeCursor when true the cursor will be changed to the
* Cursor.MOVE_CURSOR while the mouse is pressed
*/
public void setChangeCursor(boolean changeCursor) {
this.changeCursor = changeCursor;
}
/**
* Get the drag insets
*
* #return the drag insets
*/
public Insets getDragInsets() {
return dragInsets;
}
/**
* Set the drag insets. The insets specify an area where mouseDragged events
* should be ignored and therefore the component will not be moved. This
* will prevent these events from being confused with a MouseMotionListener
* that supports component resizing.
*
* #param dragInsets
*/
public void setDragInsets(Insets dragInsets) {
this.dragInsets = dragInsets;
}
/**
* Get the bounds insets
*
* #return the bounds insets
*/
public Insets getEdgeInsets() {
return edgeInsets;
}
/**
* Set the edge insets. The insets specify how close to each edge of the
* parent component that the child component can be moved. Positive values
* means the component must be contained within the parent. Negative values
* means the component can be moved outside the parent.
*
* #param edgeInsets
*/
public void setEdgeInsets(Insets edgeInsets) {
this.edgeInsets = edgeInsets;
}
/**
* Remove listeners from the specified component
*
* #param component the component the listeners are removed from
*/
public void deregisterComponent(Component... components) {
for (Component component : components) {
component.removeMouseListener(this);
}
}
/**
* Add the required listeners to the specified component
*
* #param component the component the listeners are added to
*/
public void registerComponent(Component... components) {
for (Component component : components) {
component.addMouseListener(this);
}
}
/**
* Get the snap size
*
* #return the snap size
*/
public Dimension getSnapSize() {
return snapSize;
}
/**
* Set the snap size. Forces the component to be snapped to the closest grid
* position. Snapping will occur when the mouse is dragged half way.
*/
public void setSnapSize(Dimension snapSize) {
if (snapSize.width < 1
|| snapSize.height < 1) {
throw new IllegalArgumentException("Snap sizes must be greater than 0");
}
this.snapSize = snapSize;
}
/**
* Setup the variables used to control the moving of the component:
*
* source - the source component of the mouse event destination - the
* component that will ultimately be moved pressed - the Point where the
* mouse was pressed in the destination component coordinates.
*/
#Override
public void mousePressed(MouseEvent e) {
source = e.getComponent();
int width = source.getSize().width - dragInsets.left - dragInsets.right;
int height = source.getSize().height - dragInsets.top - dragInsets.bottom;
Rectangle r = new Rectangle(dragInsets.left, dragInsets.top, width, height);
if (r.contains(e.getPoint())) {
setupForDragging(e);
}
}
private void setupForDragging(MouseEvent e) {
source.addMouseMotionListener(this);
potentialDrag = true;
// Determine the component that will ultimately be moved
if (destinationComponent != null) {
destination = destinationComponent;
} else if (destinationClass == null) {
destination = source;
} else // forward events to destination component
{
destination = SwingUtilities.getAncestorOfClass(destinationClass, source);
}
pressed = e.getLocationOnScreen();
location = destination.getLocation();
if (changeCursor) {
originalCursor = source.getCursor();
source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
// Making sure autoscrolls is false will allow for smoother dragging of
// individual components
if (destination instanceof JComponent) {
JComponent jc = (JComponent) destination;
autoscrolls = jc.getAutoscrolls();
jc.setAutoscrolls(false);
}
}
/**
* Move the component to its new location. The dragged Point must be in the
* destination coordinates.
*/
#Override
public void mouseDragged(MouseEvent e) {
Point dragged = e.getLocationOnScreen();
int dragX = getDragDistance(dragged.x, pressed.x, snapSize.width);
int dragY = getDragDistance(dragged.y, pressed.y, snapSize.height);
int locationX = location.x + dragX;
int locationY = location.y + dragY;
// Mouse dragged events are not generated for every pixel the mouse
// is moved. Adjust the location to make sure we are still on a
// snap value.
while (locationX < edgeInsets.left) {
locationX += snapSize.width;
}
while (locationY < edgeInsets.top) {
locationY += snapSize.height;
}
Dimension d = getBoundingSize(destination);
while (locationX + destination.getSize().width + edgeInsets.right > d.width) {
locationX -= snapSize.width;
}
while (locationY + destination.getSize().height + edgeInsets.bottom > d.height) {
locationY -= snapSize.height;
}
// Adjustments are finished, move the component
destination.setLocation(locationX, locationY);
}
/*
* Determine how far the mouse has moved from where dragging started
* (Assume drag direction is down and right for positive drag distance)
*/
private int getDragDistance(int larger, int smaller, int snapSize) {
int halfway = snapSize / 2;
int drag = larger - smaller;
drag += (drag < 0) ? -halfway : halfway;
drag = (drag / snapSize) * snapSize;
return drag;
}
/*
* Get the bounds of the parent of the dragged component.
*/
private Dimension getBoundingSize(Component source) {
if (source instanceof Window) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle bounds = env.getMaximumWindowBounds();
return new Dimension(bounds.width, bounds.height);
} else {
return source.getParent().getSize();
}
}
/**
* Restore the original state of the Component
*/
#Override
public void mouseReleased(MouseEvent e) {
super.mouseReleased(e);
if (!potentialDrag) {
return;
}
source.removeMouseMotionListener(this);
potentialDrag = false;
if (changeCursor) {
source.setCursor(originalCursor);
}
if (destination instanceof JComponent) {
((JComponent) destination).setAutoscrolls(autoscrolls);
}
// Layout the components on the parent container
if (autoLayout) {
if (destination instanceof JComponent) {
((JComponent) destination).revalidate();
} else {
destination.revalidate();
}
destination.repaint();
}
}
}
Related
JButtons only show up when I hover over them. Also disappear when I resize window. The error lies in the constructor here. I am extending a JFrame. Removing the JPanel doesn't fix it. Please help.
public GameBoard(String title, int width, int height)
{
super();
this.boardWidth = width;
this.boardHeight = height;
// Create game state
this.board = new GameSquare[width][height];
// Set up window
setTitle(title);
setSize(720,720);
setContentPane(boardPanel);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardPanel.setLayout(new GridLayout(height,width));
int decorator = 0;
for (int y = 0; y<height; y++){
decorator++;
for (int x = 0; x<width; x++){
if (decorator % 2 == 0)
board[x][y] = new GameSquare(x, y, "BLACK");
else
board[x][y] = new GameSquare(x, y, "WHITE");
board[x][y].addActionListener(this);
boardPanel.add(board[x][y]);
decorator++;
}
}
// make our window visible
setVisible(true);
}
MCV Example.
package chess;
public class GameBoard extends JFrame implements ActionListener
{
private JPanel boardPanel = new JPanel();
private int boardHeight;
private int boardWidth;
private GameSquare[][] board;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
GameBoard gameBoard = new GameBoard("The Revolutionary War",8,8);
}
});
}
/**
* Create a new game board of the given size.
* As soon as an instance of this class is created, it is visualised
* on the screen.
*
* #param title the name printed in the window bar.
* #param width the width of the game area, in squares.
* #param height the height of the game area, in squares.
*/
public GameBoard(String title, int width, int height)
{
super();
this.boardWidth = width;
this.boardHeight = height;
// Create game state
this.board = new GameSquare[width][height];
// Set up window
setTitle(title);
setSize(720,720);
setContentPane(boardPanel);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardPanel.setLayout(new GridLayout(height,width));
for (int y = 0; y<height; y++){
for (int x = 0; x<width; x++){
board[x][y] = new GameSquare(x, y, this);
board[x][y].setEnabled(true);
board[x][y].addActionListener(this);
boardPanel.add(board[x][y]);
}
}
// make our window visible
setVisible(true);
}
/**
* Returns the GameSquare instance at a given location.
* #param x the x co-ordinate of the square requested.
* #param y the y co-ordinate of the square requested.
* #return the GameSquare instance at a given location
* if the x and y co-ordinates are valid - null otherwise.
*/
public GameSquare getSquareAt(int x, int y)
{
if (x < 0 || x >= boardWidth || y < 0 || y >= boardHeight)
return null;
return (GameSquare) board[x][y];
}
public void actionPerformed(ActionEvent e)
{
// The button that has been pressed.s
GameSquare square = (GameSquare)e.getSource();
}
//BufferedImage background = ImageIO.read(new File("/path/to/image.jpg"));
}
The other class is here:
package chess;
public class GameSquare extends JButton {
/**
*
*/
private static final long serialVersionUID = 1L;
private int x;
private int y;
private ChessPiece chessPiece;
private boolean selected;
private ImageIcon icon;
GameSquare(int xPos, int yPos, GameBoard board){
super();
x = xPos;
y = yPos;
chessPiece = null;
selected = false;
// Test to see colour of the base tile
if ((xPos+yPos) % 2 == 1){
icon = new ImageIcon(getClass().getResource("/pics/black.png"));
this.setIcon(icon);
}
else if ((xPos+yPos) % 2 == 0) {
icon = new ImageIcon(getClass().getResource("/pics/white.png"));
this.setIcon(icon);
}
}
public int getX(){
return x;
}
public int getY(){
return y;
}
public void setChessPiece(ChessPiece piece){
chessPiece = piece;
}
public ChessPiece getChessPiece(){
return chessPiece;
}
public void setImage(){
setIcon(chessPiece.getIcon());
}
public void select(){
selected = true;
setIcon(new ImageIcon(getClass().getResource("pics/selected.png")));
}
public void deselect(){
selected = false;
if (chessPiece == null) setIcon(icon);
else setImage();
}
}
Your problem is due to your overriding JPanel's getX() and getY(). These methods are used by the container's layout managers to help place components, and by overriding these key methods, you mess up the ability of the layouts to do this. Please change the names of those methods to something that does not override a key method of the class. If this is a chess square, I'd rename the variables to rank and file, and have getRank() and getFile() methods.
public class GameSquare extends JButton {
private int file;
private int rank;
// ....
GameSquare(int xPos, int yPos, GameBoard board){
super();
file = xPos;
rank = yPos;
chessPiece = null;
selected = false;
// .....
}
public int getFile(){
return file;
}
public int getRank(){
return rank;
}
// .....
}
add
pack();
in the GameBoard class, before setting visible. This will ensure that the size is correct and make room for any buttons which are hanging off the edge... This will make the frame the size of the JPanel, exactly as it is rendered after all objects are added.
also maybe
setResizable(false);
as it will stop you resizing your windows, and with images this can cause buttons to disappear as well as reappear if the user accidentally clicks the sides. I'd put this underneath: setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
How can I implement Marquee effect in Java Swing
Here's an example using javax.swing.Timer.
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
/** #see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {
private void display() {
JFrame f = new JFrame("MarqueeTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String s = "Tomorrow, and tomorrow, and tomorrow, "
+ "creeps in this petty pace from day to day, "
+ "to the last syllable of recorded time; ... "
+ "It is a tale told by an idiot, full of "
+ "sound and fury signifying nothing.";
MarqueePanel mp = new MarqueePanel(s, 32);
f.add(mp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
mp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MarqueeTest().display();
}
});
}
}
/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {
private static final int RATE = 12;
private final Timer timer = new Timer(1000 / RATE, this);
private final JLabel label = new JLabel();
private final String s;
private final int n;
private int index;
public MarqueePanel(String s, int n) {
if (s == null || n < 1) {
throw new IllegalArgumentException("Null string or n < 1");
}
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append(' ');
}
this.s = sb + s + sb;
this.n = n;
label.setFont(new Font("Serif", Font.ITALIC, 36));
label.setText(sb.toString());
this.add(label);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public void actionPerformed(ActionEvent e) {
index++;
if (index > s.length() - n) {
index = 0;
}
label.setText(s.substring(index, index + n));
}
}
I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.
So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.
The MarqueePanel scrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.
Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.
Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.
The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.
Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeController class to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.
There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.
public class MarqueePanel extends JPanel {
private JLabel textLabel;
private int panelLocation;
private ActionListener taskPerformer;
private boolean isRunning = false;
public static final int FRAMES_PER_SECOND = 24;
public static final int MOVEMENT_PER_FRAME = 5;
/**
* Class constructor creates a marquee panel.
*/
public MarqueePanel() {
this.setLayout(null);
this.textLabel = new JLabel("Scrolling Text Here");
this.panelLocation = 0;
this.taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
MarqueePanel.this.tickAnimation();
}
}
}
/**
* Starts the animation.
*/
public void start() {
this.isRunning = true;
this.tickAnimation();
}
/**
* Stops the animation.
*/
public void stop() {
this.isRunning = false;
}
/**
* Moves the label one frame to the left. If it's out of display range, move it back
* to the right, out of display range.
*/
private void tickAnimation() {
this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
if (this.panelLocation < this.textLabel.getWidth())
this.panelLocaton = this.getWidth();
this.textLabel.setLocation(this.panelLocation, 0);
this.repaint();
if (this.isRunning) {
Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
t.setRepeats(false);
t.start();
}
}
}
Add a JLabel to your frame or panel.
ScrollText s= new ScrollText("ello Everyone.");
jLabel3.add(s);
public class ScrollText extends JComponent {
private BufferedImage image;
private Dimension imageSize;
private volatile int currOffset;
private Thread internalThread;
private volatile boolean noStopRequested;
public ScrollText(String text) {
currOffset = 0;
buildImage(text);
setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "ScrollText");
internalThread.start();
}
private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage scratchImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);
Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());
int horizontalPad = 600;
int verticalPad = 10;
imageSize = new Dimension(textWidth + horizontalPad, textHeight
+ verticalPad);
image = new BufferedImage(imageSize.width, imageSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);
int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);
g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);
// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
}
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);
int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);
// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(10); // 10 frames per second
// adjust the scroll position
currOffset = (currOffset + 1) % imageSize.width;
// signal the event thread to call paint()
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
}
This is supposed to be an improvement of #camickr MarqueePanel. Please see above.
To map mouse events to the specific components added to MarqueePanel
Override add(Component comp) of MarqueePanel in order to direct all mouse events of the components
An issue here is what do do with the MouseEvents fired from the individual components.
My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.
In my case these components are supposed to be links.
#Override
public Component add(Component comp) {
comp = super.add(comp);
if(comp instanceof MouseListener)
comp.removeMouseListener((MouseListener)comp);
comp.addMouseListener(this);
return comp;
}
Then map the component x to a MarqueePanel x and finally the correct component
#Override
public void mouseClicked(MouseEvent e)
{
Component source = (Component)e.getSource();
int x = source.getX() + e.getX();
int y = source.getY();
MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
double x2 = marqueePanel.getWidth();
double x1 = Math.abs(marqueePanel.scrollOffset);
if(x >= x1 && x <= x2)
{
System.out.println("Bang " + x1);
Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);
if(comp instanceof MouseListener)
((MouseListener) componentAt).mouseClicked(e);
System.out.println(componentAt.getName());
}
else
{
return;
}
//System.out.println(x);
}
I'm trying to create a graphical component that allows me to draw one rectangle on a selected image (the rectangle must be drawn using drag-and-drop operations): the purpose of this component is to get the coordinates and the size of the rectangle drawn; the second goal is to provide a component which can be easily integrated in a graphical user interface.
The authors of this example created a subclass of JLabel in order to draw the image, then they added a MouseInputAdapter to the instance of this subclass in order to deal with the drawing of the rectangle.
I was inspired by that example, with the difference that I have created a subclass of JPanel class: I named it FigurePanel class. Then I made some changes in order to provide the following features:
if the image is larger than the instance of FigurePanel, then the scrollers must appear;
if the image is smaller than the instance of FigurePanel, then this image must be placed in the center of the panel;
while the user draws the rectangle, it should not extend beyond the limits of the image.
Here is the source code of FigurePanel class.
package imageselectionproject;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
public class FigurePanel extends JPanel
{
private Image backgroundImage = null;
private Rectangle imageLimits = new Rectangle(0, 0, getWidth(), getHeight());
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public FigurePanel()
{
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
#Override
public Dimension getPreferredSize()
{
return backgroundImage == null ? super.getPreferredSize() : new Dimension(backgroundImage.getWidth(this), backgroundImage.getHeight(this));
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //paints the background and image
if (backgroundImage != null)
{
g.drawImage(backgroundImage, imageLimits.x, imageLimits.y, this);
}
// If currentRect exists, paint a box on top.
if (currentRect != null)
{
// Draw a rectangle on top of the image.
g.setXORMode(Color.white); // Color of line varies
// depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y,
rectToDraw.width - 1, rectToDraw.height - 1);
System.out.println(rectToDraw);
}
}
public void setImage(Image image)
{
int x = 0;
int y = 0;
if (image != null)
{
backgroundImage = image;
// The following instructions are used to center the image on the panel.
/*x = (getSize().width - image.getWidth(this)) / 2;
y = (getSize().height - image.getHeight(this)) / 2;
if (x < 0) x = 0;
if (y < 0) y = 0;*/
}
else
{
backgroundImage = null;
}
currentRect = null;
setSize(getPreferredSize());
imageLimits.setBounds(x, y, getWidth(), getHeight());
System.out.println("imageLimits = " + imageLimits);
repaint();
}
private void updateDrawableRect()
{
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
// Make the width and height positive, if necessary.
if (width < 0)
{
width = 0 - width;
x = x - width + 1;
if (x < 0)
{
width += x;
x = 0;
}
}
if (height < 0)
{
height = 0 - height;
y = y - height + 1;
if (y < 0)
{
height += y;
y = 0;
}
}
// The rectangle should not extend beyond the boundaries of the image.
if (x < imageLimits.x)
{
width -= (imageLimits.x - x);
x = imageLimits.x;
}
else if ((x + width) > imageLimits.x + imageLimits.width)
{
width = imageLimits.x + imageLimits.width - x;
}
if (y < imageLimits.y)
{
height -= (imageLimits.y - y);
y = imageLimits.y;
}
if ((y + height) > imageLimits.y + imageLimits.height)
{
height = imageLimits.y + imageLimits.height - y;
}
// Update rectToDraw after saving old value.
if (rectToDraw != null)
{
previousRectDrawn.setBounds(rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
}
else
{
rectToDraw = new Rectangle(x, y, width, height);
}
}
private class SelectionListener extends MouseInputAdapter
{
#Override
public void mousePressed(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect();
repaint();
}
#Override
public void mouseDragged(MouseEvent e)
{
updateSize(e.getX(), e.getY());
}
#Override
public void mouseReleased(MouseEvent e)
{
updateSize(e.getX(), e.getY());
}
/*
* Update the size of the current rectangle
* and call repaint. Because currentRect
* always has the same origin, translate it
* if the width or height is negative.
*
* For efficiency (though
* that isn't an issue for this program),
* specify the painting region using arguments
* to the repaint() call.
*
*/
void updateSize(int x, int y)
{
currentRect.setSize(x - currentRect.x, y - currentRect.y);
updateDrawableRect();
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y,
totalRepaint.width, totalRepaint.height);
}
}
}
The method setImage is used to set a new image, so it invokes the method repaint to redraw the graphical component. In the code shown above, I disabled (via comments) instructions to center the image: in this way, the component seems to work properly.
Instead, if I enable such instructions, the image is correctly positioned in the center of the panel when it is smaller than the panel itself, however, I encountered the following problem: suppose that it is currently displayed an image larger than the panel, if the new image that I decide to load is smaller than the currently displayed image, then the new image is not displayed; if I try to reload the new image, then it appears.
Why does this problem occur? How to solve it?
I also created the FigurePanelTest class in order to test the FigurePanel class.
package imageselectionproject;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
public class FigurePanelTest extends JFrame
{
public FigurePanelTest()
{
FigurePanel imagePanel = new FigurePanel();
JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(
new ActionListener()
{
#Override
public void actionPerformed(ActionEvent evt)
{
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try
{
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
);
Container container = getContentPane();
container.setLayout(new FlowLayout());
container.add(imageScrollPane);
container.add(imageButton);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Here is the main.
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
For Andrew, a single program:
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.MouseInputAdapter;
public class TestDrawPanel {
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
}
class FigurePanelTest extends JFrame {
public FigurePanelTest() {
final FigurePanel imagePanel = new FigurePanel();
final JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try {
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport()
.setViewPosition(new Point(0, 0));
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
Container container = getContentPane();
container.setLayout(new FlowLayout());
container.add(imageScrollPane);
container.add(imageButton);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class FigurePanel extends JPanel {
private Image backgroundImage = null;
private Rectangle imageLimits = new Rectangle(0, 0, getWidth(), getHeight());
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public FigurePanel() {
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
#Override
public Dimension getPreferredSize() {
return backgroundImage == null ? super.getPreferredSize()
: new Dimension(backgroundImage.getWidth(this),
backgroundImage.getHeight(this));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // paints the background and image
if (backgroundImage != null) {
g.drawImage(backgroundImage, imageLimits.x, imageLimits.y, this);
}
// If currentRect exists, paint a box on top.
if (currentRect != null) {
// Draw a rectangle on top of the image.
g.setXORMode(Color.white); // Color of line varies
// depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y, rectToDraw.width - 1,
rectToDraw.height - 1);
System.out.println(rectToDraw);
}
}
public void setImage(Image image) {
int x = 0;
int y = 0;
if (image != null) {
backgroundImage = image;
// The following instructions are used to center the image on the
// panel.
/*
* x = (getSize().width - image.getWidth(this)) / 2; y =
* (getSize().height - image.getHeight(this)) / 2;
*
* if (x < 0) x = 0; if (y < 0) y = 0;
*/
} else {
backgroundImage = null;
}
currentRect = null;
setSize(getPreferredSize());
imageLimits.setBounds(x, y, getWidth(), getHeight());
System.out.println("imageLimits = " + imageLimits);
repaint();
}
private void updateDrawableRect() {
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
// Make the width and height positive, if necessary.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
// The rectangle should not extend beyond the boundaries of the image.
if (x < imageLimits.x) {
width -= (imageLimits.x - x);
x = imageLimits.x;
} else if ((x + width) > imageLimits.x + imageLimits.width) {
width = imageLimits.x + imageLimits.width - x;
}
if (y < imageLimits.y) {
height -= (imageLimits.y - y);
y = imageLimits.y;
}
if ((y + height) > imageLimits.y + imageLimits.height) {
height = imageLimits.y + imageLimits.height - y;
}
// Update rectToDraw after saving old value.
if (rectToDraw != null) {
previousRectDrawn.setBounds(rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
} else {
rectToDraw = new Rectangle(x, y, width, height);
}
}
private class SelectionListener extends MouseInputAdapter {
#Override
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
updateSize(e.getX(), e.getY());
}
#Override
public void mouseReleased(MouseEvent e) {
updateSize(e.getX(), e.getY());
}
/*
* Update the size of the current rectangle and call repaint. Because
* currentRect always has the same origin, translate it if the width or
* height is negative.
*
* For efficiency (though that isn't an issue for this program), specify
* the painting region using arguments to the repaint() call.
*/
void updateSize(int x, int y) {
currentRect.setSize(x - currentRect.x, y - currentRect.y);
updateDrawableRect();
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y, totalRepaint.width,
totalRepaint.height);
}
}
}
The problem is calculating the x and y in the setImage().It's not calculating the center of panel correctly.I'm saying this by checking the values of x and y after loading small and large images many times
However I tried putting the centering the image inside paintComponent and it worked perfectly
if (backgroundImage != null) {
imageLimits.x = (this.getWidth() - backgroundImage.getWidth(this)) / 2;
imageLimits.y = (this.getHeight() - backgroundImage.getHeight(this)) / 2;
g.drawImage(backgroundImage, imageLimits.x, imageLimits.y, this);
}
I have the following NavigableImagePanel, it is under BSD license and I found it in the web. What I want to do with this panel is as follow:
I want to add a JScrollPane to it in order to show images in their full size and let the users to re-center the image using the small navigation panel. Right now, the panel resize the images to fit them in the current panel size. I want it to load the image in its real size and let users to navigate to different parts of the image using the navigation panel.
Source code for the panel:
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* #author pxt
*
*/
public class NavigableImagePanel extends JPanel {
/**
* <p>Identifies a change to the zoom level.</p>
*/
public static final String ZOOM_LEVEL_CHANGED_PROPERTY = "zoomLevel";
/**
* <p>Identifies a change to the zoom increment.</p>
*/
public static final String ZOOM_INCREMENT_CHANGED_PROPERTY = "zoomIncrement";
/**
* <p>Identifies that the image in the panel has changed.</p>
*/
public static final String IMAGE_CHANGED_PROPERTY = "image";
private static final double SCREEN_NAV_IMAGE_FACTOR = 0.15; // 15% of panel's width
private static final double NAV_IMAGE_FACTOR = 0.3; // 30% of panel's width
private static final double HIGH_QUALITY_RENDERING_SCALE_THRESHOLD = 1.0;
private static final Object INTERPOLATION_TYPE =
RenderingHints.VALUE_INTERPOLATION_BILINEAR;
private double zoomIncrement = 0.2;
private double zoomFactor = 1.0 + zoomIncrement;
private double navZoomFactor = 1.0 + zoomIncrement;
private BufferedImage image;
private BufferedImage navigationImage;
private int navImageWidth;
private int navImageHeight;
private double initialScale = 0.0;
private double scale = 0.0;
private double navScale = 0.0;
private int originX = 0;
private int originY = 0;
private Point mousePosition;
private Dimension previousPanelSize;
private boolean navigationImageEnabled = true;
private boolean highQualityRenderingEnabled = true;
private WheelZoomDevice wheelZoomDevice = null;
private ButtonZoomDevice buttonZoomDevice = null;
/**
* <p>Defines zoom devices.</p>
*/
public static class ZoomDevice {
/**
* <p>Identifies that the panel does not implement zooming,
* but the component using the panel does (programmatic zooming method).</p>
*/
public static final ZoomDevice NONE = new ZoomDevice("none");
/**
* <p>Identifies the left and right mouse buttons as the zooming device.</p>
*/
public static final ZoomDevice MOUSE_BUTTON = new ZoomDevice("mouseButton");
/**
* <p>Identifies the mouse scroll wheel as the zooming device.</p>
*/
public static final ZoomDevice MOUSE_WHEEL = new ZoomDevice("mouseWheel");
private String zoomDevice;
private ZoomDevice(String zoomDevice) {
this.zoomDevice = zoomDevice;
}
public String toString() {
return zoomDevice;
}
}
//This class is required for high precision image coordinates translation.
private class Coords {
public double x;
public double y;
public Coords(double x, double y) {
this.x = x;
this.y = y;
}
public int getIntX() {
return (int)Math.round(x);
}
public int getIntY() {
return (int)Math.round(y);
}
public String toString() {
return "[Coords: x=" + x + ",y=" + y + "]";
}
}
private class WheelZoomDevice implements MouseWheelListener {
public void mouseWheelMoved(MouseWheelEvent e) {
Point p = e.getPoint();
boolean zoomIn = (e.getWheelRotation() < 0);
if (isInNavigationImage(p)) {
if (zoomIn) {
navZoomFactor = 1.0 + zoomIncrement;
} else {
navZoomFactor = 1.0 - zoomIncrement;
}
zoomNavigationImage();
} else if (isInImage(p)) {
if (zoomIn) {
zoomFactor = 1.0 + zoomIncrement;
} else {
zoomFactor = 1.0 - zoomIncrement;
}
zoomImage();
}
}
}
private class ButtonZoomDevice extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
if (SwingUtilities.isRightMouseButton(e)) {
if (isInNavigationImage(p)) {
navZoomFactor = 1.0 - zoomIncrement;
zoomNavigationImage();
} else if (isInImage(p)) {
zoomFactor = 1.0 - zoomIncrement;
zoomImage();
}
} else {
if (isInNavigationImage(p)) {
navZoomFactor = 1.0 + zoomIncrement;
zoomNavigationImage();
} else if (isInImage(p)) {
zoomFactor = 1.0 + zoomIncrement;
zoomImage();
}
}
}
}
/**
* <p>Creates a new navigable image panel with no default image and
* the mouse scroll wheel as the zooming device.</p>
*/
public NavigableImagePanel() {
setOpaque(false);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
if (scale > 0.0) {
if (isFullImageInPanel()) {
centerImage();
} else if (isImageEdgeInPanel()) {
scaleOrigin();
}
if (isNavigationImageEnabled()) {
createNavigationImage();
}
repaint();
}
previousPanelSize = getSize();
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
if (isInNavigationImage(e.getPoint())) {
Point p = e.getPoint();
displayImageAt(p);
}
}
}
public void mouseClicked(MouseEvent e){
if (e.getClickCount() == 2) {
resetImage();
}
}
});
addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)
&& !isInNavigationImage(e.getPoint())) {
Point p = e.getPoint();
moveImage(p);
}
}
public void mouseMoved(MouseEvent e) {
//we need the mouse position so that after zooming
//that position of the image is maintained
mousePosition = e.getPoint();
}
});
setZoomDevice(ZoomDevice.MOUSE_WHEEL);
}
/**
* <p>Creates a new navigable image panel with the specified image
* and the mouse scroll wheel as the zooming device.</p>
*/
public NavigableImagePanel(BufferedImage image) throws IOException {
this();
setImage(image);
}
private void addWheelZoomDevice() {
if (wheelZoomDevice == null) {
wheelZoomDevice = new WheelZoomDevice();
addMouseWheelListener(wheelZoomDevice);
}
}
private void addButtonZoomDevice() {
if (buttonZoomDevice == null) {
buttonZoomDevice = new ButtonZoomDevice();
addMouseListener(buttonZoomDevice);
}
}
private void removeWheelZoomDevice() {
if (wheelZoomDevice != null) {
removeMouseWheelListener(wheelZoomDevice);
wheelZoomDevice = null;
}
}
private void removeButtonZoomDevice() {
if (buttonZoomDevice != null) {
removeMouseListener(buttonZoomDevice);
buttonZoomDevice = null;
}
}
/**
* <p>Sets a new zoom device.</p>
*
* #param newZoomDevice specifies the type of a new zoom device.
*/
public void setZoomDevice(ZoomDevice newZoomDevice) {
if (newZoomDevice == ZoomDevice.NONE) {
removeWheelZoomDevice();
removeButtonZoomDevice();
} else if (newZoomDevice == ZoomDevice.MOUSE_BUTTON) {
removeWheelZoomDevice();
addButtonZoomDevice();
} else if (newZoomDevice == ZoomDevice.MOUSE_WHEEL) {
removeButtonZoomDevice();
addWheelZoomDevice();
}
}
/**
* <p>Gets the current zoom device.</p>
*/
public ZoomDevice getZoomDevice() {
if (buttonZoomDevice != null) {
return ZoomDevice.MOUSE_BUTTON;
} else if (wheelZoomDevice != null) {
return ZoomDevice.MOUSE_WHEEL;
} else {
return ZoomDevice.NONE;
}
}
//Called from paintComponent() when a new image is set.
private void initializeParams() {
double xScale = (double)getWidth() / image.getWidth();
double yScale = (double)getHeight() / image.getHeight();
initialScale = Math.min(xScale, yScale);
scale = initialScale;
//An image is initially centered
centerImage();
if (isNavigationImageEnabled()) {
createNavigationImage();
}
}
//Centers the current image in the panel.
private void centerImage() {
originX = (int)(getWidth() - getScreenImageWidth()) / 2;
originY = (int)(getHeight() - getScreenImageHeight()) / 2;
}
//Creates and renders the navigation image in the upper let corner of the panel.
private void createNavigationImage() {
//We keep the original navigation image larger than initially
//displayed to allow for zooming into it without pixellation effect.
navImageWidth = (int)(getWidth() * NAV_IMAGE_FACTOR);
navImageHeight = navImageWidth * image.getHeight() / image.getWidth();
int scrNavImageWidth = (int)(getWidth() * SCREEN_NAV_IMAGE_FACTOR);
int scrNavImageHeight = scrNavImageWidth * image.getHeight() / image.getWidth();
navScale = (double)scrNavImageWidth / navImageWidth;
navigationImage = new BufferedImage(navImageWidth, navImageHeight,
image.getType());
Graphics g = navigationImage.getGraphics();
g.drawImage(image, 0, 0, navImageWidth, navImageHeight, null);
}
/**
* <p>Sets an image for display in the panel.</p>
*
* #param image an image to be set in the panel
*/
public void setImage(BufferedImage image) {
BufferedImage oldImage = this.image;
this.image = image;
//Reset scale so that initializeParameters() is called in paintComponent()
//for the new image.
scale = 0.0;
firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image);
repaint();
}
/**
* <p>resets an image to the centre of the panel</p>
*
*/
public void resetImage() {
BufferedImage oldImage = this.image;
this.image = image;
//Reset scale so that initializeParameters() is called in paintComponent()
//for the new image.
scale = 0.0;
firePropertyChange(IMAGE_CHANGED_PROPERTY, (Image)oldImage, (Image)image);
repaint();
}
/**
* <p>Tests whether an image uses the standard RGB color space.</p>
*/
public static boolean isStandardRGBImage(BufferedImage bImage) {
return bImage.getColorModel().getColorSpace().isCS_sRGB();
}
//Converts this panel's coordinates into the original image coordinates
private Coords panelToImageCoords(Point p) {
return new Coords((p.x - originX) / scale, (p.y - originY) / scale);
}
//Converts the original image coordinates into this panel's coordinates
private Coords imageToPanelCoords(Coords p) {
return new Coords((p.x * scale) + originX, (p.y * scale) + originY);
}
//Converts the navigation image coordinates into the zoomed image coordinates
private Point navToZoomedImageCoords(Point p) {
int x = p.x * getScreenImageWidth() / getScreenNavImageWidth();
int y = p.y * getScreenImageHeight() / getScreenNavImageHeight();
return new Point(x, y);
}
//The user clicked within the navigation image and this part of the image
//is displayed in the panel.
//The clicked point of the image is centered in the panel.
private void displayImageAt(Point p) {
Point scrImagePoint = navToZoomedImageCoords(p);
originX = -(scrImagePoint.x - getWidth() / 2);
originY = -(scrImagePoint.y - getHeight() / 2);
repaint();
}
//Tests whether a given point in the panel falls within the image boundaries.
private boolean isInImage(Point p) {
Coords coords = panelToImageCoords(p);
int x = coords.getIntX();
int y = coords.getIntY();
return (x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight());
}
//Tests whether a given point in the panel falls within the navigation image
//boundaries.
private boolean isInNavigationImage(Point p) {
return (isNavigationImageEnabled() && p.x < getScreenNavImageWidth()
&& p.y < getScreenNavImageHeight());
}
//Used when the image is resized.
private boolean isImageEdgeInPanel() {
if (previousPanelSize == null) {
return false;
}
return (originX > 0 && originX < previousPanelSize.width
|| originY > 0 && originY < previousPanelSize.height);
}
//Tests whether the image is displayed in its entirety in the panel.
private boolean isFullImageInPanel() {
return (originX >= 0 && (originX + getScreenImageWidth()) < getWidth()
&& originY >= 0 && (originY + getScreenImageHeight()) < getHeight());
}
/**
* <p>Indicates whether the high quality rendering feature is enabled.</p>
*
* #return true if high quality rendering is enabled, false otherwise.
*/
public boolean isHighQualityRenderingEnabled() {
return highQualityRenderingEnabled;
}
/**
* <p>Enables/disables high quality rendering.</p>
*
* #param enabled enables/disables high quality rendering
*/
public void setHighQualityRenderingEnabled(boolean enabled) {
highQualityRenderingEnabled = enabled;
}
//High quality rendering kicks in when when a scaled image is larger
//than the original image. In other words,
//when image decimation stops and interpolation starts.
private boolean isHighQualityRendering() {
return (highQualityRenderingEnabled
&& scale > HIGH_QUALITY_RENDERING_SCALE_THRESHOLD);
}
/**
* <p>Indicates whether navigation image is enabled.<p>
*
* #return true when navigation image is enabled, false otherwise.
*/
public boolean isNavigationImageEnabled() {
return navigationImageEnabled;
}
/**
* <p>Enables/disables navigation with the navigation image.</p>
* <p>Navigation image should be disabled when custom, programmatic navigation
* is implemented.</p>
*
* #param enabled true when navigation image is enabled, false otherwise.
*/
public void setNavigationImageEnabled(boolean enabled) {
navigationImageEnabled = enabled;
repaint();
}
//Used when the panel is resized
private void scaleOrigin() {
originX = originX * getWidth() / previousPanelSize.width;
originY = originY * getHeight() / previousPanelSize.height;
repaint();
}
//Converts the specified zoom level to scale.
private double zoomToScale(double zoom) {
return initialScale * zoom;
}
/**
* <p>Gets the current zoom level.</p>
*
* #return the current zoom level
*/
public double getZoom() {
return scale / initialScale;
}
/**
* <p>Sets the zoom level used to display the image.</p>
* <p>This method is used in programmatic zooming. The zooming center is
* the point of the image closest to the center of the panel.
* After a new zoom level is set the image is repainted.</p>
*
* #param newZoom the zoom level used to display this panel's image.
*/
public void setZoom(double newZoom) {
Point zoomingCenter = new Point(getWidth() / 2, getHeight() / 2);
setZoom(newZoom, zoomingCenter);
}
/**
* <p>Sets the zoom level used to display the image, and the zooming center,
* around which zooming is done.</p>
* <p>This method is used in programmatic zooming.
* After a new zoom level is set the image is repainted.</p>
*
* #param newZoom the zoom level used to display this panel's image.
*/
public void setZoom(double newZoom, Point zoomingCenter) {
Coords imageP = panelToImageCoords(zoomingCenter);
if (imageP.x < 0.0) {
imageP.x = 0.0;
}
if (imageP.y < 0.0) {
imageP.y = 0.0;
}
if (imageP.x >= image.getWidth()) {
imageP.x = image.getWidth() - 1.0;
}
if (imageP.y >= image.getHeight()) {
imageP.y = image.getHeight() - 1.0;
}
Coords correctedP = imageToPanelCoords(imageP);
double oldZoom = getZoom();
scale = zoomToScale(newZoom);
Coords panelP = imageToPanelCoords(imageP);
originX += (correctedP.getIntX() - (int)panelP.x);
originY += (correctedP.getIntY() - (int)panelP.y);
firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom),
new Double(getZoom()));
repaint();
}
/**
* <p>Gets the current zoom increment.</p>
*
* #return the current zoom increment
*/
public double getZoomIncrement() {
return zoomIncrement;
}
/**
* <p>Sets a new zoom increment value.</p>
*
* #param newZoomIncrement new zoom increment value
*/
public void setZoomIncrement(double newZoomIncrement) {
double oldZoomIncrement = zoomIncrement;
zoomIncrement = newZoomIncrement;
firePropertyChange(ZOOM_INCREMENT_CHANGED_PROPERTY,
new Double(oldZoomIncrement), new Double(zoomIncrement));
}
//Zooms an image in the panel by repainting it at the new zoom level.
//The current mouse position is the zooming center.
private void zoomImage() {
Coords imageP = panelToImageCoords(mousePosition);
double oldZoom = getZoom();
scale *= zoomFactor;
Coords panelP = imageToPanelCoords(imageP);
originX += (mousePosition.x - (int)panelP.x);
originY += (mousePosition.y - (int)panelP.y);
firePropertyChange(ZOOM_LEVEL_CHANGED_PROPERTY, new Double(oldZoom),
new Double(getZoom()));
repaint();
}
//Zooms the navigation image
private void zoomNavigationImage() {
navScale *= navZoomFactor;
repaint();
}
/**
* <p>Gets the image origin.</p>
* <p>Image origin is defined as the upper, left corner of the image in
* the panel's coordinate system.</p>
* #return the point of the upper, left corner of the image in the panel's coordinates
* system.
*/
public Point getImageOrigin() {
return new Point(originX, originY);
}
/**
* <p>Sets the image origin.</p>
* <p>Image origin is defined as the upper, left corner of the image in
* the panel's coordinate system. After a new origin is set, the image is repainted.
* This method is used for programmatic image navigation.</p>
I've used this component before myself, but never in exactly the way you describe. There is an existing function in the code, setZoom(), that can be used to programatically set the zoom level and repaint the image. I would recommend that you experiment with that function and the zoom level.
I have a JFrame with BorderLayout as the layout manager.
In the south border, I have a JPanel, I want this JPanel's size to be adjustable by the user, i.e. the user can click on the edge of the border and drag it up to make it larger.
Is there any way you know that I can do this?
In order to make panels in a frame individually resizable you need to add them onto a JSplitPane.
Instead of putting it in the South portion of the Frame, put the JSplitPane in the Center. The split pane will make the bottom panel in the split seem like it is in the South, and the top panel in the split will be in the Center of the frame.
Make sure you set the orientation of the two panels with setOrientation(JSplitPane.VERTICAL_SPLIT ).
Then, you can resize the panels that are in the pane.
I think you meant to say JPanel. You can add a custom mouseListener and handle mouse clicks, drags and mouse releases and then resize the panel programmaticaly.
This will demonstrate this. Note that the jframe does NOT resize automatically with the JPanel. To make the effect more visible i painted the panel red and added a beveled border :
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.BevelBorder;
#SuppressWarnings("serial")
public class ResizablePanel extends JPanel {
private boolean drag = false;
private Point dragLocation = new Point();
public ResizablePanel() {
setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
setPreferredSize(new Dimension(500, 500));
final JFrame f = new JFrame("Test");
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
drag = true;
dragLocation = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e) {
drag = false;
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
if (drag) {
if (dragLocation.getX()> getWidth()-10 && dragLocation.getY()>getHeight()-10) {
System.err.println("in");
setSize((int)(getWidth()+(e.getPoint().getX()-dragLocation.getX())),
(int)(getHeight()+(e.getPoint().getY()-dragLocation.getY())));
dragLocation = e.getPoint();
}
}
}
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(this,BorderLayout.CENTER);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new ResizablePanel();
}
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
I made a class for that if you want to take a look. It isn`t finished yet.
package projetoSplitPainel;
import java.awt.Component;
import java.util.ArrayList;
import javax.swing.JSplitPane;
/**
* This Components is based on the JSplitPane. JSplitPane is used to divide two
* (and only two) Components. This class intend to manipulate the JSplitPane in
* a way that can be placed as many Component as wanted.
*
* #author Bode
*
*/
public class JSplitPaneMultiTabs extends JSplitPane {
private ArrayList<JSplitPane> ecanpsulationList = new ArrayList<JSplitPane>();
private int numberOfComponents = 1;
private int sizeOfDivision = 6;
/**
* Builds the Pane
*/
public JSplitPaneMultiTabs() {
super();
this.setLeftComponent(null);
this.setBorder(null);
ecanpsulationList.add(this);
setAllBorders(sizeOfDivision);
}
/**
*
* #param comp - adds a Component to the Pane
*/
public void addComponent(Component comp) {
JSplitPane capsule = new JSplitPane();
capsule.setRightComponent(null);
capsule.setLeftComponent(comp);
capsule.setDividerSize(sizeOfDivision);
capsule.setBorder(null);
ecanpsulationList.get(numberOfComponents - 1).setRightComponent(capsule);
ecanpsulationList.add(capsule);
numberOfComponents++;
this.fixWeights();
}
/**
*
* #param orientation
* JSplitPane.HORIZONTAL_SPLIT - sets the orientation of the
* Components to horizontal alignment
* #param orientation
* JSplitPane.VERTICAL_SPLIT - sets the orientation of the
* Components to vertical alignment
*/
public void setAlignment(int orientation) {
for (int i = 0; i < numberOfComponents; i++) {
ecanpsulationList.get(i).setOrientation(orientation);
}
}
/**
*
* #param newSize - resizes the borders of the all the Components of the Screen
*/
public void setAllBorders(int newSize) {
this.setDividerSize(newSize);
for (int i = 0; i < numberOfComponents; i++) {
ecanpsulationList.get(i).setDividerSize(newSize);
}
}
/**
* each Component added needs to be readapteded to the screen
*/
private void fixWeights() {
ecanpsulationList.get(0).setResizeWeight(1.0);
for (int i = 1; i < numberOfComponents; i++) {
double resize = (double) 1 / (double) (i + 1);
ecanpsulationList.get(numberOfComponents - i - 1).setResizeWeight(
resize);
}
ecanpsulationList.get(numberOfComponents - 1).setResizeWeight(0.0);
}
}
So i Made a Class which does mostly what the OP wanted. Basically it is a Pannel which can be resized when clicking at the border. It also (tries) to resize all the components contained inside it to fit the new size. It also tries to detect collision between other components in the parent frame to prevent overlapping as much as possible.
Anyway here you go:
import java.awt.BasicStroke;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JDesktopPane;
#SuppressWarnings("serial")
public class ResizablePanel extends JDesktopPane {
private boolean drag = false;
private boolean top =false;
private boolean bottom =false;
private boolean left=false;
private boolean right =false;
private Point dragLocation = new Point();
public ResizablePanel() {
setMinimumSize(new Dimension(200, 200));
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
evaluateMousePress(e.getPoint());
}
#Override
public void mouseReleased(MouseEvent e) {
drag = false;
setCursor(Cursor.getDefaultCursor());
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
evaluateMouseDrag(e.getPoint());
}
#Override
public void mouseMoved(MouseEvent e) {
evaluateMouseHover(e.getPoint());
}
});
}
#Override
public void setBounds(int x, int y, int width, int height) {
Rectangle oldBounds=getBounds();
Rectangle newBounds = new Rectangle(x, y, width, height);
super.setBounds(x, y, width, height);
resizeSingleComponent(this,oldBounds, newBounds);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Stroke stroke = g2.getStroke();
g2.setStroke(new BasicStroke(5));
g.drawRect(0, 0, getBounds().width, getBounds().height);
g2.setStroke(stroke);
}
private void resizeSingleComponent(Component component , Rectangle oldBounds , Rectangle newBounds) {
if(component instanceof Container) {
if(!component.equals(this)) {
Rectangle componentBounds = component.getBounds();
double x = getNew(componentBounds.getX(), oldBounds.getWidth(), newBounds.getWidth());
double y = getNew(componentBounds.getY(), oldBounds.getHeight(), newBounds.getHeight());
double width = getNew(componentBounds.getWidth(), oldBounds.getWidth(), newBounds.getWidth());
double height = getNew(componentBounds.getHeight(), oldBounds.getHeight(), newBounds.getHeight());
componentBounds.width = (int) Math.round(width);
componentBounds.height = (int) Math.round(height);
componentBounds.x = (int) Math.round(x);
componentBounds.y = (int) Math.round(y);
component.setBounds(componentBounds);
}
for(Component child:((Container) component).getComponents())
resizeSingleComponent(child, oldBounds, newBounds);
}
else {
Rectangle componentBounds = component.getBounds();
double x = getNew(componentBounds.getX(), oldBounds.getWidth(), newBounds.getWidth());
double y = getNew(componentBounds.getY(), oldBounds.getHeight(), newBounds.getHeight());
double width = getNew(componentBounds.getWidth(), oldBounds.getWidth(), newBounds.getWidth());
double height = getNew(componentBounds.getHeight(), oldBounds.getHeight(), newBounds.getHeight());
componentBounds.width = (int) Math.round(width);
componentBounds.height = (int) Math.round(height);
componentBounds.x = (int) Math.round(x);
componentBounds.y = (int) Math.round(y);
component.setBounds(componentBounds);
}
}
private void evaluateMouseDrag(Point point) {
if (drag) {
if (top || left || right || bottom) {
int heightIncrement=0;
int widthIncrement=0;
int posX = getBounds().x;
int posY = getBounds().y;
if(top || bottom)
heightIncrement = (int) (point.getY()-dragLocation.getY());
if(left || right)
widthIncrement = (int) (point.getX()-dragLocation.getX());
if(top) {
posY = posY+heightIncrement;
heightIncrement=-heightIncrement;
}
if(left) {
posX=posX+widthIncrement;
widthIncrement=-widthIncrement;
}
if(right)
dragLocation.x=(int) point.getX();
if(bottom)
dragLocation.y=(int) point.getY();
Rectangle bounds = new Rectangle(posX, posY, getWidth()+ widthIncrement,getHeight()+ heightIncrement);
justifyCollision(bounds,point);
}
}
}
private void evaluateMousePress(Point point) {
drag = true;
dragLocation = point;
top=dragLocation.getY() <=5;
left=dragLocation.getX() <=5;
bottom = dragLocation.getY() >=getHeight()-5;
right = dragLocation.getX() >=getWidth()-5;
setResizeCursor(point);
}
private void evaluateMouseHover(Point point) {
if(!drag) {
top=point.getY() <=5;
left=point.getX() <=5;
bottom = point.getY() >=getHeight()-5;
right = point.getX() >=getWidth()-5;
setResizeCursor(point);
}
}
private void justifyCollision(Rectangle bounds, Point point) {
//justify undersizing
if(bounds.width<getMinimumSize().width) {
if(left)
bounds.x= getBounds().x;
bounds.width=getWidth();
}
if(bounds.height<getMinimumSize().height) {
if(top)
bounds.y=getBounds().y;
bounds.height=getHeight();
}
Container parent = getParent();
//justify parent container bounds
if(bounds.x<0 ||bounds.width+bounds.x>parent.getWidth()) {
bounds.width = getWidth();
if(right &&!(bounds.width+bounds.x>parent.getWidth()))
dragLocation.x=(int) point.getX();
if(bounds.x<0)
bounds.x=0;
}
if(bounds.y<0 ||bounds.height+bounds.y>parent.getHeight()) {
bounds.height = getHeight();
if(bottom &&!(bounds.height+bounds.y>parent.getHeight()))
dragLocation.y=(int) point.getY();
if(bounds.y<0)
bounds.y=0;
}
//justify any other component in it's way
for(Component c:parent.getComponents()) {
if(!c.equals(this) &&c.getBounds().intersects(bounds)) {
evaluateOverlaps(bounds, c,point);
}
}
setBounds(bounds);
}
private Rectangle evaluateOverlaps(Rectangle bounds , Component component,Point point) {
if(bounds.intersects(component.getBounds())) {
if(component instanceof ResizablePanel) {
ResizablePanel panel=(ResizablePanel) component;
Rectangle panelBound=panel.getBounds();
if(top || bottom) {
panelBound.height = panelBound.height - bounds.intersection(panelBound).height;
if(panelBound.height<panel.getMinimumSize().height) {
panelBound.height=panel.getMinimumSize().height;
bounds.height = getHeight();
if(top) {
bounds.y = component.getBounds().y+component.getHeight();
}
}
else if(bottom) {
panelBound.y = panelBound.y + bounds.intersection(panelBound).height;
}
}
else if(left || right) {
panelBound.width = panelBound.width - bounds.intersection(panelBound).width;
if(panelBound.width<panel.getMinimumSize().width) {
panelBound.width=panel.getMinimumSize().width;
bounds.width = getWidth();
if(left) {
bounds.x = component.getBounds().x+component.getWidth();
}
}
else if(right) {
panelBound.x = panelBound.x + bounds.intersection(panelBound).width;
}
}
panel.setBounds(panelBound);
return bounds;
}
if(left || right) {
bounds.width = getWidth();
if(left) {
bounds.x = component.getBounds().x+component.getWidth();
}
}
if(top || bottom) {
bounds.height = getHeight();
if(top) {
bounds.y = component.getBounds().y+component.getHeight();
}
}
}
return bounds;
}
private void setResizeCursor(Point point) {
if(top) {
if(left)
setCursor(new Cursor(Cursor.NW_RESIZE_CURSOR));
else if(right)
setCursor(new Cursor(Cursor.NE_RESIZE_CURSOR));
else
setCursor(new Cursor(Cursor.N_RESIZE_CURSOR));
}
else if(bottom) {
if(left)
setCursor(new Cursor(Cursor.SW_RESIZE_CURSOR));
else if(right)
setCursor(new Cursor(Cursor.SE_RESIZE_CURSOR));
else
setCursor(new Cursor(Cursor.S_RESIZE_CURSOR));
}
else if (left)
setCursor(new Cursor(Cursor.W_RESIZE_CURSOR));
else if (right)
setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
else
setCursor(Cursor.getDefaultCursor());
}
private long getNew(double size,double oldMax,double newMax) {
double value = ((size/oldMax)*newMax);
return Math.round(value);
}
}
You might have to specify JFrame.setResizeable = true; on both the Parent JFrame(the one with the border layout) and the child JFrame.
You also might want to use a JPanel in the south border.