原文: http://zetcode.com/tutorials/javaswingtutorial/resizablecomponent/

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

可调整大小的组件

在创建图表时,经常使用可调整大小的组件。 常见的可调整大小的组件是电子表格应用中的图表。 可以将图表移到应用的表格小部件上并调整大小。

Tweet

为了创建可以在面板上自由拖动的组件,我们使用启用了绝对定位的面板。 在我们的示例中,我们将创建一个可以在父窗口上自由移动并调整大小的组件。

当可调整大小的组件具有焦点时,将在其可调整大小的组件的边框上绘制八个小矩形。 矩形用作拖动点,我们可以在其中绘制组件并开始调整大小。

ResizableComponentEx.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.EventQueue;
  4. import java.awt.event.MouseAdapter;
  5. import java.awt.event.MouseEvent;
  6. import javax.swing.JFrame;
  7. import javax.swing.JPanel;
  8. public class ResizableComponentEx extends JFrame {
  9. private Resizable res;
  10. public ResizableComponentEx() {
  11. initUI();
  12. }
  13. private void initUI() {
  14. var pnl = new JPanel(null);
  15. add(pnl);
  16. var area = new JPanel();
  17. area.setBackground(Color.white);
  18. res = new Resizable(area);
  19. res.setBounds(50, 50, 200, 150);
  20. pnl.add(res);
  21. addMouseListener(new MouseAdapter() {
  22. @Override
  23. public void mousePressed(MouseEvent me) {
  24. requestFocus();
  25. res.repaint();
  26. }
  27. });
  28. setSize(350, 300);
  29. setTitle("Resizable component");
  30. setLocationRelativeTo(null);
  31. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  32. }
  33. public static void main(String[] args) {
  34. EventQueue.invokeLater(() -> {
  35. var ex = new ResizableComponentEx();
  36. ex.setVisible(true);
  37. });
  38. }
  39. }

ResizableComponentEx设置面板和组件。

  1. var pnl = new JPanel(null);

我们对可调整大小的组件使用绝对定位。 通过将null提供给JPanel的构造器,我们创建了具有绝对定位的面板。

  1. addMouseListener(new MouseAdapter() {
  2. @Override
  3. public void mousePressed(MouseEvent me) {
  4. requestFocus();
  5. res.repaint();
  6. }
  7. });

如果我们在父面板上(即在可调整大小的组件外部)按下,我们将抓住焦点并重新绘制该组件。 边框上的矩形将消失。

ResizableBorder.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.Component;
  4. import java.awt.Cursor;
  5. import java.awt.Graphics;
  6. import java.awt.Insets;
  7. import java.awt.Rectangle;
  8. import java.awt.event.MouseEvent;
  9. import javax.swing.SwingConstants;
  10. import javax.swing.border.Border;
  11. public class ResizableBorder implements Border {
  12. private int dist = 8;
  13. int locations[] = {
  14. SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
  15. SwingConstants.EAST, SwingConstants.NORTH_WEST,
  16. SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
  17. SwingConstants.SOUTH_EAST
  18. };
  19. int cursors[] = {
  20. Cursor.N_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR,
  21. Cursor.E_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
  22. Cursor.SW_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
  23. };
  24. public ResizableBorder(int dist) {
  25. this.dist = dist;
  26. }
  27. @Override
  28. public Insets getBorderInsets(Component component) {
  29. return new Insets(dist, dist, dist, dist);
  30. }
  31. @Override
  32. public boolean isBorderOpaque() {
  33. return false;
  34. }
  35. @Override
  36. public void paintBorder(Component component, Graphics g, int x, int y,
  37. int w, int h) {
  38. g.setColor(Color.black);
  39. g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);
  40. if (component.hasFocus()) {
  41. for (int i = 0; i < locations.length; i++) {
  42. var rect = getRectangle(x, y, w, h, locations[i]);
  43. g.setColor(Color.WHITE);
  44. g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
  45. g.setColor(Color.BLACK);
  46. g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
  47. }
  48. }
  49. }
  50. private Rectangle getRectangle(int x, int y, int w, int h, int location) {
  51. switch (location) {
  52. case SwingConstants.NORTH:
  53. return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);
  54. case SwingConstants.SOUTH:
  55. return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist, dist);
  56. case SwingConstants.WEST:
  57. return new Rectangle(x, y + h / 2 - dist / 2, dist, dist);
  58. case SwingConstants.EAST:
  59. return new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist, dist);
  60. case SwingConstants.NORTH_WEST:
  61. return new Rectangle(x, y, dist, dist);
  62. case SwingConstants.NORTH_EAST:
  63. return new Rectangle(x + w - dist, y, dist, dist);
  64. case SwingConstants.SOUTH_WEST:
  65. return new Rectangle(x, y + h - dist, dist, dist);
  66. case SwingConstants.SOUTH_EAST:
  67. return new Rectangle(x + w - dist, y + h - dist, dist, dist);
  68. }
  69. return null;
  70. }
  71. public int getCursor(MouseEvent me) {
  72. var c = me.getComponent();
  73. int w = c.getWidth();
  74. int h = c.getHeight();
  75. for (int i = 0; i < locations.length; i++) {
  76. var rect = getRectangle(0, 0, w, h, locations[i]);
  77. if (rect.contains(me.getPoint())) {
  78. return cursors[i];
  79. }
  80. }
  81. return Cursor.MOVE_CURSOR;
  82. }
  83. }

