I have a JPanel for which I set some image as the background. I need to draw a bunch of circles on top of the image. Now the circles will be positioned based on some coordinate x,y, and the size will be based on some integer size. This is what I have as my class.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
class ImagePanel extends JPanel {
private Image img;
CircleList cList; //added this
public ImagePanel(Image img) {
this.img = img;
Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
setSize(size);
setLayout(null);
cList = new CircleList(); //added this
}
public void paintComponent(Graphics g) {
g.drawImage(img, 0, 0, null);
cList.draw(null); //added this
}
}
How can I create some method that can performed this?
Your approach can be something similar to this, in which you use a class CircleList to hold all the circles and the drawing routine too:
class CircleList
{
static class Circle
{
public float x, y, diameter;
}
ArrayList<Circle> circles;
public CirclesList()
{
circles = new ArrayList<Circle>();
}
public void draw(Graphics2D g) // draw must be called by paintComponent of the panel
{
for (Circle c : circles)
g.fillOval(c.x, c.y, c.diameter, c.diameter)
}
}
Easiest thing to do would be to place something along these lines into your paintComponent method.
int x = ...;
int y = ...;
int radius = ...;
g.drawOval(x, y, radius, radius);
Well, you will probably want to create an ArrayList to store the information about the circles to be drawn. Then when the paintComponent() method is invoked you just loop through the ArrayList and draw the circles.
Custom Painting Approaches shows how this might be done for a rectangle. You can modify the code for an oval as well you would probably add methods to update the Array with the location information rather than by doing it dynamically.
Have you looked at JXLayer? It's an awesome library that allows you to layer special painting on top of any GUI element in an obvious way. I believe that will be included in the main java libraries for JDK7
Related
I've got a class called Shape which inherits from JPanel.
A number of sub-classes in turn extend the Shape classes, one for each type of shape.
Each shape has its own overriden paint() method, which draws the respective shape.
I would like to be able to click on any shape, and am trying to implement this logic for now. Please note that each shape has been added to an arrayList.
However, the contains statement always returns false, even when I have clearly clicked inside the shape.
Any ideas?
Never override paint() in JPanel rather paintComponent(..)
Im not quite sure I understand however I made a short example which I hope will help.
Basically it is a simple JFrame with a DrawingPanel (my own class which extends JPanel and the shapes are drawn on). This panel will create shapes (only 2 for testing) add them to an ArrayList and draw them to the JPanel via paintComponent(..) and a for loop, it also has a MouseAdapter to check for user mouseClicked(..) evnets on the JPanel. When a click is made we iterate through each Shape in the ArrayList and check whether the Shape contains the point or not, and if so prints its class name and uses instance of to check what type of Shape is clicked and prints appropriate message:
Output (after clicking both shapes):
Clicked a java.awt.geom.Rectangle2D$Double
Clicked a rectangle
Clicked a java.awt.geom.Ellipse2D$Double
Clicked a circle
ShapeClicker.java:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeClicker {
public ShapeClicker() {
JFrame frame = new JFrame();
frame.setTitle("Shape Clicker");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
initComponents(frame);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//create frame and components on EDT
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ShapeClicker();
}
});
}
private void initComponents(JFrame frame) {
frame.add(new ShapePanel());
}
}
//custom panel
class ShapePanel extends JPanel {
private Shape rect = new Rectangle2D.Double(50, 100, 200, 100);
private Shape cirlce = new Ellipse2D.Double(260, 100, 100, 100);
private Dimension dim = new Dimension(450, 300);
private final ArrayList<Shape> shapes;
public ShapePanel() {
shapes = new ArrayList<>();
shapes.add(rect);
shapes.add(cirlce);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
for (Shape s : shapes) {
if (s.contains(me.getPoint())) {//check if mouse is clicked within shape
//we can either just print out the object class name
System.out.println("Clicked a "+s.getClass().getName());
//or check the shape class we are dealing with using instance of with nested if
if (s instanceof Rectangle2D) {
System.out.println("Clicked a rectangle");
} else if (s instanceof Ellipse2D) {
System.out.println("Clicked a circle");
}
}
}
}
});
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
for (Shape s : shapes) {
g2d.draw(s);
}
}
#Override
public Dimension getPreferredSize() {
return dim;
}
}
If you are implementing Shape you have to implement the contains method yourself. The default implementation for Shape always returns false.
If your Shape is bounded by curves that you know how to intersect (or determine if a point is on one or the other side), you can use the even-odd rule. Cast a ray from the point tested in any direction not parallel to a straight line. If the number of intersections is odd, the point is inside. If the number of intersections is even, the point is outside.
The built-in classes implement this method, so you can use/extend the Polygon, Ellipse2D.Double or RoundRectangle2D.Double class and have a filled polygon / ellipse / round rectangle that knows its inside.
I am trying to add a border to a Rectangle element and for some reason it will not work, is it not compatible with JFrame? I can set my entire JFrame to having a border, but it can't find setBorder with my rectangles. Here is my code:
package trivia;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.border.Border;
#SuppressWarnings("serial")
public class Main extends JFrame{
boolean mainMenu = true;
static Color tan = Color.decode("#F4EBC3");
static Color darkGreen = Color.decode("#668284");
static Color buttonColor = Color.decode("#A2896B");
Rectangle header = new Rectangle(0, 0, 500, 100);
Rectangle body = new Rectangle(0, 100, 500, 400);
Rectangle start = new Rectangle(150, 150, 200, 40);
public Main() {
setTitle("Trivia Game!");
setSize(500, 500);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
#Override
public void paint(Graphics g) {
Dimension d = this.getSize();
Border blackline;
blackline = BorderFactory.createLineBorder(Color.black);
if(mainMenu = true){
g.setColor(darkGreen);
g.fillRect(header.x, header.y, header.width, header.height);
g.setFont(new Font("Courier", Font.BOLD, 24));
g.setColor(Color.BLACK);
drawCenteredString("Trivia Game!", d.width, 125, g);
g.setColor(tan);
g.fillRect(body.x, body.y, body.width, body.height);
g.setColor(buttonColor);
g.fillRect(start.x, start.y, start.width, start.height);
}
}
public void drawCenteredString(String s, int w, int h, Graphics g) {
FontMetrics fm = g.getFontMetrics();
int x = (w - fm.stringWidth(s)) / 2;
int y = (fm.getAscent() + (h- (fm.getAscent() + fm.getDescent())) / 2);
g.drawString(s, x, y);
}
public static void main(String[] args) {
#SuppressWarnings("unused")
Main m = new Main();
}
}
And when I add this in my paint function:
start.setBorder(blackline);
It gives me the error:
The method setBorder(Border) is undefined for the type Rectangle
I am not sure how I can make it recognize the setBorder function, can anyone help? All help is much appreciated!
Rectangle does not have a setBorder method, instead, set the color of the Graphics context using Graphics#setColor(Color) and either use Graphics#drawRect(int, int, int, int) or Graphics2D#draw(Shape)
You're breaking the paint chain. Painting is made up of a series of chained method calls, which when called correctly, paint the current component and its child components. By not calling super.paint you're preventing from doing this and could cause any number of nasty side effects, none of which you really want...
You should avoid overriding paint of top level containers, like JFrame, for a number of reasons; they're not double buffered; there a bunch of other components sitting on top of the frame which may paint over it; etc. Instead, create a custom component, extending from something like JPanel and override it's paintComponent method instead (ensuring that you call super.paintComponent first)...
See Painting in AWT and Swing,
Performing Custom Painting and 2D Graphics for more details
Sounds like you're trying to draw the rectangle referenced by start. In that case, you want to be invoking a method on a Graphics, not on a Rectangle. So:
g.drawRect(start.x, start.y, start.width, start.height);
I was writing a program with some custom rendering, and needed to render a rectangle with a border. I decided to simply call graphics2D.fillRect(), switch to the border color, and call graphics2D.drawRect(). However, even though I make these calls back to back with the same coordinates and sizes, fillRect() does not always fill in the entire area contained by drawRect when the color I'm drawing with is translucent (has alpha). Furthermore, the area painted by fillRect() is sometimes outside of the area contained by drawRect(). Why do these two methods draw things in different places when given different colors?
Here is an example to demonstrate the problem. Clicking the mouse in the window will switch between drawing the fill with alpha and without. Notice that there is a row of pixels at the bottom of the rectangle that is white when drawing with alpha, but that row of pixels is not there when drawing without alpha.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ColorWithAlpha extends JPanel {
private boolean hasAlpha = true;
private static final long serialVersionUID = 1L;
/**
* #param args
*/
public static void main(String[] args) {
// setup a basic frame with a ColorWithAlpha in it
JFrame frame = new JFrame();
JPanel panel = new ColorWithAlpha();
panel.setPreferredSize(new Dimension(500, 500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.show();
}
public ColorWithAlpha() {
super();
setBackground(Color.WHITE);
this.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent arg0) {
// when the user clicks their mouse, toggle whether we are drawing a color with alhpa or without.
hasAlpha = !hasAlpha;
ColorWithAlpha.this.repaint();
}
#Override
public void mouseEntered(MouseEvent arg0) {}
#Override
public void mouseExited(MouseEvent arg0) {}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
});
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = new Color(100, 100, 250);// this color doesnt have an alpha component
// some coordinates that demonstrate the bug. Not all combinations of x,y,width,height will show the bug
int x = -900;
int y = 1557;
int height = 503;
int width = 502;
if (hasAlpha) { // toggle between drawing with alpha and without
color = new Color(200, 100, 250, 100);
}
Graphics2D g2 = (Graphics2D) g;
// this is the transform I was using when I found the bug.
g2.setTransform(new AffineTransform(0.160642570281124, 0.0, 0.0, -0.160642570281124, 250.0, 488.0));
g2.setColor(color);
g2.fillRect(x, y, width, height);
g2.setColor(Color.DARK_GRAY);
g2.setStroke(new BasicStroke(8f));
g2.drawRect(x, y, width, height);
}
}
Scrap that answer, I reread your question and copied your code and found what your talking about. The small white line is due to a round-off error in the painting. Very interesting little problem. Add this after creating your Graphics2D
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Rendering hints tell the painting class how you want certain procedures to work. I have no idea why adding transparency to the color would make the roundoff different. I figure it must have to do with multiple rendering hints combining together like antialiasing.
What I want to do:
Create a JPanel's subclass to draw a simple overlay on top of contained components.
Why don't I use JLayeredPane?
See JComponent#isOptimizedDrawingEnabled().
When a JMenu is present in a JFrame, adding a JPanel with an overridden paintChildren(Graphics) method, an incorrect coordinate starting point is provided in the passed Graphics object, as observed with this code sample:
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public final class Sscce {
public static void main(String[] args) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
// a normal frame
JFrame f = new JFrame();
// set up a simple menu
JMenuBar mb = new JMenuBar();
JMenu m = new JMenu("Test");
JMenuItem mi = new JMenu("Whatever");
m.add(mi);
mb.add(m);
f.setJMenuBar(mb);
// a panel with a simple text overlay over components.
// works much faster than JLayeredPane, which doesn't have
// isOptimizedDrawingEnabled()
JPanel p = new JPanel() {
#Override
public void paint(Graphics g) {
// I'm not so stupid to draw stuff here
super.paint(g);
// JavaDoc: delegates to paintComponent, paintBorder, paintChildren
// in that order
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// it is common knowledge that children are painted after parent
Graphics tmp = g.create();
try {
tmp.setColor(Color.MAGENTA);
tmp.fillRect(0, 0, getWidth(), getHeight());
} finally {
tmp.dispose();
}
}
#Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
// draw some text
FontMetrics fm = g.getFontMetrics();
// will be drawn outside panel; under menu
g.drawString("TEST TOP/LEFT", 0 + getX(), 0 + getY());
final String s = "TEST BOTTOM/RIGHT";
// will be drawn noticeably above the bottom
g.drawString(s,
getWidth() - fm.charsWidth(s.toCharArray(), 0, s.length()),
getHeight() - fm.getHeight());
}
};
// add something to the panel
p.add(new JTextArea(10, 15));
f.add(p);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
} catch (Throwable t) {
// this is a SSCCE
}
}
}
The first string is drawn outside of JPanel (under the JMenu), even though both coordinates are non-negative.
The second string is NOT drawn at the bottom right corner. It is pushed up by the height of the JMenu.
Image
Even though:
When AWT invokes this method, the Graphics object parameter is pre-configured with the appropriate state for drawing on this particular component:
The Graphics object's color is set to the component's foreground
property.
The Graphics object's font is set to the component's font
property.
The Graphics object's translation is set such that the
coordinate (0,0) represents the upper left corner of the component.
The Graphics object's clip rectangle is set to the area of the
component that is in need of repainting.
Programs must use this Graphics object (or one derived from it) to render output. They are free to change the state of the Graphics object as necessary.
What am I doing wrong?
The first string is drawn outside of JPanel (under the JMenu), even though both coordinates are non-negative. The second string is NOT drawn at the bottom right corner. It is pushed up by the height of the JMenu.
In both cases, note that drawString() expects the coordinates to represent the baseline of the String. The font;s ascent and descent are useful in this context. It may be a coincidence that mb.getHeight() and fm.getHeight() are of comparable magnitude.
#Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
// draw some text
FontMetrics fm = g.getFontMetrics();
// will be drawn outside panel; under menu
g.drawString("TEST TOP/LEFT", 0, fm.getAscent());
final String s = "TEST BOTTOM/RIGHT";
// will be drawn noticeably above the bottom
g.drawString(s, getWidth() - fm.stringWidth(s),
getHeight() - fm.getDescent());
}
I have a custom JPanel and sometimes throughout my program, I need to call a method which paints the screen black, that's it.
public void clearScreen() {
Graphics g = getGraphics();
g.setColor(Color.black);
g.fillRect(0,0,getWidth(),getHeight());
}
When I launch the program, I call this method.
However, I find that sometimes it works, and sometimes it doesn't. It's very odd. I also found out that when it doesn't work, the graphics object is NOT null, and the width and height are also correctly defined (from getWidth() and getHeight()).
Why would this sometimes work and sometimes not work?
What is the correct way to make a custom drawing on my JPanel at some point in the program? Is it correct to use getGraphics() as I am doing? My JPanel (at some point) has JComponents, but later on I remove those JComponents and do some custom graphics drawing. Why would this sometimes only work?
Don't get your Graphics object by calling getGraphics on a component such as a JPanel since the Graphics object obtained will not persist on the next repaint (which is likely the source of your problems).
Instead, consider doing all of your drawing in a BufferedImage, and then you can use getGraphics() to your heart's content. If you do this, don't forget to dispose of the Graphics object when you're done painting with it.
e.g.,
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class MyPaint extends JPanel {
public static final int IMG_WIDTH = 400;
public static final int IMG_HEIGHT = IMG_WIDTH;
private BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGHT,
BufferedImage.TYPE_INT_ARGB);
public MyPaint() {
MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
addMouseListener(myMouseAdapter);
addMouseMotionListener(myMouseAdapter);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (image != null) {
g.drawImage(image, 0, 0, null);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(IMG_WIDTH, IMG_HEIGHT);
}
public void clearScreen() {
Graphics g = image.getGraphics();
g.setColor(Color.black);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
g.dispose();
repaint();
}
private class MyMouseAdapter extends MouseAdapter {
// code to draw on the buffered image.
// Don't forget to call repaint() on the "this" JPanel
}
}