Resizable component in Java Swing
last modified January 10, 2023
In this part of the Java Swing tutorial, we will create a resizable component.
Resizable component
Resizable components are often used when creating charts or diagrams. A common resizable component is a chart in a spreadsheet application. The chart can be moved over a table widget of the application and resized.
In order to create a component that can be freely dragged over a panel, we use a panel with absolute positioning enabled. In our example, we will create a component that we can freely move over a parent window and resize.
Eight small rectangles are drawn on the border of our resizable component when it has the focus. The rectangles serve as dragging points, where we can draw the component and start resizing.
package com.zetcode; import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.Color; import java.awt.EventQueue; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; public class ResizableComponentEx extends JFrame { private Resizable res; public ResizableComponentEx() { initUI(); } private void initUI() { var pnl = new JPanel(null); add(pnl); var area = new JPanel(); area.setBackground(Color.white); res = new Resizable(area); res.setBounds(50, 50, 200, 150); pnl.add(res); addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { requestFocus(); res.repaint(); } }); setSize(550, 400); setTitle("Resizable component"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(() -> { var ex = new ResizableComponentEx(); ex.setVisible(true); }); } }
ResizableComponentEx
sets up the panel and the component.
var pnl = new JPanel(null);
We use absolute positioning for a resizable component. By providing null
to the constructor of a JPanel
, we create a panel with
absolute positioning.
addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { requestFocus(); res.repaint(); } });
If we press on the parent panel, i.e. outside the resizable component, we grab focus and repaint the component. The rectangles over the border will disappear.
package com.zetcode; import javax.swing.SwingConstants; import javax.swing.border.Border; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.MouseEvent; public class ResizableBorder implements Border { private int dist = 8; int[] locations = { SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST, SwingConstants.EAST, SwingConstants.NORTH_WEST, SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST, SwingConstants.SOUTH_EAST }; int[] cursors = { Cursor.N_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR, Cursor.E_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR, Cursor.SW_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR }; public ResizableBorder(int dist) { this.dist = dist; } @Override public Insets getBorderInsets(Component component) { return new Insets(dist, dist, dist, dist); } @Override public boolean isBorderOpaque() { return false; } @Override public void paintBorder(Component component, Graphics g, int x, int y, int w, int h) { g.setColor(Color.black); g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist); if (component.hasFocus()) { for (int location : locations) { var rect = getRectangle(x, y, w, h, location); if (rect != null) { g.setColor(Color.WHITE); g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1); g.setColor(Color.BLACK); g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1); } } } } private Rectangle getRectangle(int x, int y, int w, int h, int location) { return switch (location) { case SwingConstants.NORTH -> new Rectangle(x + w / 2 - dist / 2, y, dist, dist); case SwingConstants.SOUTH -> new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist, dist); case SwingConstants.WEST -> new Rectangle(x, y + h / 2 - dist / 2, dist, dist); case SwingConstants.EAST -> new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist, dist); case SwingConstants.NORTH_WEST -> new Rectangle(x, y, dist, dist); case SwingConstants.NORTH_EAST -> new Rectangle(x + w - dist, y, dist, dist); case SwingConstants.SOUTH_WEST -> new Rectangle(x, y + h - dist, dist, dist); case SwingConstants.SOUTH_EAST -> new Rectangle(x + w - dist, y + h - dist, dist, dist); default -> new Rectangle();; }; } public int getCursor(MouseEvent me) { var c = me.getComponent(); int w = c.getWidth(); int h = c.getHeight(); for (int i = 0; i < locations.length; i++) { var rect = getRectangle(0, 0, w, h, locations[i]); if (rect != null && rect.contains(me.getPoint())) { return cursors[i]; } } return Cursor.MOVE_CURSOR; } }
ResizableBorder
is responsible for drawing
the border of the component and determining the type of the cursor to use.
int locations[] = { SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST, SwingConstants.EAST, SwingConstants.NORTH_WEST, SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST, SwingConstants.SOUTH_EAST };
These are locations where the rectangles are drawn. These locations are also grabbing points, where it is possible to grab the component and resize it.
g.setColor(Color.black); g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);
In the paintBorder()
method, we draw the border
of the resizable component.
The upper code draws the outer border of the component.
if (component.hasFocus()) { for (int location : locations) { var rect = getRectangle(x, y, w, h, location); if (rect != null) { g.setColor(Color.WHITE); g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1); g.setColor(Color.BLACK); g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1); } } }
The eight rectangles are drawn only in case that the resizable component currently has focus.
private Rectangle getRectangle(int x, int y, int w, int h, int location) { return switch (location) { case SwingConstants.NORTH -> new Rectangle(x + w / 2 - dist / 2, y, dist, dist); case SwingConstants.SOUTH -> new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist, dist); case SwingConstants.WEST -> new Rectangle(x, y + h / 2 - dist / 2, dist, dist); ... }
The getRectangle()
method returns the coordinates of
a rectangle.
public int getCursor(MouseEvent me) { var c = me.getComponent(); int w = c.getWidth(); int h = c.getHeight(); for (int i = 0; i < locations.length; i++) { var rect = getRectangle(0, 0, w, h, locations[i]); if (rect != null && rect.contains(me.getPoint())) { return cursors[i]; } } return Cursor.MOVE_CURSOR; }
The getCursor()
method gets the cursor type for the grab
point in question.
package com.zetcode; import javax.swing.JComponent; import javax.swing.event.MouseInputAdapter; import javax.swing.event.MouseInputListener; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Cursor; import java.awt.Point; import java.awt.event.MouseEvent; public class Resizable extends JComponent { public Resizable(Component comp) { this(comp, new ResizableBorder(8)); } public Resizable(Component comp, ResizableBorder border) { setLayout(new BorderLayout()); add(comp); addMouseListener(resizeListener); addMouseMotionListener(resizeListener); setBorder(border); } private void resize() { if (getParent() != null) { getParent().revalidate(); } } MouseInputListener resizeListener = new MouseInputAdapter() { @Override public void mouseMoved(MouseEvent me) { if (hasFocus()) { var resizableBorder = (ResizableBorder) getBorder(); setCursor(Cursor.getPredefinedCursor(resizableBorder.getCursor(me))); } } @Override public void mouseExited(MouseEvent mouseEvent) { setCursor(Cursor.getDefaultCursor()); } private int cursor; private Point startPos = null; @Override public void mousePressed(MouseEvent me) { var resizableBorder = (ResizableBorder) getBorder(); cursor = resizableBorder.getCursor(me); startPos = me.getPoint(); requestFocus(); repaint(); } @Override public void mouseDragged(MouseEvent me) { if (startPos != null) { int x = getX(); int y = getY(); int w = getWidth(); int h = getHeight(); int dx = me.getX() - startPos.x; int dy = me.getY() - startPos.y; switch (cursor) { case Cursor.N_RESIZE_CURSOR -> { if (!(h - dy < 50)) { setBounds(x, y + dy, w, h - dy); resize(); } } case Cursor.S_RESIZE_CURSOR -> { if (!(h + dy < 50)) { setBounds(x, y, w, h + dy); startPos = me.getPoint(); resize(); } } case Cursor.W_RESIZE_CURSOR -> { if (!(w - dx < 50)) { setBounds(x + dx, y, w - dx, h); resize(); } } case Cursor.E_RESIZE_CURSOR -> { if (!(w + dx < 50)) { setBounds(x, y, w + dx, h); startPos = me.getPoint(); resize(); } } case Cursor.NW_RESIZE_CURSOR -> { if (!(w - dx < 50) && !(h - dy < 50)) { setBounds(x + dx, y + dy, w - dx, h - dy); resize(); } } case Cursor.NE_RESIZE_CURSOR -> { if (!(w + dx < 50) && !(h - dy < 50)) { setBounds(x, y + dy, w + dx, h - dy); startPos = new Point(me.getX(), startPos.y); resize(); } } case Cursor.SW_RESIZE_CURSOR -> { if (!(w - dx < 50) && !(h + dy < 50)) { setBounds(x + dx, y, w - dx, h + dy); startPos = new Point(startPos.x, me.getY()); resize(); } } case Cursor.SE_RESIZE_CURSOR -> { if (!(w + dx < 50) && !(h + dy < 50)) { setBounds(x, y, w + dx, h + dy); startPos = me.getPoint(); resize(); } } case Cursor.MOVE_CURSOR -> { var bounds = getBounds(); bounds.translate(dx, dy); setBounds(bounds); resize(); } } setCursor(Cursor.getPredefinedCursor(cursor)); } } @Override public void mouseReleased(MouseEvent mouseEvent) { startPos = null; } }; }
The Resizable
class represents the component that is
being resized and moved on the window.
private void resize() { if (getParent() != null) { getParent().revalidate(); } }
The resize()
method is called after we have resized
the component. The revalidate()
method causes the
component to be redrawn.
MouseInputListener resizeListener = new MouseInputAdapter() { @Override public void mouseMoved(MouseEvent me) { if (hasFocus()) { var border = (ResizableBorder) getBorder(); setCursor(Cursor.getPredefinedCursor(border.getCursor(me))); } } ... }
We change the cursor type, when we hover the cursor over the grab points. The cursor type changes only if the component has focus.
@Override public void mousePressed(MouseEvent me) { var resizableBorder = (ResizableBorder) getBorder(); cursor = resizableBorder.getCursor(me); startPos = me.getPoint(); requestFocus(); repaint(); }
If we click on the resizable component, we change the cursor, get the starting point of dragging, give focus to the component, and redraw it.
int x = getX(); int y = getY(); int w = getWidth(); int h = getHeight(); int dx = me.getX() - startPos.x; int dy = me.getY() - startPos.y;
In the mouseDragged()
method, we determine the x and y coordinates of
the cursor and width and height of the component. We calculate the distances that
we make during the mouse drag event.
case Cursor.N_RESIZE_CURSOR -> { if (!(h - dy < 50)) { setBounds(x, y + dy, w, h - dy); resize(); } }
For all resizing we ensure that the component is not smaller than 50 px.
Otherwise, we could make it so small that we would eventually hide the
component. The setBounds
method relocates and resizes the
component.
In this part of the Java Swing tutorial, we have created a resizable component.