1

I created a graphical component that allows you to view an image and allows you to make a selection of a part of the image: the selection of a portion of the image is accomplished by drawing a rectangle on this image (using drag-and-drop).

To this purpose, I used this example, which created a subclass of JLabel in order to draw the image and in order to deal with the drawing of the rectangle. Then I put an instance of this subclass within a JPanel, in order to have the image always positioned at the center of the panel.

FigurePanel.java

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;


public class FigurePanel extends JPanel
{
    private SelectionLabel imageLabel = null;


    public FigurePanel()
    {
        this.setLayout(new GridBagLayout());

        imageLabel = new SelectionLabel();
        this.add(imageLabel, null);
    }

    public void setImage(Image image)
    {
        imageLabel.setImage(image);
    }

    private class SelectionLabel extends JLabel
    {
        private Rectangle currentRect = null;
        private Rectangle rectToDraw = null;
        private final Rectangle previousRectDrawn = new Rectangle();


        public SelectionLabel()
        {
            super();
            setOpaque(true);

            SelectionListener listener = new SelectionListener();
            addMouseListener(listener);
            addMouseMotionListener(listener);
        }


        public void setImage(Image image)
        {
            currentRect = null;
            rectToDraw = null;
            previousRectDrawn.setBounds(0, 0, 0, 0);

            setIcon(new ImageIcon(image));
        }

        private class SelectionListener extends MouseInputAdapter
        {
            @Override
            public void mousePressed(MouseEvent e)
            {
                int x = e.getX();
                int y = e.getY();
                currentRect = new Rectangle(x, y, 0, 0);
                updateDrawableRect(getWidth(), getHeight());
                repaint();
            }

            @Override
            public void mouseDragged(MouseEvent e)
            {
                updateSize(e);
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                updateSize(e);
            }

            /* 
             * Update the size of the current rectangle
             * and call repaint.  Because currentRect
             * always has the same origin, translate it
             * if the width or height is negative.
             * 
             * For efficiency (though
             * that isn't an issue for this program),
             * specify the painting region using arguments
             * to the repaint() call.
             * 
             */
            void updateSize(MouseEvent e)
            {
                int x = e.getX();
                int y = e.getY();
                currentRect.setSize(x - currentRect.x,
                                    y - currentRect.y);
                updateDrawableRect(getWidth(), getHeight());
                Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
                repaint(totalRepaint.x, totalRepaint.y,
                        totalRepaint.width, totalRepaint.height);
            }
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g); //paints the background and image

            //If currentRect exists, paint a box on top.
            if (currentRect != null) {
                //Draw a rectangle on top of the image.
                g.setXORMode(Color.white); //Color of line varies
                                           //depending on image colors
                g.drawRect(rectToDraw.x, rectToDraw.y, 
                           rectToDraw.width - 1, rectToDraw.height - 1);

                System.out.println("rectToDraw: " + rectToDraw);
            }
        }

        private void updateDrawableRect(int compWidth, int compHeight)
        {
            int x = currentRect.x;
            int y = currentRect.y;
            int width = currentRect.width;
            int height = currentRect.height;

            //Make the width and height positive, if necessary.
            if (width < 0) {
                width = 0 - width;
                x = x - width + 1; 
                if (x < 0) {
                    width += x; 
                    x = 0;
                }
            }
            if (height < 0) {
                height = 0 - height;
                y = y - height + 1; 
                if (y < 0) {
                    height += y; 
                    y = 0;
                }
            }

            //The rectangle shouldn't extend past the drawing area.
            if ((x + width) > compWidth) {
                width = compWidth - x;
            }
            if ((y + height) > compHeight) {
                height = compHeight - y;
            }

            //Update rectToDraw after saving old value.
            if (rectToDraw != null) {
                previousRectDrawn.setBounds(
                            rectToDraw.x, rectToDraw.y, 
                            rectToDraw.width, rectToDraw.height);
                rectToDraw.setBounds(x, y, width, height);
            } else {
                rectToDraw = new Rectangle(x, y, width, height);
            }
        }
    }

}

