Java Swing - Valid approach for dragging rectangles onto a JPanel?

drawing, java, swing, user-interface

Solution

To show lots of non-changing background shapes, draw them to a BufferedImage and then show that BufferedImage in the `paintComponent(...)` method. So while a shape is being drawn, draw it in `paintComponent(...)` but once the shape is done being drawn, perhaps on mouseRelease, then draw it in the background BufferedImage.

Note that what will slow your current drawing code the most may be your debugging SOP statements, but I assume that these will be removed from the finished code.

For example:

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

import javax.swing.*;

@SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
   private static final int PREF_W = 600;
   private static final int PREF_H = 400;
   private static final Color DRAWING_COLOR = new Color(255, 100, 200);
   private static final Color FINAL_DRAWING_COLOR = Color.red;

   private BufferedImage backgroundImg;
   private Point startPt = null;
   private Point endPt = null;
   private Point currentPt = null;

   public DrawingPanel() {
      backgroundImg = new BufferedImage(PREF_W, PREF_H,
            BufferedImage.TYPE_INT_ARGB);
      Graphics g = backgroundImg.getGraphics();
      g.setColor(Color.blue);
      g.fillRect(0, 0, PREF_W, PREF_H);
      g.dispose();

      MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
      addMouseMotionListener(myMouseAdapter);
      addMouseListener(myMouseAdapter);
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (backgroundImg != null) {
         g.drawImage(backgroundImg, 0, 0, this);
      }

      if (startPt != null && currentPt != null) {
         g.setColor(DRAWING_COLOR);
         int x = Math.min(startPt.x, currentPt.x);
         int y = Math.min(startPt.y, currentPt.y);
         int width = Math.abs(startPt.x - currentPt.x);
         int height = Math.abs(startPt.y - currentPt.y);
         g.drawRect(x, y, width, height);
      }
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(PREF_W, PREF_H);
   }

   public void drawToBackground() {
      Graphics g = backgroundImg.getGraphics();
      g.setColor(FINAL_DRAWING_COLOR);
      int x = Math.min(startPt.x, endPt.x);
      int y = Math.min(startPt.y, endPt.y);
      int width = Math.abs(startPt.x - endPt.x);
      int height = Math.abs(startPt.y - endPt.y);
      g.drawRect(x, y, width, height);
      g.dispose();

      startPt = null;
      repaint();
   }

   private class MyMouseAdapter extends MouseAdapter {
      @Override
      public void mouseDragged(MouseEvent mEvt) {
         currentPt = mEvt.getPoint();
         DrawingPanel.this.repaint();
      }

      @Override
      public void mouseReleased(MouseEvent mEvt) {
         endPt = mEvt.getPoint();
         currentPt = null;
         drawToBackground();
      }

      @Override
      public void mousePressed(MouseEvent mEvt) {
         startPt = mEvt.getPoint();
      }
   }

   private static void createAndShowGui() {
      DrawingPanel mainPanel = new DrawingPanel();

      JFrame frame = new JFrame("Drawing Panel");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

Problem

I have some code to draw rectangles. It's used to draw rectangles on a `JPanel`, to mark boundaries of widgets. Here the code first, after that I'll explain my problem cq. question. First off, I have a class (`WidgetDrawingPanel`) which extends `JPanel`. ``` public WidgetDrawingPanel(int width, int height) { /*To make things visible at least*/ widgets.add(new Widget(10,10,100,100, WidgetType.TextField)); widgets.add(new Widget(50,50,100,200, WidgetType.TextField)); this.width = width; this.height = height; this.setBackground(Color.BLUE); addListener(); //adds both MouseMotionListener and MouseListener } ``` Below you'll see me reference `ch` a lot. This is a `CoordinateHolder`, which holds start and current coordinates of my mouse movement. ``` private void addListener() { this.addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent arg0) { ch.currentX = arg0.getX(); ch.currentY = arg0.getY(); System.out.println("dragging " + ch.currentX + ","+ch.currentY); WidgetDrawingPanel.this.repaint(); } }); this.addMouseListener(new MouseListener() { @Override public void mouseReleased(MouseEvent event) { ch.endX = event.getX(); ch.endY = event.getY(); try { checkCoords(); } catch (OutsidePanelException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "drawn Outside Panel"); } } @Override public void mousePressed(MouseEvent event) { ch = new CoordinateHolder(event.getX(), event.getY()); } }); } ``` and, finally, the `paintComponent(Grapics)` method. There's loop through `Widgets`, which are actually just already drawn `Rects` (x, y, w, h attributes), but which a little more information, which is not useful in the drawing part of the application. Everytime you release the mouse, the `CoordinateHolder` is converted into a Widget, and added to `widgets`. ``` @Override public void paintComponent(Graphics g) { super.paintComponent(g); System.out.println("Paint"); g.setColor(Color.BLUE); g.fillRect(0, 0, width, height); //making the whole panel blue g.setColor(Color.RED); Graphics2D g2 = (Graphics2D)g; g2.setStroke(new BasicStroke(3)); for (Widget w : widgets) { g.drawRect(w.getX(), w.getY(), w.getW(), w.getH()); } if (ch != null) g.drawRect(ch.startX, ch.startY, ch.currentX - ch.startX, ch.currentY - ch.startY); } ``` This code is working, but I suspect this is highly inefficient and inperformant, as above code continually refreshes the `JPanel` on mouse drag, which is, say, once every 10ms? I suppose it'll get slow really soon, especially when the user creates a heck of a lot rectangles (which are also continally redrawn, as seen in `painComponent(Graphics)`). Question cq. Problem Is there a better, less resource consuming method, where the user can drag rectangles smoothly? I read an answer to this Drag rectangle on JFrame in Java, but the author of that answer seems to do it the same as me. But again, that's way inperformant, right? Or should computers be easily able to redraw the component continually, and is this actually a valid approach?

Original source