0

So I have a JPanel that is populated by the contents of a 2D array. I have a mouse listener which changes the colour of a cell when pressed. My question is, is it possible to have the user drag the mouse over a line of cells and colour them all in succession? I have looked into mouse motion listener but this doesn't seem to help.

Any ideas?

mark
  • 2,841
  • 4
  • 25
  • 23
  • You can add a MouseMotionListener to your component and code it into the mouseDragged(MouseEvent e) { } method which will run everytime the user clicks down on your component and then moves the mouse with the button held down. – Kon Aug 04 '13 at 20:01

2 Answers2

1

You can use the mouseDragged() method of the MouseMotionListener in conjunction with the mousePressed() method of the MouseListener.

The mousePressed() method will handle a simple click without movement, and mouseDragged() will handle any dragging done. I combined the code I wrote for my answer to your original question here to better clarify what everything does, and a response on your other question would be very much appreciated.

package stackoverflow.answers;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import javax.swing.*;

public class JPanelPaint {
    JPanel panel;
    JFrame frame;
    BufferedImage image;

    public JPanelPaint() {
        image = new BufferedImage(50, 50, BufferedImage.TYPE_INT_ARGB);
        for (int i = 0; i < image.getWidth(); i++) {
            for (int j=0; j < image.getHeight(); j++) {
                /* I'm just initializing the image with an arbitrary color (white in this case), you can easily change this. */
                image.setRGB(i, j, new Color((int)(255 ), (int)(255 ), (int)(255 )).getRGB());
            }
        }

        frame = new JFrame("JPanel Paint");
        panel = new JPanel() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                Rectangle rect = g.getClipBounds();
                g.setColor(Color.white);
                g.fillRect(rect.x, rect.y, rect.width, rect.height);
                for (int i = 0; i < image.getWidth(); i++) {
                    for (int j=0; j < image.getHeight(); j++) {
                        /* Set the color of the "quadpixel" to that of the original cell on the image. */
                        g.setColor(new Color(image.getRGB(i, j)));
                        g.fillRect(j*4, i*4, 4, 4);
                    }
                }


            }
        };

        panel.addMouseListener(new MouseListener() {

            @Override
            public void mouseClicked(MouseEvent arg0) { }

            @Override
            public void mouseEntered(MouseEvent arg0) { }

            @Override
            public void mouseExited(MouseEvent arg0) { }

            @Override
            public void mousePressed(MouseEvent arg0) {
                /* Y and X are swapped, just a quirk in the JRE */
                /* I'm just setting the pixel with an arbitrary color (black in this case), you can easily change this. */
                image.setRGB(arg0.getY() / 4, arg0.getX() / 4, new Color(0, 0, 0).getRGB());
                panel.repaint();
            }

            @Override
            public void mouseReleased(MouseEvent arg0) { }

        });

        panel.addMouseMotionListener(new MouseMotionListener() {

            @Override
            public void mouseDragged(MouseEvent arg0) {
                /* Y and X are swapped, just a quirk in the JRE */
                /* I'm just setting the pixel with an arbitrary color (black in this case), you can easily change this. */
                image.setRGB(arg0.getY() / 4, arg0.getX() / 4, new Color(0, 0, 0).getRGB());
                panel.repaint();
            }

            @Override
            public void mouseMoved(MouseEvent arg0) { }

        });

        panel.setPreferredSize(new Dimension(200, 200));

        frame.add(panel);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        panel.repaint();
    }

    public static void main(String[] args) {
        new JPanelPaint();
    }
}
Community
  • 1
  • 1
