简体   繁体   中英

Java/Swing: Create text fields upon clicking a rectangle

I am creating an app where the user can upload an image, draw rectangles on top of the image using the mouse for annotation, and each rectangle upon click will display an empty text field on the right as shown in pic for the user to attach comments to that certain region. 在此处输入图片说明 Right now the program will upload the image and lets the user draw rectangles on top of it (the left side of the image shown above is implemented), but I'm having trouble figuring out how to make the program create and display an empty text field when the user clicks on a rectangle for the first time (implementing the right side of the image shown). The rectangle will also be highlighted in red when selected, and if a comment is already there, the comment should be in the text box upon click. My guess is I will have to create the JTextFields in the DrawingArea class and somehow pass it over to the ImageAnnotator class? or I have to import the ImageAnnotator inside the DrawingArea and input the TextFields there directly? Or maybe there is a connection that I'm not making. I'm also having trouble displaying the image name on the screen, even though I'm passing it to the JLabel. Any help is appreciated.

DrawingArea.java:

public class DrawingArea extends JPanel
    {
        private final static int AREA_SIZE = 490;
        private BufferedImage image =
            new BufferedImage(AREA_SIZE, AREA_SIZE, BufferedImage.TYPE_INT_ARGB);
        private Rectangle shape;
        private ArrayList<Box> rectangles = new ArrayList<Box>();
        public String imageName = ""; // this will store the image/file name

        public DrawingArea()
        {
            setBackground(Color.WHITE);

            MyMouseListener ml = new MyMouseListener();
            addMouseListener(ml);
            addMouseMotionListener(ml);
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);

            //  Custom code to support painting from the BufferedImage

            if (image != null)
            {
                g.drawImage(image, 0, 0, null);
            }
            
            Color foreground = g.getColor();


            for (Box b : rectangles)
            {
                g.setColor( b.getForeground() );
                Rectangle r = b.getRectangle();
                g.drawRect(r.x, r.y, r.width, r.height);
            }

            //  Paint the Rectangle as the mouse is being dragged

            if (shape != null)
            {
                Graphics2D g2d = (Graphics2D)g;
                g2d.draw( shape );
            }
        }

        public void addRectangle(Rectangle rectangle, Color color)
        {
            //  Draw the Rectangle onto the BufferedImage

            Box b = new Box(color, rectangle);
            rectangles.add( b );
            repaint();
        }

        public void clear()
        {
            rectangles.clear();
            repaint();
        }
        
        public void loadImage() {
            ...
            this.imageName = f.getName(); //this is where I pass the file name
            ...
        }
        
        public static BufferedImage scaleImage(int w, int h, BufferedImage img) throws Exception {...}

        class MyMouseListener extends MouseInputAdapter
        {
            private Point startPoint;

            public void mousePressed(MouseEvent e)
            {
                startPoint = e.getPoint();
                shape = new Rectangle();
            }

            public void mouseDragged(MouseEvent e)
            {
                int x = Math.min(startPoint.x, e.getX());
                int y = Math.min(startPoint.y, e.getY());
                int width = Math.abs(startPoint.x - e.getX());
                int height = Math.abs(startPoint.y - e.getY());

                shape.setBounds(x, y, width, height);
                repaint();
            }

            public void mouseReleased(MouseEvent e)
            {
                if (shape.width != 0 || shape.height != 0)
                {
                    addRectangle(shape, e.getComponent().getForeground());
                }

                shape = null;
            }
        }       
    }

ImageAnnotator.java:

public class ImageAnnotator extends JFrame {

    private JPanel contentPane;
    Model model;
    private JLabel ImageName;
    DrawingArea drawingArea;
    ButtonPanel buttonPanel;
    GroupLayout gl_contentPane;
    private JLabel lblNewLabel;
    /**
     * Create the frame.
     */
    public ImageAnnotator(Model m) {
        super();
        this.model = m;
        setTitle("Image Annotator");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 850, 646);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        drawingArea = new DrawingArea();
        
        ImageName = new JLabel(drawingArea.imageName); // here I'm trying to set the filename
        buttonPanel = new ButtonPanel( drawingArea );
        
        lblNewLabel = new JLabel("Comments");
        lblNewLabel.setFont(new Font("Times New Roman", Font.BOLD, 17));
        
