0

I have essentially no experience designing GUIs, so I am currently trying to teach myself a little Swing. My current project involves rendering a simple game-board-like grid, and a number of objects on top of it which can be freely moved around by dragging and dropping. I have successfully gotten the frontend of the project into a more-or-less working state, modulo one irritating graphical quirk. Objects are being rendered twice in a small region in the upper-left corner of the grid:

Duplicated object Duplicated object vanishing

Note that the second image disappears and everything works perfectly normally after the object has been moved sufficiently away from the upper corner.

I've had a very hard time locating a solution to this problem (due in no small part due to my lack of the proper vocabulary to describe it, I'm sure). I've attempted to trim down my code into a minimal example that exhibits the anomalous behavior, but the fragment I have is still unfortunately pretty bulky:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class MinimumBug {
    public static final int CELL_DIMENSION = 100;

    public static void main(String[] args) {
        JFrame window = new JFrame();
        window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        window.setVisible(true);
        GridPanel panel = new GridPanel(9,9);
        DraggableComponent component = new DraggableComponent();
        panel.addDraggableComponent(component,0,0);
        window.add(panel);
        window.pack();
    }

    public static class GridCell {
        public final int row;
        public final int col;
        private DraggableComponent occupant;

        public boolean isOccupied() {
            return occupant != null;
        }

        public GridCell(int row, int col) {
            this.row = row;
            this.col = col;
        }

        public int xPos() {
            return (row * CELL_DIMENSION);
        }

        public int yPos() {
            return (col * CELL_DIMENSION);
        }

        public DraggableComponent getOccupant() {
            return occupant;
        }

        public void setOccupant(DraggableComponent occupant) {
            this.occupant = occupant;
            occupant.setCell(this);
        }
    }

    private static class GridPanel extends JPanel {
        public static final int CELL_DIMENSION = 100;

        private final int rows;
        private final int cols;
        private final GridCell[][] grid;

        public GridPanel(int rows, int cols) {
            this.rows = rows;
            this.cols = cols;
            this.grid = new GridCell[rows][cols];
            setLayout(null);
            setPreferredSize(new Dimension(rows*CELL_DIMENSION, cols*CELL_DIMENSION));
            setSize(new Dimension(rows*CELL_DIMENSION, cols*CELL_DIMENSION));
            buildGrid();
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            drawOccupants(g);
        }

        public void addDraggableComponent(DraggableComponent comp, int row, int col) {
            grid[row][col].setOccupant(comp);
            add(comp);
        }

        private void buildGrid() {
            for (int i=0; i<rows; i++) {
                for (int j=0; j<cols; j++) {
                    grid[i][j] = new GridCell(i,j);
                }
            }
        }

        private void drawOccupants(Graphics g) {
            for (int row = 0 ; row <rows; row++) {
                for (int col = 0; col<cols; col++) {
                    if (grid[row][col].isOccupied()) {
                        grid[row][col].getOccupant().paintComponent(g);
                    }
                }
            }
        }
    }

    private static class DraggableComponent extends JComponent {
        private int relativeX, relativeY, screenX, screenY;

        public DraggableComponent() {
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    relativeX = getX();
                    relativeY = getY();
                    screenX = e.getXOnScreen();
                    screenY = e.getYOnScreen();
                }
            });
            addMouseMotionListener(new MouseAdapter() {
                @Override
                public void mouseDragged(MouseEvent e) {
                    int dx = screenX - e.getXOnScreen();
                    int dy = screenY - e.getYOnScreen();
                    setLocation(relativeX - dx, relativeY - dy);
                }
            });
        }

        @Override
        public void setLocation(int x, int y) {
            super.setLocation(x, y);
            setBounds(x, y, GridPanel.CELL_DIMENSION, GridPanel.CELL_DIMENSION);
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.BLUE);
            int x = (int) (getX() + (CELL_DIMENSION *0.35));
            int y = (int) (getY() + (CELL_DIMENSION *0.35));
            int span = (int) (CELL_DIMENSION *0.3);
            g.fillOval(x,y,span,span);
        }

        public void setCell(GridCell container) {
            setLocation(container.xPos(), container.yPos());
        }
    }
}

Edit: As a first stab at addressing the issue, I have taken the course of action suggested in the answer to "https://stackoverflow.com/questions/13358658/paintcomponent-draws-other-components-on-top-of-my-drawing" and made sure to additionally call the super method in my overridden paintComponent method. Unfortunately, that did not address the issue, and I am still getting the anomalous behavior.

R. Thomas
  • 1
  • 1
  • 1
    Just by the title alone, I can guess that you are overriding paint or paintComponent and not calling the super method within your override. The fix is to call the super's method. – Hovercraft Full Of Eels Sep 02 '23 at 21:49
  • 1
    Yes, it is paintComponent. At the start of the method, call `super.paintComponent(g);`. Doing this will tell the JPanel to do some housekeeping painting, including removing "dirty" pixels, before doing the additional drawing that you call in that method. – Hovercraft Full Of Eels Sep 02 '23 at 21:50
  • I have added the suggested calls, but that didn't address the issue. The problem isn't that the component is leaving a trail of duplicated dirty pixels, but that there are two rendered copies of the object that move at different rates with respect to the mouse speed. – R. Thomas Sep 02 '23 at 22:45
  • You're also calling paintComponent directly on components, something that you shouldn't be doing, ever. If you need to drag a component, then move it into the glasspane or use D&D support. – Hovercraft Full Of Eels Sep 03 '23 at 00:03
  • 1) Don't invoke paintComponent() directly. 2) Your logic for painting the compont seems off. Custom painting should be done relative to (0, 0) of the component. The location of the component will then position the component on the board. So I would think you should NOT use the getX() and getY() methods. – camickr Sep 03 '23 at 00:10
  • Maybe check out: https://stackoverflow.com/a/6811800/131872 which demonstrates how to drag a JLabel from a chess board grid. You could create an oval Icon to add to the label. – camickr Sep 03 '23 at 00:14

0 Answers0