Java Swing custom shapes (2D Graphics)

11,362

Solution 1

In answer to your question, I would definitely do what you describe as an AWT solution--that way you can track the objects created and be able to allow the user to reload them onto an edit Canvas and more than likely each of the shapes the user creates would be a "layer" not the Layer Swing Container, but an object that would store and track what shapes are drawn and be able to redraw them--the main thing to keep in mind is "Draw order". Basically you can do this by assigning each object or group of object that are your "shapes" to have a Z=[0-100] etc. (100, can be any number) which determine what order each of the object/shapes are drawn in and thus how they over lay each other.

Basically you are going to need a shape class that stores the

It should be possible to edit a drawn shape:

resize change its fill color change the stroke color copy/paste it move a single point of the polygon ...

you outlined and a storage object/manager that will enumerate the shape class objects/instances that are created. This classes will more or less be contained by a java.awt.Canvas container that will actually handle all the graphics.

Mostly you want to use awt over Swing due to the fact Swing is not Thread safe--that way you do not "paint yourself in the corner" early on in your design. Another reason is this is an implementation that needs to respond and interact in a way that a user is used to. Swing is built over the AWT and adds a great deal of complexity that an application like this doesn't need. Over all you are going to be creating a class Custom component that is exactly what the Canvas object was mean to provide and if Sun would have kept this tact earlier on they would not have gotten into the mess that Swing turned out to be... The Developer community--myself included--were well on the way to creating a lot of what Swing offered in "slickness" and component based design, but what we were building was totally AWT based. When Swing entered the scene, Java as a GUI platform was greatly complicated and started Sun and Java down a slippery path...

Also you have to decide what you ultimately want as far as control over what you are creating here. If you need it fast and do not really care about modifying it in the future, then there are lots of open-source examples that can do this--most for free. If you want to do it yourself, then hopefully what I have talked about above and the rubber band code below will be enough to get you there and have a deeper understanding of Java as a GUI. I personally hope you take it on yourself--this language desperately needs more "core" people that truly understand the Language and its design and not just how to "work" frameworks like Hibernate and Spring among others...

Good luck hope this helps,

WM

As far as "Rubber-band" select code goes, this is mine I have used in the past, just consider it GLP and use it as you need to...

First is the Listener interface:

/*
 * RubberBandListener.java
 *
 * Created on August 18, 2005, 3:27 PM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */
package com.ges.util;

import java.util.EventListener;
import java.awt.Rectangle;

/**
 *
 * @author mstemen
 */
public interface RubberBandListener extends EventListener {

    public abstract void notifyBounds(Rectangle boundingBox);
}

Here is the class which is a custom AWT component--it should be fine in either Swing/AWT probably even SWT

/*
 * RubberBandSelect.java
 *
 * Created on August 18, 2005, 9:11 AM
 * By Matthew Stemen/Wintermute Studios for util like use
 *
 */
package com.ges.util;

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

/**
 *
 * @author mstemen
 */
public class RubberBandSelect extends Component {

    /** Creates a new instance of RubberBandSelect */
    private Point startPoint = null;
    private Point endPoint = null;
    private Graphics hostGraphics = null;
    private Component hostComponent = null;
    private Color bandColor = Color.ORANGE.darker().darker();
    private boolean started = false;
    private boolean eraseSomething = false;
    private int startX, endX, startY, endY = 0;
    private Rectangle boundingBox;
    private StringBuilder QuadrantMessage = null;
    private HashSet<RubberBandListener> listeners =
        new HashSet<RubberBandListener>();
    private int width = 0;
    private int height = 0;

    public RubberBandSelect(Component c) {
        hostComponent = c;
        hostGraphics = c.getGraphics();
    }

    public void addListener(RubberBandListener l) {
        listeners.add(l);
    }

    public void paint(Graphics g) {
        draw();
    }

    public void erase() {
        if (eraseSomething) {
//            hostComponent.repaint();
            draw();
            eraseSomething = false;
        }
    }

    private void draw() {
        hostGraphics = hostComponent.getGraphics();
        if (hostGraphics != null) {
            try {
                /// hostGraphics.setXORMode( hostComponent.getBackground() );
                erase();
                drawRubberBand();
                eraseSomething = false;
            } finally {
//                hostGraphics.dispose();
            }
        }
    }