        gl_contentPane = new GroupLayout(contentPane);
        gl_contentPane.setHorizontalGroup(
            gl_contentPane.createParallelGroup(Alignment.LEADING)
                .addGroup(gl_contentPane.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(drawingArea, GroupLayout.PREFERRED_SIZE, 490, GroupLayout.PREFERRED_SIZE)
                    .addPreferredGap(ComponentPlacement.RELATED, 128, Short.MAX_VALUE)
                    .addComponent(lblNewLabel)
                    .addGap(117))
                .addGroup(gl_contentPane.createSequentialGroup()
                    .addGap(121)
                    .addGroup(gl_contentPane.createParallelGroup(Alignment.TRAILING)
                        .addComponent(buttonPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                        .addComponent(ImageName, GroupLayout.PREFERRED_SIZE, 254, GroupLayout.PREFERRED_SIZE))
                    .addContainerGap(451, Short.MAX_VALUE))
        );
        gl_contentPane.setVerticalGroup(
            gl_contentPane.createParallelGroup(Alignment.LEADING)
                .addGroup(gl_contentPane.createSequentialGroup()
                    .addContainerGap()
                    .addGroup(gl_contentPane.createParallelGroup(Alignment.LEADING)
                        .addComponent(lblNewLabel)
                        .addGroup(gl_contentPane.createSequentialGroup()
                            .addComponent(drawingArea, GroupLayout.PREFERRED_SIZE, 490, GroupLayout.PREFERRED_SIZE)
                            .addPreferredGap(ComponentPlacement.RELATED)
                            .addComponent(ImageName, GroupLayout.PREFERRED_SIZE, 26, GroupLayout.PREFERRED_SIZE)))
                    .addPreferredGap(ComponentPlacement.RELATED)
                    .addComponent(buttonPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                    .addContainerGap(27, Short.MAX_VALUE))
        );
        contentPane.add(drawingArea);
        contentPane.setLayout(gl_contentPane);
    }

Box.java:

public class Box {
    int bWidth, bHeight, bX, bY;
    String bImageName, bComment;
    
    Color foreground;
    Rectangle rectangle;
    
    public Box(int width, int height) {
        bWidth = width;
        bHeight = height;
    }
    
    public Box(Color foreground, Rectangle rectangle) {
        this.foreground = foreground;
        this.rectangle = rectangle;
    }
    
    public void setImageName(String imageName) { bImageName = imageName; }
    public String getImageName() { return bImageName; }
    
    public void setComment(String comment) { bComment = comment; }
    public String getComment() { return bComment; }
    
    public void setX(int x) { bX = x; }
    public int getX() { return bX; }
    
    public void setY(int y) { bY = y; }
    public int getY() { return bY; }
    
    public Color getForeground()
    {
        return foreground;
    }

    public void setForeground(Color foreground)
    {
        this.foreground = foreground;
    }

    public Rectangle getRectangle()
    {
        return rectangle;
    }
    
}

The first thing you probably need is some kind of listener or observer that can be used to notify interested parties when the Box is selected

public interface BoxSelectionListener extends EventListener {
    public void didSelect(Box box);
}

This would be implemented by the parent part of the UI that is managing both the drawing and input components, when triggered, the interested party would then take appropriate action based on its needs.

Next, we need to add some management code for the DrawingArea to manage the listener support. This is made easier as Swing components have some handy support

public void addBoxSelectionListener(BoxSelectionListener listener) {
    listenerList.add(BoxSelectionListener.class, listener);
}

public void removeBoxSelectionListener(BoxSelectionListener listener) {
    listenerList.remove(BoxSelectionListener.class, listener);
}

protected void fireBoxSelected(Box box) {
    BoxSelectionListener[] listeners = listenerList.getListeners(BoxSelectionListener.class);
    // Normally, I'd create a event object, which would wrap the source (this) and
    // the Box together, but if there are no listeners, it's a bit of
    // a waste to do so, so I return early in those cases
    if (listeners.length == 0) {
        return;
    }
    for (BoxSelectionListener listener : listeners) {
        listener.didSelect(box);
    }
}

Okay, pretty simple, you can add or remove a listener and trigger the event to fire when you need to.

Okay, now the "slightly" harder bit. When the mouse is pressed and released, you need to make some decisions about what to do, do you want to select a box or do you want to draw a new one.

class MyMouseListener extends MouseInputAdapter {

    private Point startPoint;

    public void mousePressed(MouseEvent e) {
        // Mark the clip point
        startPoint = e.getPoint();
    }

    public void mouseDragged(MouseEvent e) {
        // Only create the shape when dragging starts
        if (shape == null) {
            shape = new Rectangle();
        }
        int x = Math.min(startPoint.x, e.getX());
        int y = Math.min(startPoint.y, e.getY());
        int width = Math.abs(startPoint.x - e.getX());
        int height = Math.abs(startPoint.y - e.getY());

        shape.setBounds(x, y, width, height);
        repaint();
    }

    public void mouseReleased(MouseEvent e) {
        if (shape != null) {
            if (shape.width != 0 || shape.height != 0) {
                addRectangle(shape, e.getComponent().getForeground());
            }
        } else {
            for (Box b : rectangles) {
                if (b.getRectangle().contains(e.getPoint())) {
                    didSelect(b);
                    break;
                }
            }
        }

        startPoint = null;
        shape = null;
    }
}

So, some modifications to your code.

  1. Until the drag is detected, we don't create a new shape , this allows us some wiggle room in our decision making process.
  2. If the user "clicked" the component, we scan through the available shapes and determine if the user clicked one, if one was clicked we call didSelect

Why do we do this? The main reason for doing this is it allows use to detect when some one is creating overlapping rectangles (more or less), but with any luck they won't do that, because it makes selections super difficult.

The didSelect method is a simple opportunity to handle all the things you need to do when you want to "select" a particular box. This decouples the functionality and allow you to make decisions about when it might be called

public void didSelect(Box box) {
    // Probably assign this to a "assigned" or "selected" property
    // so it can painted differently
    // And now we want to notify some kind of listener so that
    // it can update the UI as required
    
    fireBoxSelected(box);
}

你可以用,当你点击一个广场去,你既可以,捕获鼠标的位置,并做一些数学和构建jpanel用一种无形的jtextbox ,或做同样的事情,但产卵时,提请箱面板。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM