如何在绘制新矩形时触发自定义事件?

How do I fire a custom event when a new Rectangle is drawn?

我创建了一个图形组件,它允许您查看图像并允许您选择图像的一部分:通过在此图像上绘制一个矩形(使用拖放)。

为此,我使用了this example,它创建了JLabel 的子class 来绘制图像并处理矩形的绘制。然后我将这个 subclass 的一个实例放在 JPanel 中,以使图像始终位于面板的中心。

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);
            }
        });
    }

}

私有 class SelectionLabel 是来自 this example.

的 class SelectionArea

绘制新矩形时,控制台上会打印一条消息。现在我将用自定义事件的触发替换消息的打印,以便应用程序业务逻辑可以访问矩形的位置和大小。

我阅读了如何 create a custom event in Java. Moreover, this article identifies two super types for creating events: EventObject and AWTEvent。这篇文章指出:

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

由于有关选择图像的一部分的事件是由图形组件(即 FigurePanel 面板)生成的,因此我可以通过以下方式实现 ImageSelectionEvent class将 AWTEvent 扩展为以下代码片段。

public class ImageSelectionEvent extends AWTEvent
{

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

}

文档标识 id as the event type。那么,这个参数应该赋什么值呢?

此外,为什么 EventObject class 的构造函数没有 id 参数?

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.

这个呢?

Javadoc for AWTEvent 说:

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.

主要是为您的事件使用一致的值。

最后,我会考虑子类化 ComponentEvent over AWTEvent as the source of your event is a Component,而不是 Object

我不知道创建自定义事件需要什么。

但是,由于您要扩展 JLabel,也许您可​​以创建一个 PropertyChangeEvent

要生成事件,您只需使用类似的东西:

firePropertyChange("selectionRectangle", oldRectangle, newRectangle);

然后您可以使用 PropertyChangeListener 来监听 "selectionRectangle" 变化。