Java Swing 中的可调整大小的组件
最后修改于 2023 年 1 月 10 日
在 Java Swing 教程的这一部分,我们将创建一个可调整大小的组件。
可调整大小的组件
创建图表或图表时,通常使用可调整大小的组件。一个常见的可调整大小的组件是电子表格应用程序中的图表。该图表可以在应用程序的表格小部件上移动和调整大小。
为了创建一个可以在面板上自由拖动的组件,我们使用一个启用了绝对定位的面板。在我们的示例中,我们将创建一个可以在父窗口上自由移动和调整大小的组件。
当可调整大小的组件具有焦点时,会在其边框上绘制八个小矩形。这些矩形用作拖动点,我们可以在其中绘制组件并开始调整大小。
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
设置面板和组件。
var pnl = new JPanel(null);
我们对可调整大小的组件使用绝对定位。通过向 JPanel
的构造函数提供 null
,我们创建一个具有绝对定位的面板。
addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { requestFocus(); res.repaint(); } });
如果我们按下了父面板(即可调整大小的组件之外),我们会获得焦点并重新绘制组件。边框上的矩形将消失。
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
负责绘制组件的边框并确定要使用的光标类型。
int locations[] = { SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST, SwingConstants.EAST, SwingConstants.NORTH_WEST, SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST, SwingConstants.SOUTH_EAST };
这些是绘制矩形的位置。这些位置也是抓取点,可以在其中抓取组件并调整其大小。
g.setColor(Color.black); g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);
在 paintBorder()
方法中,我们绘制可调整大小的组件的边框。上面的代码绘制了组件的外边框。
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); ... }
getRectangle()
方法返回矩形的坐标。
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; }
getCursor()
方法获取所讨论的抓取点的光标类型。
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; } }; }
Resizable
类表示在窗口上调整大小和移动的组件。
private void resize() { if (getParent() != null) { getParent().revalidate(); } }
在调整组件大小后,将调用 resize()
方法。revalidate()
方法导致重新绘制组件。
MouseInputListener resizeListener = new MouseInputAdapter() { @Override public void mouseMoved(MouseEvent me) { if (hasFocus()) { var border = (ResizableBorder) getBorder(); setCursor(Cursor.getPredefinedCursor(border.getCursor(me))); } } ... }
当我们将光标悬停在抓取点上时,我们会更改光标类型。仅当组件具有焦点时,光标类型才会更改。
@Override public void mousePressed(MouseEvent me) { var resizableBorder = (ResizableBorder) getBorder(); cursor = resizableBorder.getCursor(me); startPos = me.getPoint(); requestFocus(); repaint(); }
如果我们单击可调整大小的组件,我们会更改光标,获取拖动的起始点,将焦点赋予组件,并重新绘制它。
int x = getX(); int y = getY(); int w = getWidth(); int h = getHeight(); int dx = me.getX() - startPos.x; int dy = me.getY() - startPos.y;
在 mouseDragged()
方法中,我们确定光标的 x 和 y 坐标以及组件的宽度和高度。我们计算在鼠标拖动事件期间产生的距离。
case Cursor.N_RESIZE_CURSOR -> { if (!(h - dy < 50)) { setBounds(x, y + dy, w, h - dy); resize(); } }
对于所有调整大小,我们确保组件不小于 50 像素。否则,我们可以使其变得很小,最终会隐藏组件。setBounds
方法重新定位和调整组件的大小。

在 Java Swing 教程的这一部分,我们创建了一个可调整大小的组件。