Pandacoder
  • 708
  • 7
  • 11
  • This is not a bad solution, but extending JPanel is usually a better idea than adding listeners with inline classes because it gives you a fully self-contained reusable component. – Jason C Aug 04 '13 at 20:27
  • 1
    @JasonC Normally I would do what you mentioned, and what you said was entirely valid, and I highly recommend that the OP does follow your advice in his or her actual application. I made the design choice because I wanted to post a somewhat short and stand-alone application. – Pandacoder Aug 04 '13 at 20:37
  • The only think I find wrong with this example is the use of `paint` – MadProgrammer Aug 04 '13 at 21:48
  • As I said in my post, my answer here uses code I wrote for the question that the OP has asked that is in direct relation to this question. In the original question he used `paint()`, so I used it, but recommended against using it nonetheless. – Pandacoder Aug 04 '13 at 22:04
  • @RobbieLodico Sorry to harass you, but I don't see any coded provided by the `OP`, which *might* lead them astray. Even if the OP had used `paint`, I'd at least question and provide some reasons when it might be appropriate - but that's just me :P – MadProgrammer Aug 04 '13 at 23:46
  • @MadProgrammer This was the link that the word "here" links to in my post: http://stackoverflow.com/questions/18039172/java-why-doesnt-this-jpanel-paint-properly – Pandacoder Aug 04 '13 at 23:57
1

You don't need the mouse listeners if you extend JPanel. Just enable mouse events then override the component's mouse event handlers. The general logic is:

if mouse pressed {
    dragging = true 
    begin drag
}

if mouse dragged and dragging == true {
    process drag
}

if mouse released and dragging == true {
    dragging = false
    finalize drag
}

Here is an example:

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

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

public class DragTest {

    // example JPanel. click and drag on it to create lines.
    static class DragPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        static class Line {
            int x1, y1, x2, y2;
        }

        private final List<Line> lines = new ArrayList<Line>();
        private Line draggedLine; // null if not dragging

        public DragPanel() {

            // enable mouse event processing even if no listeners are registered
            enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);

        }

        @Override
        public void paintComponent(Graphics g) {

            super.paintComponent(g);
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
            // draw saved lines
            g.setColor(Color.WHITE);
            for (Line line : lines)
                g.drawLine(line.x1, line.y1, line.x2, line.y2);
            // draw currently active line if there is one
            if (draggedLine != null) {
                g.setColor(Color.RED);
                g.drawLine(draggedLine.x1, draggedLine.y1, draggedLine.x2, draggedLine.y2);
            }

        }

        // does the work; since motion and press/release are all MouseEvent,
        // we can just direct MouseEvents here from the event handler overrides
        // and handle based on event ID.
        private void handleMouseEvent(MouseEvent e) {

            if (e.getID() == MouseEvent.MOUSE_PRESSED && e.getButton() == MouseEvent.BUTTON1) {
                // begin drag by initializing a new Line at mouse position
                if (draggedLine == null) {
                    draggedLine = new Line();
                    draggedLine.x1 = draggedLine.x2 = e.getX();
                    draggedLine.y1 = draggedLine.y2 = e.getY();
                    e.consume();
                }
            } else if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
                // if drag in progress, update line endpoint
                if (draggedLine != null) {
                    draggedLine.x2 = e.getX();
                    draggedLine.y2 = e.getY();
                    e.consume();
                }
            } else if (e.getID() == MouseEvent.MOUSE_RELEASED && e.getButton() == MouseEvent.BUTTON1) {
                // if drag in progress, accept new line and end drag
                if (draggedLine != null) {
                    draggedLine.x2 = e.getX();
                    draggedLine.y2 = e.getY();
                    lines.add(draggedLine);
                    draggedLine = null;
                    e.consume();
                }
            }

            if (e.isConsumed())
                repaint();

        }

        @Override
        public void processMouseMotionEvent(MouseEvent e) {
            handleMouseEvent(e); // pass to our handler, may consume event
            super.processMouseMotionEvent(e); // in case there are registered listeners
        }

        @Override
        public void processMouseEvent(MouseEvent e) {
            handleMouseEvent(e); // pass to our handler, may consume event
            super.processMouseEvent(e); // in case there are registered listeners
        }

    }

    public static final void main(String[] args) {

        JFrame frame = new JFrame("Panel Drag Example");
        frame.getContentPane().add(new DragPanel());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(640, 480);
        frame.setVisible(true);

    }

}