FigurePanelTest.java

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;


public class FigurePanelTest extends JFrame
{
    public FigurePanelTest()
    {
        FigurePanel imagePanel = new FigurePanel();

        JScrollPane imageScrollPane = new JScrollPane();
        imageScrollPane.setPreferredSize(new Dimension(420, 250));
        imageScrollPane.setViewportView(imagePanel);

        JButton imageButton = new JButton("Load Image");
        imageButton.addActionListener(
                new ActionListener()
                {
                    @Override
                    public void actionPerformed(ActionEvent evt)
                    {
                        JFileChooser fc = new JFileChooser();
                        int returnValue = fc.showOpenDialog(null);
                        if (returnValue == JFileChooser.APPROVE_OPTION) {
                            File selectedFile = fc.getSelectedFile();
                            System.out.println(selectedFile.getName());

                            try
                            {
                                Image image = ImageIO.read(selectedFile.getAbsoluteFile());
                                imagePanel.setImage(image);

                                imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
                            }
                            catch(IOException e)
                            {
                                e.printStackTrace();
                            }
                        }
                    }
                }
        );

        Container container = getContentPane();
        container.setLayout(new BorderLayout());
        container.add(imageScrollPane, BorderLayout.CENTER);
        container.add(imageButton, BorderLayout.NORTH);

        setSize(600, 400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }


    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new FigurePanelTest().setVisible(true);
            }
        });
    }

}

The private class SelectionLabel is the class SelectionArea from this example.

When a new rectangle is drawn, a message is printed on the console. Now I would replace the printing of the message with the firing of a custom event, so that the position and size of the rectangle are accessible to the application business logic.

I read how to create a custom event in Java. Moreover, this article identifies two super types for creating events: EventObject and AWTEvent. This articles states:

Normally you extend AWTEvent for events generated by a graphical component and EventObject any other time.

Since the event concerning the selection of a part of the image is generated by a graphical component (that is the FigurePanel panel), I could implement the ImageSelectionEvent class by extending AWTEvent, as the following code snippet.

public class ImageSelectionEvent extends AWTEvent
{

    public ImageSelectionEvent(Object source, int id) {
        super(source, id);
    }

}

The documentation identifies the id as the event type. So, what value should be assigned to this parameter?

Moreover, why does the constructor of EventObject class be devoid of the id parameter?

When creating an event class, you must guarantee that the event is immutable. The event generator will share the same event instance among the listeners; so ensure any one listener cannot change the event's state.

What about this?

Community
  • 1
  • 1
enzom83
  • 8,080
  • 10
  • 68
  • 114
  • 1
    There is a general difference between AWTEvent and EventObject, AWTEvent typically describes system or core events which are predefined in the API, and instead of using something like "instanceof" are identified by there ID. EventObject on the other hand is used to create more generic or custom events. Typically, this is where I start. I then define a listener interface to go with it that implements EventListener – MadProgrammer Jul 19 '15 at 21:43

2 Answers2

3

I don't know what is needed to create a custom event.

However, since you are extending JLabel maybe you can just create a PropertyChangeEvent.

To generated the event you would just use something like:

firePropertyChange("selectionRectangle", oldRectangle, newRectangle);

Then you can use a PropertyChangeListener to listen for "selectionRectangle" changes.

camickr
  • 321,443
  • 19
  • 166
  • 288
1

The Javadoc for AWTEvent says:

Subclasses of this root AWTEvent class defined outside of the java.awt.event package should define event ID values greater than the value defined by RESERVED_ID_MAX.

This value is 1999. You can set it to whatever you want that's higher than that. This value is specified by all the different types of Swing events, and Swing uses values that are less than that. For example, the MouseEvent event types use values from 500-507.

The main thing is to use a consistent value for your events.

Finally, I would consider subclassing ComponentEvent over AWTEvent as the source of your event is a Component, not an Object.

durron597
  • 31,968
  • 17
  • 99
  • 158