    private void drawRubberBand() {
        if (!started) {
            return;
        }

        hostGraphics = hostComponent.getGraphics();
        if (hostGraphics == null) {
            return;
        }

        if (startPoint == null || endPoint == null) {
            return;
        }

        hostGraphics.setColor(bandColor);

        if (endX > startX && endY > startY) {
            boundingBox = new Rectangle(startX, startY, endX - startX, endY - startY);
            hostGraphics.drawRect(startX, startY, endX - startX, endY - startY);

            QuadrantMessage = new StringBuilder("Drawing in Q - IV X1=");
            width = endX - startX;
            height = endY - startY;
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
        } else if (endX < startX && endY < startY) {
            boundingBox = new Rectangle(endX, endY, startX - endX, startY - endY);
            hostGraphics.drawRect(endX, endY, startX - endX, startY - endY);
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
            QuadrantMessage = new StringBuilder("Drawing in Q - II X1=");
            width = startX - endX;
            height = startY - endY;

        } else if (endX > startX && endY < startY) {
            boundingBox = new Rectangle(startX, endY, endX - startX, startY - endY);
            hostGraphics.drawRect(startX, endY, endX - startX, startY - endY);
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
            QuadrantMessage = new StringBuilder("Drawing in Q - I X1=");
            width = endX - startX;
            height = startY - endY;
        } else if (endX < startX && endY > startY) {
            boundingBox = new Rectangle(endX, startY, startX - endX, endY - startY);
            hostGraphics.drawRect(endX, startY, startX - endX, endY - startY);
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
            QuadrantMessage = new StringBuilder("Drawing in Q - III X1=");
            width = startX - endX;
            height = endY - startY;
        }


    }

    public void assignToCompoent(Component c) {
        this.hostComponent = c;
        hostGraphics = c.getGraphics();
    }

    public void update(Graphics g) {
        drawRubberBand();
    }

    public Point getStartPoint() {
        return startPoint;
    }

    public void setStartPoint(Point startPoint) {
        this.startPoint = startPoint;
        startX = (int) startPoint.getX();
        startY = (int) startPoint.getY();
        QuadrantMessage = new StringBuilder();
        // UDTMgr.getMgr().sendStatusMessage( "RubberBandSelect--Started:  point is: X=" + startX + " Y=" + startY );
        // drawRubberBand();
        // started = true;
    }

    public Point getEndPoint() {
        return endPoint;
    }

    public void setEndPoint(Point endPoint) {

        this.endPoint = endPoint;
        clear();
        endX = (int) endPoint.getX();
        endY = (int) endPoint.getY();
        // UDTMgr.getMgr().sendStatusMessage( "RubberBandSelect--Streching: points are: X=" + startX + " Y=" + startY + " Ending Point is: X=" + endX + " Y="+ endY );

        draw();
        notifyListeners();
        started = true;
    }

    public Color getBandColor() {
        return bandColor;
    }

    public void setBandColor(Color bandColor) {
        this.bandColor = bandColor;
    }

    public void setForeground(Color color) {
        this.bandColor = color;

    }

    private void clear() {
        hostGraphics = hostComponent.getGraphics();
        if (hostGraphics == null) {
            return;
        }
        // hostGraphics.setXORMode( hostComponent.getBackground() );
        try {
            // hostGraphics.setXORMode( hostComponent.getBackground() );
            drawRubberBand();
        } finally {
//            hostGraphics.dispose();
        }
    }

    public void breakBand() {
        startPoint = null;
        endPoint = null;
        started = false;
        boundingBox = new Rectangle(0, 0, 0, 0);
        if (hostGraphics != null) {
            hostGraphics.dispose();
        }
        clear();
        hostComponent.repaint();
        // UDTMgr.getMgr().sendStatusMessage( "RubberBandSelect-- Broke band, click to restart" );
    }

    public boolean isStarted() {
        return started;
    }

    public void notifyListeners() {
        Iterator<RubberBandListener> it = listeners.iterator();

        while (it.hasNext()) {
            it.next().notifyBounds(boundingBox);
        }
    }