ResizableBorder负责绘制组件的边框并确定要使用的光标的类型。

  1. int locations[] = {
  2. SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
  3. SwingConstants.EAST, SwingConstants.NORTH_WEST,
  4. SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
  5. SwingConstants.SOUTH_EAST
  6. };

这些是绘制矩形的位置。 这些位置也是抓取点,可以在其中抓取组件并调整其大小。

  1. g.setColor(Color.black);
  2. g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);

paintBorder()方法中,我们绘制了可调整大小组件的边框。 上面的代码绘制了组件的外边界。

  1. if (component.hasFocus()) {
  2. for (int i = 0; i < locations.length; i++) {
  3. var rect = getRectangle(x, y, w, h, locations[i]);
  4. g.setColor(Color.WHITE);
  5. g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
  6. g.setColor(Color.BLACK);
  7. g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
  8. }
  9. }

仅当可调整大小的组件当前具有焦点时才绘制八个矩形。

  1. private Rectangle getRectangle(int x, int y, int w, int h, int location) {
  2. switch (location) {
  3. case SwingConstants.NORTH:
  4. return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);
  5. case SwingConstants.SOUTH:
  6. return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist, dist);
  7. ...
  8. }

getRectangle()方法返回矩形的坐标。

  1. public int getCursor(MouseEvent me) {
  2. var c = me.getComponent();
  3. int w = c.getWidth();
  4. int h = c.getHeight();
  5. for (int i = 0; i < locations.length; i++) {
  6. var rect = getRectangle(0, 0, w, h, locations[i]);
  7. if (rect.contains(me.getPoint())) {
  8. return cursors[i];
  9. }
  10. }
  11. return Cursor.MOVE_CURSOR;
  12. }

getCursor()方法获取相关抓点的光标类型。