Extending JPanel is usually a better idea than adding listeners with inline classes because it gives you a fully self-contained reusable component.

Jason C
  • 38,729
  • 14
  • 126
  • 182
  • I'm not sure I understand what's wrong with using MouseListener's. The event handling your describing is a reminent of the AWT API, which MouseListener hooks into anyway. Prior to inner classes, I might have agreeded with the self contained argument, but now, personally, I don't see the point. This is placing the OP quite low down in the event chain, which may complicate things more - IMHO – MadProgrammer Aug 04 '13 at 21:04
  • Well, there's nothing *wrong* with using MouseListeners, per se, it's that if you set mouse listeners to inline subclasses, you no longer have a self-contained component. You have to set those listeners every time you create the component, rather than creating a new DragPanel. You lose the self-containment of the class, which is problematic when creating libraries, and it also is not compatible with, say, a GUI editor that lets you place your own custom components. Think about it in a situation where you have many of these components on many forms, or used by many applications. – Jason C Aug 04 '13 at 21:09
  • As for the event handling itself, processXXXEvent() is in fact the correct place to handle component events. The old mouseMoved(), mousePressed(), etc. callbacks were deprecated and replaced by processXXXEvent() way back in 1.1. A listener would be appropriate when you're trying to hook into events on other components (or, arguably, if you're putting together a quick application or very unique component), whereas an event handler override is appropriate when implementing a new component. – Jason C Aug 04 '13 at 21:12
  • You're still talking about AWT, Swing makes extensive use of `MouseListeners` as it's recommended method for listening to mouse input since before Java 1.3. I think you're talking about the `mouseDown` methods that exist within `Component`, which you would be right – MadProgrammer Aug 04 '13 at 21:42
  • Even in your example, you have to "register" the mouse events, which are registered directly to the `Toolkit` (on your behalf all be it), so "technically", you could simple add a mouse listener when you enable your input events, using either a inner or anonymous class - just saying ;) - And you don't need to worry about event masking, which is always fun ;) – MadProgrammer Aug 04 '13 at 21:47
  • Both are valid and you should make a decision on which to use based on whatever is the most appropriate for the situation, and always keep the big picture of what you're doing and how it's going to affect future use and maintenance in mind. If for personal reasons you are anti event-handler, you can create and add event listeners in your derived component's constructor. That does add an extra layer of event handling (as the processXXXEvent methods are responsible for invoking those listeners), but would be more palatable to if you prefer / are used to working with listeners. – Jason C Aug 04 '13 at 22:50
  • To be clear, it is not the use of listeners that I recommend against. What I recommend against is defining distinct behaviors for custom components outside of that custom component's class. When you have to put code that is integral to the behavior of your component outside of your component (i.e. when "new MyComponent()" is not enough to create a new MyComponent), then you *will* run into trouble, or at the bare minimum end up with spaghetti. – Jason C Aug 04 '13 at 22:55
  • (Imagine if there were no Swing components derived from JComponent, but instead the API required you to create JComponent then create a specific set of listeners to make e.g. a text box. It's especially "dangerous" to show an example that encourages this to an inexperienced programmer looking for examples on a site like this.) – Jason C Aug 04 '13 at 22:59
  • Sorry, I'm not arguing if the answer is right or wrong, it's both, with context. In 14+ years of development, I've never had register an input event handler this way, but that's just me. Prior to Java 1.5 (I think), I would have agreed with, but with inner class support, the point is mute. I think my sticking point is more about *"This is the correct way"* - I think it's just a way and I was curious as to why you might chose it, that's all ;) - Always looking to learn – MadProgrammer Aug 04 '13 at 23:44
  • ps- I, personally, wouldn't see it as a reason to down vote the question – MadProgrammer Aug 04 '13 at 23:46
  • 1
    No, I'm just pointing out that "I" don't see any reason to down vote the question, the reason for the conversation was for clarification, not argument ;) – MadProgrammer Aug 04 '13 at 23:50