    public void redraw(Graphics g) {
        if (startPoint == null || endPoint == null) {
            return;
        }

        g.setColor(bandColor);
//        hostGraphics.setPaintMode();
        // hostComponent.repaint();
        // four way case state to determine what quadrant to draw in
        if (endX > startX && endY > startY) {
            boundingBox = new Rectangle(startX, startY, endX - startX, endY - startY);
            g.drawRect(startX, startY, endX - startX, endY - startY);

            QuadrantMessage = new StringBuilder("Drawing in Q - IV X1=");
            width = endX - startX;
            height = endY - startY;
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
        } else if (endX < startX && endY < startY) {
            boundingBox = new Rectangle(endX, endY, startX - endX, startY - endY);
            g.drawRect(endX, endY, startX - endX, startY - endY);
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
            QuadrantMessage = new StringBuilder("Drawing in Q - II X1=");
            width = startX - endX;
            height = startY - endY;

        } else if (endX > startX && endY < startY) {
            boundingBox = new Rectangle(startX, endY, endX - startX, startY - endY);
            g.drawRect(startX, endY, endX - startX, startY - endY);
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
            QuadrantMessage = new StringBuilder("Drawing in Q - I X1=");
            width = endX - startX;
            height = startY - endY;
        } else if (endX < startX && endY > startY) {
            boundingBox = new Rectangle(endX, startY, startX - endX, endY - startY);
            g.drawRect(endX, startY, startX - endX, endY - startY);
            //UDTMgr.getMgr().sendStatusMessage( "Drawing Rect: " + "(X1=" + startX + ",Y1=" + startY + ") (X2=" + endX + ",Y2=" + endY + ")"  );
            QuadrantMessage = new StringBuilder("Drawing in Q - III X1=");
            width = startX - endX;
            height = endY - startY;
        }
    }

    public Rectangle getBoundingBox() {
        return boundingBox;
    }
}

Solution 2

The ImageJ project has a particularly nice implementation of a Polygon tool with adjustable vertices, as seen here and here.

alt text

Solution 3

Have you looked at the Graphics class in java (there is also a Polygon class)? There are draw and fill polygon methods, and each can take in an array of x-coordinates and y-coordinates. By using these arrays, you should be able to change the positions of the points fairly easily. Like you could change all of them equally for a re-size, or copy and paste by just moving all points equally. Changing the color is as easy as filling it with a new color and repainting.

Share:
11,362
juFo
Author by

juFo

Updated on June 04, 2022

Comments

  • juFo
    juFo almost 2 years

    I need to draw custom shapes. Now when a user clicks on several points on the panel I create a shape using a polygon.

    public void mouseClicked(MouseEvent e) {
                polygon.addPoint(e.getX(), e.getY());
                repaint();
            }
    

    But I don't know if this is the best way to draw custom shapes.

    It should be possible to edit a drawn shape:

    • resize
    • change its fill color
    • change the stroke color
    • copy/paste it
    • move a single point of the polygon
    • ...

    I have seen people creating an own class implementing the Shape class and using a GeneralPath. But again I have no idea if this is a good way.

    Now I can create my own shape with a polygon (or with a GeneralPath) but I have no clue how to attach all the edit functions to my own shape (the edit functions I mean the resize, move, etc from above).

    I hope somebody could show me a way to do this or maybe write a little bit of code to demonstrate this.

    Thanks in advance!!

  • juFo
    juFo about 14 years
    And when I want to resize it I want a custom cursor and a bounding box to show me where I can click and drag to resize it, how would I do that?
  • John Kane
    John Kane about 14 years
    You can change the cursor on events. Like you can add a mouse clicked listener, or mouse dragged listener, or something like that. You can set some boolean variable for when you are dragging the mouse in your paint method. So, if you are dragging you can paint your bounding box where you want, or do the same type thing for cutting and pasting.
  • John Kane
    John Kane about 14 years
    blog.codebeach.com/2008/02/using-custom-cursors-in-java.html This is a link showing how to use a custom cursor.
  • John Kane
    John Kane about 14 years
    Here is an example of using a mouse motion listener: leepoint.net/notes-java/examples/mouse/20dragdemo.html
  • juFo
    juFo about 14 years
    wow thanks Wintermute! I am a Java beginner but I will check this code, I guess it is going to help me a lot! Going to investigate now how this code works and try to modify it so it will fit my needs. (And frameworks, well I rather don't use them now ;) )
  • Wintermut3
    Wintermut3 about 14 years
    Thank you trashgod I was a bit quick with it--I did make sure it still compiled--as I did abstract this from a rather complex GUI frameworks I wrote a while back, plus I am still getting the hang of the edit abilities that this site offers still. juFo: Your very welcome. Over all I am, and have been for years, a champion of Java as a language. To that end any chance I can get someone to as take it as more than just a way of getting to a 'quick' solution but wish to truly understand it as a whole solution to most problem spaces.