Resizable.java

  1. package com.zetcode;
  2. import javax.swing.JComponent;
  3. import javax.swing.event.MouseInputAdapter;
  4. import javax.swing.event.MouseInputListener;
  5. import java.awt.BorderLayout;
  6. import java.awt.Component;
  7. import java.awt.Cursor;
  8. import java.awt.Point;
  9. import java.awt.event.MouseEvent;
  10. public class Resizable extends JComponent {
  11. public Resizable(Component comp) {
  12. this(comp, new ResizableBorder(8));
  13. }
  14. public Resizable(Component comp, ResizableBorder border) {
  15. setLayout(new BorderLayout());
  16. add(comp);
  17. addMouseListener(resizeListener);
  18. addMouseMotionListener(resizeListener);
  19. setBorder(border);
  20. }
  21. private void resize() {
  22. if (getParent() != null) {
  23. getParent().revalidate();
  24. }
  25. }
  26. MouseInputListener resizeListener = new MouseInputAdapter() {
  27. @Override
  28. public void mouseMoved(MouseEvent me) {
  29. if (hasFocus()) {
  30. var resizableBorder = (ResizableBorder) getBorder();
  31. setCursor(Cursor.getPredefinedCursor(resizableBorder.getCursor(me)));
  32. }
  33. }
  34. @Override
  35. public void mouseExited(MouseEvent mouseEvent) {
  36. setCursor(Cursor.getDefaultCursor());
  37. }
  38. private int cursor;
  39. private Point startPos = null;
  40. @Override
  41. public void mousePressed(MouseEvent me) {
  42. var resizableBorder = (ResizableBorder) getBorder();
  43. cursor = resizableBorder.getCursor(me);
  44. startPos = me.getPoint();
  45. requestFocus();
  46. repaint();
  47. }
  48. @Override
  49. public void mouseDragged(MouseEvent me) {
  50. if (startPos != null) {
  51. int x = getX();
  52. int y = getY();
  53. int w = getWidth();
  54. int h = getHeight();
  55. int dx = me.getX() - startPos.x;
  56. int dy = me.getY() - startPos.y;
  57. switch (cursor) {
  58. case Cursor.N_RESIZE_CURSOR:
  59. if (!(h - dy < 50)) {
  60. setBounds(x, y + dy, w, h - dy);
  61. resize();
  62. }
  63. break;
  64. case Cursor.S_RESIZE_CURSOR:
  65. if (!(h + dy < 50)) {
  66. setBounds(x, y, w, h + dy);
  67. startPos = me.getPoint();
  68. resize();
  69. }
  70. break;
  71. case Cursor.W_RESIZE_CURSOR:
  72. if (!(w - dx < 50)) {
  73. setBounds(x + dx, y, w - dx, h);
  74. resize();
  75. }
  76. break;
  77. case Cursor.E_RESIZE_CURSOR:
  78. if (!(w + dx < 50)) {
  79. setBounds(x, y, w + dx, h);
  80. startPos = me.getPoint();
  81. resize();
  82. }
  83. break;
  84. case Cursor.NW_RESIZE_CURSOR:
  85. if (!(w - dx < 50) && !(h - dy < 50)) {
  86. setBounds(x + dx, y + dy, w - dx, h - dy);
  87. resize();
  88. }
  89. break;
  90. case Cursor.NE_RESIZE_CURSOR:
  91. if (!(w + dx < 50) && !(h - dy < 50)) {
  92. setBounds(x, y + dy, w + dx, h - dy);
  93. startPos = new Point(me.getX(), startPos.y);
  94. resize();
  95. }
  96. break;
  97. case Cursor.SW_RESIZE_CURSOR:
  98. if (!(w - dx < 50) && !(h + dy < 50)) {
  99. setBounds(x + dx, y, w - dx, h + dy);
  100. startPos = new Point(startPos.x, me.getY());
  101. resize();
  102. }
  103. break;
  104. case Cursor.SE_RESIZE_CURSOR:
  105. if (!(w + dx < 50) && !(h + dy < 50)) {
  106. setBounds(x, y, w + dx, h + dy);
  107. startPos = me.getPoint();
  108. resize();
  109. }
  110. break;
  111. case Cursor.MOVE_CURSOR:
  112. var bounds = getBounds();
  113. bounds.translate(dx, dy);
  114. setBounds(bounds);
  115. resize();
  116. }
  117. setCursor(Cursor.getPredefinedCursor(cursor));
  118. }
  119. }
  120. @Override
  121. public void mouseReleased(MouseEvent mouseEvent) {
  122. startPos = null;
  123. }
  124. };
  125. }

Resizable类表示正在调整大小并在窗口上移动的组件。

  1. private void resize() {
  2. if (getParent() != null) {
  3. getParent().revalidate();
  4. }
  5. }

调整组件大小后,将调用resize()方法。 revalidate()方法导致重画组件。

  1. MouseInputListener resizeListener = new MouseInputAdapter() {
  2. @Override
  3. public void mouseMoved(MouseEvent me) {
  4. if (hasFocus()) {
  5. var border = (ResizableBorder) getBorder();
  6. setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
  7. }
  8. }
  9. ...
  10. }

当我们将光标悬停在抓取点上时,我们将更改光标类型。 仅当组件具有焦点时,光标类型才会更改。

  1. @Override
  2. public void mousePressed(MouseEvent me) {
  3. var resizableBorder = (ResizableBorder) getBorder();
  4. cursor = resizableBorder.getCursor(me);
  5. startPos = me.getPoint();
  6. requestFocus();
  7. repaint();
  8. }

如果单击可调整大小的组件,则将更改光标,获得拖动的起点,将焦点放在该组件上,然后重新绘制它。

  1. int x = getX();
  2. int y = getY();
  3. int w = getWidth();
  4. int h = getHeight();
  5. int dx = me.getX() - startPos.x;
  6. int dy = me.getY() - startPos.y;

mouseDragged()方法中,我们确定光标的 x 和 y 坐标以及组件的宽度和高度。 我们计算鼠标拖动事件期间的距离。

  1. case Cursor.N_RESIZE_CURSOR:
  2. if (!(h - dy < 50)) {
  3. setBounds(x, y + dy, w, h - dy);
  4. resize();
  5. }
  6. break;

对于所有大小调整,我们确保该组件不小于 50px。 否则,我们可以将其减小到最终将其隐藏的程度。 setBounds()方法重新定位组件并调整其大小。

Java Swing 中的可调整大小的组件 - 图1

图:可调整大小的组件

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