Friday, April 12, 2013

Resizable components in Java Swing

Resizable components in Java Swing

In this part of the Java Swing tutorial, we will create a resizable component.

Resizable component

Resizable components are most often used when creating charts, diagrams and similar. The most common resizable component is a chart in a spreadsheet application. For example, when we create a chart in a OpenOffice application. The chart can be moved over the grid widget of the application and resized.
In order to create a component that can be freely dragged over a panel, we need a panel with absolute positioning enabled. We must not use a layout manager. In our example, we will create a component (a JPanel) that we can freely move over a parent window and resize.
In order to distinguish which component has a focus, we draw 8 small rectangles on the border of our resizable component. This way we know, that the component has focus. The rectangles serve as a dragging points, where we can draw the component and start resizing. I have learnt to use resizable components from this blog.
package resizablecomponent;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;


/* ResizableComponent.java */

public class ResizableComponent extends JFrame {

private JPanel panel = new JPanel(null);
private Resizable resizer;


public ResizableComponent() {

add(panel);

JPanel area = new JPanel();
area.setBackground(Color.white);
resizer = new Resizable(area);
resizer.setBounds(50, 50, 200, 150);
panel.add(resizer);


setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(new Dimension(350, 300));
setTitle("Resizable Component");
setLocationRelativeTo(null);

addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {

requestFocus();
resizer.repaint();
}
});
}

public static void main(String[] args) {
ResizableComponent rc = new ResizableComponent();
rc.setVisible(true);
}
}
The ResizableComponent sets up the panel and the component.
private JPanel panel = new JPanel(null);
We have already mentioned, that we cannot use any layout manager. We must use absolute positioning for resizable component. By providing null to the constructor, we create a panel with absolute positioning.
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {

requestFocus();
resizer.repaint();
}
});
If we press on the parent panel, e.g outside the resizable component, we grab focus and repaint the component. The rectangles over he border will disappear.
package resizablecomponent;

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;

import javax.swing.SwingConstants;
import javax.swing.border.Border;

// ResizableBorder.java

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;
}

public Insets getBorderInsets(Component component) {
return new Insets(dist, dist, dist, dist);
}

public boolean isBorderOpaque() {
return false;
}

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 i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(x, y, w, h, locations[i]);
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) {
switch (location) {
case SwingConstants.NORTH:
return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);
case SwingConstants.SOUTH:
return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist,
dist);
case SwingConstants.WEST:
return new Rectangle(x, y + h / 2 - dist / 2, dist, dist);
case SwingConstants.EAST:
return new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist,
dist);
case SwingConstants.NORTH_WEST:
return new Rectangle(x, y, dist, dist);
case SwingConstants.NORTH_EAST:
return new Rectangle(x + w - dist, y, dist, dist);
case SwingConstants.SOUTH_WEST:
return new Rectangle(x, y + h - dist, dist, dist);
case SwingConstants.SOUTH_EAST:
return new Rectangle(x + w - dist, y + h - dist, dist, dist);
}
return null;
}

public int getCursor(MouseEvent me) {
Component c = me.getComponent();
int w = c.getWidth();
int h = c.getHeight();

for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(0, 0, w, h, locations[i]);
if (rect.contains(me.getPoint()))
return cursors[i];
}

return Cursor.MOVE_CURSOR;
}
}
The 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 we will draw rectangles. These locations are grabbing points, where we can 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 i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(x, y, w, h, locations[i]);
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 has currently focus.
Finally, the getRectangle() method gets the coordinates of the rectangles and the getCursor() methods gets the cursor type for the grab point in question.
package resizablecomponent;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;

// Resizable.java

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) {
((JComponent)getParent()).revalidate();
}
}

MouseInputListener resizeListener = new MouseInputAdapter() {
public void mouseMoved(MouseEvent me) {
if (hasFocus()) {
ResizableBorder border = (ResizableBorder)getBorder();
setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
}
}

public void mouseExited(MouseEvent mouseEvent) {
setCursor(Cursor.getDefaultCursor());
}

private int cursor;
private Point startPos = null;

public void mousePressed(MouseEvent me) {
ResizableBorder border = (ResizableBorder)getBorder();
cursor = border.getCursor(me);
startPos = me.getPoint();
requestFocus();
repaint();
}

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();
}
break;

case Cursor.S_RESIZE_CURSOR:
if (!(h + dy < 50)) {
setBounds(x, y, w, h + dy);
startPos = me.getPoint();
resize();
}
break;

case Cursor.W_RESIZE_CURSOR:
if (!(w - dx < 50)) {
setBounds(x + dx, y, w - dx, h);
resize();
}
break;

case Cursor.E_RESIZE_CURSOR:
if (!(w + dx < 50)) {
setBounds(x, y, w + dx, h);
startPos = me.getPoint();
resize();
}
break;

case Cursor.NW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h - dy < 50)) {
setBounds(x + dx, y + dy, w - dx, h - dy);
resize();
}
break;

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();
}
break;

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();
}
break;

case Cursor.SE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h + dy < 50)) {
setBounds(x, y, w + dx, h + dy);
startPos = me.getPoint();
resize();
}
break;

case Cursor.MOVE_CURSOR:
Rectangle bounds = getBounds();
bounds.translate(dx, dy);
setBounds(bounds);
resize();
}


setCursor(Cursor.getPredefinedCursor(cursor));
}
}

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) {
((JComponent)getParent()).revalidate();
}
}
The resize() method is called, after we have resized the component. The revalidate() method will cause the component to be redrawn.
MouseInputListener resizeListener = new MouseInputAdapter() {
public void mouseMoved(MouseEvent me) {
if (hasFocus()) {
ResizableBorder border = (ResizableBorder)getBorder();
setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
}
}
We change the cursor type, when we hover the cursor over the grip points. The cursor type changes only if the component has focus.
public void mousePressed(MouseEvent me) {
ResizableBorder border = (ResizableBorder)getBorder();
cursor = border.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, y coordinates of the cursor, 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();
}
break;
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.
Resizable component
Figure: Resizable component
In this part of the Java Swing tutorial, we have created a resizable component.

No comments:

Post a Comment