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'm currently attempting to paint to a JComponent several times from a public method which calls the "paintComponent" method in order to place several shapes onto the JComponent, as if it had been painted to within a local context. (Don't ask why.) Whenever I attempt this, however, the JComponent is wiped before the new shape is drawn, leaving only the new shape. Is there a way the contents of the JComponent can be held over between calls of paintComponent, or is there an entirely different approach to take which achieves the same result?
No matter the solution, I need to be able to draw any given shape onto the JComponent using a public method, and have the contents of the JComponent be maintained between additions.
Here's my code for the JComponent in question:
package imageProcessor.elements;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class ImageElement extends JComponent {
private Shape shapeToBeDrawn;
private Color shapeColor;
public ImageElement(int width, int height) {
this.setSize(width, height);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D graphics = (Graphics2D)g;
if (shapeToBeDrawn != null) {
graphics.setColor(shapeColor);
graphics.fill(shapeToBeDrawn);
}
}
public void drawRect(int x, int y, int width, int height, Color color) {
shapeToBeDrawn = new Rectangle2D.Double(x, y, width, height);
shapeColor = color;
repaint();
}
}
I'm new to Java and I want to try some graphic things with it. I want to generate two circles with two different colors and different positions. My code:
Paint Class:
package de.test.pkg;
import javax.swing.*;
public class paint {
public static void main(String[] args) throws Exception{
JFrame frame = new JFrame("Titel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Circle d = new Circle();
Circle r = new CircleRed();
frame.add(d);
frame.add(r);
frame.setSize(600,200);
frame.setVisible(true);
}
}
Circle class
package de.test.pkg;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class Circle extends JPanel {
private double iconRadius = 100;
private Color defaultColor = new Color(89,104,99);
private int positionX = 0;
private int positionY = 0;
private Ellipse2D iconBody = new Ellipse2D.Double(getPositionX(),getPositionY(),iconRadius,iconRadius);
public Icon(){
}
public Color getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(Color defaultColor) {
this.defaultColor = defaultColor;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
super.paintComponent(g2d);
this.setBackground(new Color(255,255,255));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(getDefaultColor());
g2d.draw(iconBody);
g2d.fill(iconBody);
}
}
CircleRed class
package de.design.pkg;
import java.awt.Color;
public class CircleRed extends Circle {
private Color defaultColor = Color.RED;
private int positionX = 120;
private int positionY = 0;
public CircleRed() {
}
public Color getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(Color defaultColor) {
this.defaultColor = defaultColor;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
}
The Result is that the Circles have the same positions.
My questions are:
Why do they have the same positions?
Is this a good way to do that or are there better solutions? I want to use a class so please don't gave me answers with do all that paint thing in the Main.
Is there a better way to hold the position. Maybe in an array? But how should the setter and getter look like if I want to return array[position]?
If I want to set the Position from the Main function. How can I do this?
The Result is that the Circles have the same positions.
(1) Why do they have the same positions?
Not really. The result is that only CircleRed is displayed. Your problem here is not with painting, it's with adding components to a container with a suitable layout manager. The lines
Circle d = new Circle();
Circle r = new CircleRed();
frame.add(d);
frame.add(r);
add r instead of d. This is because JFrame uses BorderLayout by default and you are replacing the center component d with r the line after. Just to show the point, add the line
frame.setLayout(new GridLayout(1, 2));
(2) Is this a good way to do that or are there better solutions? I want to use a class so please don't gave me answers with do all that paint thing in the Main.
It depends on what you are aiming to do. I would venture a guess that if you want to practice inheritance, it would be better for you to create an abstract class for a general circle with shared code and then subclass it with concrete implementation and specific code for the various types. Otherwise, you can just create a single customizable circle class and instantiate that one with different parameters.
It's not a good practical approach in any case because the placement of the circles (which are JPanels) will be determined by the layout manager. The painting only determines the location of the painted shape in the panel. It would be better to just paint the shapes on a single big panel and not with using multiple panels.
There are a few very good answers on the site about moving components around.
(3) Is there a better way to hold the position. Maybe in an array? But how should the setter and getter look like if i want to return array[position]?
There are effectively 2 positions in your design. One is for the panels in the frame, the other is for the shapes in the panels.
For the latter, I would use a Point or just an int x, y fields in the class itself. Getters and setters are the standard ones, the setters will control the position (you will need to call repaint() though).
For the first, it is determined by the layout manager and you don't (shouldn't) control it in a pixel-prefect way. You just instruct the layout manager with "guidelines" and it does the calculations for you.
(4) If I want to set the Position from the Main function. How can i do this?
Again, depends on which position you are talking about. See above.
What your doing is very overkill for just creating two colored circles. You can just use the paint method in java.awt
public void paint(Graphics g){
g.setColor(Color.YELLOW);
g.fillOval(20,20,160,160);
g.setColor(Color.RED);
g.fillOval(60,60,80,80);
}
fillOval takes the following parameters (int x, int y, int width, int height)
You can use g.setColor(Color.NAME) to change the color of your circle. Just call this method before your draw calls.
They're in the same position because the paintComponent() method in Circle is being used for both - and it's using the position variables defined in Circle. When you have CircleRed extend Circle, you don't need to define the position variables again - you inherit them from Circle. Instead, call the setPosition() methods on CircleRed. (You don't need to define these either, they're also inherited.)
There are a variety better solutions, I think the biggest improvement would be to improve your use of inheritance.
That's a fine way to hold the position. If anything else, you could also use a Point object. (Already in Java)
To set the position from the main function, you just call for example
Circle c = new Circle();
c.setPositionX(120);
c.setPositionY(40);
You are going wrong way. You can simply draw two circles by overriding paint method of JFrame class.
Here is a sample demo program.
import java.awt.*;
/**
*
* #author Pankaj
*/
public class TestFrame extends javax.swing.JFrame {
/**
* Creates new form NewJFrame
*/
public TestFrame() {
initComponents();
setTitle("Graphics Demo");
setSize(200,200);
}
/**
* You need to override this method.
*/
#Override
public void paint(Graphics g) {
int X1 = 10; //X coordinate of first circle
int X2 = 60; //X coordinate of second circle
int Y1 = 100; //Y coordinate of first circle
int Y2 = 100; //Y coordinate of second circle
int width = 50; //Width of the circle
int height = 50; //Height of the circle
Color color1 = Color.RED; //Color of first circle
Color color2 = Color.BLUE; //Color of second circle
g.setColor(color1);
g.fillOval(X1, Y1, width, height);
g.setColor(color2);
g.fillOval(X2, Y2, width, height);
}
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setResizable(false);
pack();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
}
And here's the output
In java how can you make a game fully realizable! But so logic and graphics can work with it? I have tried using SCALE methods. But this doesn't allow perfect full-screen for every computer. So I made this:
public void resize(int WIDTH, int HEIGHT, boolean UNDECORATED) {
frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame.setMaximumSize(new Dimension(WIDTH, HEIGHT));
frame.setMinimumSize(new Dimension(WIDTH, HEIGHT));
this.WIDTH = WIDTH;
this.HEIGHT = HEIGHT;
frame.setUndecorated(UNDECORATED);
frame.setSize(WIDTH, HEIGHT);
}
So you can set your screen size to whatever you want! It works but the graphics will not work with it? Is there a way in Graphics2D to stretch all the graphics so it fits? For example if there was a method that existed like:
G2D.resize(WIDTH, HEIGHT, Image.NEAREST_PARENT_RESCALE);
Any idea?
Things I have tried:
Drawing all graphics to a Buffered-image then drawing that Image onto the screen size.
Just using SCALE and doing WIDTH * SCALE etc.
Lots of math
Things I do not mind
If you have a WIDE-SCREEN it stretches graphic2D objects to the size.
If you have a SQUARE-SCREEN it squishes graphics2D objects to the size.
So how can I make a perfectly resealable game using Graphics2D, JFrame.
In the most generic form, one can consider this as a classical problem of graphics programming, namely, as the transformation from world coordinates to screen coordinates. You have an object that has a size of "1.0 x 1.0" in your world coordinate system (regardless of which unit this has). And this object should be painted so that it has a size of, for example, "600 pixels * 600 pixels" on the screen.
Broadly speaking, there are at least three options to achieve this in Swing:
You can draw into an image, and then draw a scaled version of the image
You can draw into a scaled Graphics2D object
You can draw scaled objects
Each of this has possible advantages and disadvantages, and hidden caveats.
Drawing into an image, and drawing a scaled version of the image:
This might look like a simple solution, but has a potential drawback: The image itself has a certain resolution (size). If the image is too small, and you are scaling it up to fill the screen, it may appear blocky. If the image is too large, and you are scaling it down to fit into the screen, pixels of the image may be lost.
In both cases, there are several tuning parameters for the process of scaling the image. In fact, scaling an image is far more tricky than it looks at the first glance. For details, one may refer to the article The Perils of Image.getScaledInstance() by Chris Campbell.
Drawing into a scaled Graphics2D object
The Graphics2D class already offers the full functionality that is necessary to create the transformation between the world coordinate system and the screen coordinate system. This is accomplished by the Graphics2D class by internally storing an AffineTransform, which describes this transformation. This AffineTransform may be modified directly via the Graphics2D object:
void paintSomething(Graphics2D g) {
...
g.draw(someShape);
// Everything that is painted after this line will
// be painted 3 times as large:
g.scale(3.0, 3.0);
g.draw(someShape); // Will be drawn larger
}
Some care has to be taken to properly manage the transform that is stored in the Graphics2D object. In general, one should create a backup of the original AffineTransform before applying additional transformations, and restore this original transform afterwards:
// Create a backup of the original transform
AffineTransform oldAT = g.getTransform();
// Apply some transformations
g.scale(3.0, 4.0);
g.translate(10.0, 20.0);
// Do custom painting the the transformed graphics
paintSomething(g):
// Restore the original transformation
g.setTransform(oldAT);
(Another advice for the last method: The Graphics2D#setTransform method should never be used to apply a new coordinate transform on top of an existing transform. It is solely intended for restoring an "old" transform, as shown in this example (and in the documentation of this method)).
One potential drawback of scaling with the Graphics2D class is that afterwards, everything will be scaled. Particularly, this scaling will also affect line widths (that is, the width of the Stroke). For example, consider a sequence of calls like this one:
// By default, this will paint a line with a width (stroke) of 1.0:
g.draw(someLine);
// Apply some scaling...
g.scale(10.0, 10.0);
// Now, this will paint the same line, but with a width of 10.
g.draw(someLine);
The second call will cause a line to be drawn that is 10 pixels wide. This may not be desired in many cases. This effect can be avoided with the third alternative:
Drawing scaled objects
The transformation between the world coordinate system and the screen coordinate system can also be maintained manually. It is convenient to represent this as an AffineTransform. The AffineTransform class can be used to create transformed versions of Shape object, that can then be drawn directly into an (un-transformed) Graphics2D object. This is accomplished with the AffineTransform#createTransformedShape method:
void paintSomething(Graphics2D g) {
...
// Draw some shape in its normal size
g.draw(someShape);
// Create a scaling transform
AffineTransform at = AffineTransform.getScaleInstance(3.0, 3.0);
// Create a scaled version of the shape
Shape transformedShape = at.createTransformedShape(someShape);
// Draw the scaled shape
g.draw(transformedShape);
}
This is probably the most versatile approach. The only potential drawback is that, when many small, simple shapes are drawn, this will cause many, small temporary transformed shapes to be created, which may cause reduced performance. (There are ways to alleviate this problem, but detailed performance considerations and optimizations are beyond the scope of this answer).
Summary
The follwing image shows the comparison of all approaches. Some example objects (represented as Shape objects) are drawn. Each row compares the three different scaling methods mentioned above. With their "default" size, the objects fill a rectangle in world coordinates that has a size of 100x100. In the first two rows, they are scaled up to fill an area on the screen of 190x190 pixels. In the last two rows, they are scaled down to fill an area on the screen of 60x60 pixels. (These sizes have been chosen in order to have some "odd" scaling factors of 1.9 and 0.6. Certain effects (artifacts) may not appear when the scaling factors are whole numbers, or exactly 0.5, for example).
For the upscaling and the downscaling, there additionally is a comparison between the "standard" way of painting, and "high quality" painting (indicated by the "(HQ)" in the title of each panel). The "high quality" here simply means that the rendering hints
KEY_ANTIALIAS = VALUE_ANTIALIAS_ON
KEY_RENDERING = VALUE_RENDER_QUALITY
have been set:
Here is the corresponding program, as an MCVE:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScalingMethodComparison
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new GridLayout(0,1));
Dimension larger = new Dimension(190,190);
Dimension smaller = new Dimension(60,60);
f.getContentPane().add(createPanel(larger, false));
f.getContentPane().add(createPanel(larger, true));
f.getContentPane().add(createPanel(smaller, false));
f.getContentPane().add(createPanel(smaller, true));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static JPanel createPanel(Dimension d, boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,3));
for (ScalingMethodComparisonPanel.ScalingMethod scalingMethod :
ScalingMethodComparisonPanel.ScalingMethod.values())
{
p.add(createPanel(d, scalingMethod, highQuality));
}
return p;
}
private static JPanel createPanel(
Dimension d, ScalingMethodComparisonPanel.ScalingMethod scalingMethod,
boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,1));
p.setBorder(BorderFactory.createTitledBorder(
scalingMethod.toString()+(highQuality?" (HQ)":"")));
JPanel scalingMethodComparisonPanel =
new ScalingMethodComparisonPanel(
createObjects(), d, scalingMethod, highQuality);
p.add(scalingMethodComparisonPanel);
return p;
}
// Returns a list of objects that should be drawn,
// occupying a rectangle of 100x100 in WORLD COORDINATES
private static List<Shape> createObjects()
{
List<Shape> objects = new ArrayList<Shape>();
objects.add(new Ellipse2D.Double(10,10,80,80));
objects.add(new Rectangle2D.Double(20,20,60,60));
objects.add(new Line2D.Double(30,30,70,70));
return objects;
}
}
class ScalingMethodComparisonPanel extends JPanel
{
private static final Color COLORS[] = {
Color.RED, Color.GREEN, Color.BLUE,
};
enum ScalingMethod
{
SCALING_IMAGE,
SCALING_GRAPHICS,
SCALING_SHAPES,
}
private final List<Shape> objects;
private final ScalingMethod scalingMethod;
private final boolean highQuality;
private final Dimension originalSize = new Dimension(100,100);
private final Dimension scaledSize;
private BufferedImage image;
public ScalingMethodComparisonPanel(
List<Shape> objects,
Dimension scaledSize,
ScalingMethod scalingMethod,
boolean highQuality)
{
this.objects = objects;
this.scaledSize = new Dimension(scaledSize);
this.scalingMethod = scalingMethod;
this.highQuality = highQuality;
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(scaledSize);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0,0,getWidth(), getHeight());
if (highQuality)
{
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
if (scalingMethod == ScalingMethod.SCALING_IMAGE)
{
paintByScalingImage(g);
}
else if (scalingMethod == ScalingMethod.SCALING_GRAPHICS)
{
paintByScalingGraphics(g);
}
else if (scalingMethod == ScalingMethod.SCALING_SHAPES)
{
paintByScalingShapes(g);
}
}
private void paintByScalingImage(Graphics2D g)
{
if (image == null)
{
image = new BufferedImage(
originalSize.width, originalSize.height,
BufferedImage.TYPE_INT_ARGB);
}
Graphics2D ig = image.createGraphics();
paintObjects(ig, null);
ig.dispose();
g.drawImage(image, 0, 0, scaledSize.width, scaledSize.height, null);
}
private void paintByScalingGraphics(Graphics2D g)
{
AffineTransform oldAT = g.getTransform();
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
g.scale(scaleX, scaleY);
paintObjects(g, null);
g.setTransform(oldAT);
}
private void paintByScalingShapes(Graphics2D g)
{
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
AffineTransform at =
AffineTransform.getScaleInstance(scaleX, scaleY);
paintObjects(g, at);
}
private void paintObjects(Graphics2D g, AffineTransform at)
{
for (int i=0; i<objects.size(); i++)
{
Shape shape = objects.get(i);
g.setColor(COLORS[i%COLORS.length]);
if (at == null)
{
g.draw(shape);
}
else
{
g.draw(at.createTransformedShape(shape));
}
}
}
}
This is actually quite easy in Java. In a Graphics2d environment, the logical coordinate system (the coordinates you use in the drawing routines) and the physical coordinate system (the coordinates as they appear) on the screen are completely unrelated. Every time you draw onto a Graphics2d object, the logical coordinates are first translated to the physical coordinates by an AffineTransform object, and this AffineTransform object can be modified. For this you can use the Graphics2D.scale(double,double), Graphics2D.rotate(double), Graphics2D.translate(double,double) and Graphics2D.shear(double,double) methods.
So if you first call
g2d.scale(2.0,2.0);
then all your graphics that you subsequently draw will be twice as large in both directions.
If I understood you correctly all you want is to draw your graphics in different resolutions without removing or adding any content.
Well one of the "things you have tried" can do that.
Drawing to a fixed size BufferedImage will ensure that all your components are visible within that BufferedImage (assuming you draw them correctly and relative to it's fixed size) then you can just draw the image to your flexible size screen.
Here's a full runnable code example that does that:
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test extends Canvas implements Runnable {
// fixed size for the image
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
private BufferedImage image;
private boolean running;
private Thread t;
public Test(Dimension dims) {
super();
setPreferredSize(dims); // actual screen size
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
running = false;
}
public synchronized void start() {
if (running)
return;
t = new Thread(this);
running = true;
t.start();
}
public synchronized void stop() {
if (!running)
return;
running = false;
boolean retry = true;
while (retry) {
try {
t.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void render() {
// draw to your image
Graphics2D g2d = (Graphics2D) image.getGraphics().create();
g2d.fillRect((WIDTH / 2) - 25, (HEIGHT / 2) - 25, 50, 50);
g2d.dispose();
// draw the image to your screen
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
g2d = (Graphics2D) bs.getDrawGraphics().create();
g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g2d.dispose();
bs.show();
}
public void run() {
// approximately sync rendering to 60 FPS don't use it as it is.
// there are much better ways to do this.
long startTime = System.currentTimeMillis();
long frameTime = 1000 / 60;
long tick = 0;
while (running) {
while ((System.currentTimeMillis() - startTime) > tick) {
render();
tick += frameTime;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test test = new Test(new Dimension(800, 600));
JFrame frame = new JFrame("Fit to screen");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
test.stop();
frame.dispose();
super.windowClosing(e);
}
});
frame.getContentPane().add(test);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
test.start();
}
});
}
}
This is only a quick implementation there are things that can be done better in that code bu you get the picture. Hope this helps.
Maybe this will help :
Scaling graphics2D that contains basic shapes has a drawback : thickness of lines are doubled if the scale is doubled, that's a problem in an application implementing a zoom feature...
The only way I found is to make the preferred size of the container bigger and then, draw the shapes.
Here's a zoom function using mouse wheel and the pixel of the object pointed by the mouse stays under the mouse pointer.
It took me a long time to figure out how to do that properly, but I finally found out...(the application is an astrolabe and I wanted to zoom in and out)
The graphics2D belongs to a JPanel that is contained in the bottom part of a JSplitPane :
public void mouseWheelMoved(MouseWheelEvent e) {
Dimension dim = new Dimension(), oldDim = this.getPreferredSize();
double newX, newY;
Rectangle rect, oldRect;
if(this.mousewheel >= 0){
this.mousewheel += -e.getWheelRotation() * this.mousewheelSensibility;
}
else {
this.mousewheel = 0;
}
dim.setSize(this.astro.splitBottomDimension.getWidth() + this.mousewheel, this.astro.splitBottomDimension.getHeight() + this.mousewheel);
oldRect = this.getVisibleRect();
this.mouseX = e.getX();
this.mouseY = e.getY();
this.setPreferredSize(dim);
newX = this.mouseX / oldDim.getWidth() * dim.getWidth();
newY = this.mouseY / oldDim.getHeight() * dim.getHeight();
rect = new Rectangle((int)newX - (this.mouseX - oldRect.x), (int)newY - (this.mouseY - oldRect.y), oldRect.width, oldRect.height);
this.scrollRectToVisible(rect);
this.revalidate();
I am making a molecule designing application. I can draw the lines and circles, but it clears the old lines each time you click, so basically, you can only design molecules with 2 atoms.
Also, the mouseEvents don't deliver if you click very fast which is also a problem.
Here is the code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class MoleculeDesigner extends JComponent implements MouseListener {
private Point op, cp;
private boolean first = true;
public static final Color linecolor = new Color(0, 255, 0);
private static final long serialVersionUID = 1L;
private BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
public MoleculeDesigner() {
JFrame f = new JFrame("Molecule Designer");
f.setBackground(Color.WHITE);
f.addMouseListener(this);
f.add(this);
f.setSize(100, 100);
f.setDefaultCloseOperation(3);
f.setVisible(true);
}
public static void main(String[] args) {
new MoleculeDesigner();
}
#Override
protected void paintComponent(Graphics g) {
if(op != null && cp != null) {
Graphics2D g2 = img.createGraphics();
super.paintComponent(g2);
g2.setColor(linecolor);
g2.drawLine((int) op.getX(), (int) op.getY(), (int) cp.getX(), (int) cp.getY());
g2.setColor(Color.BLACK);
g2.fillOval((int) cp.getX(), (int) cp.getY(), 10, 10);
op = (Point) cp.clone();
g2.dispose();
}
}
#Override
public Dimension getPreferredSize() {
return getParent().getMaximumSize();
}
#Override
public void mouseClicked(MouseEvent e) {
if(!first) {
cp = e.getPoint();
cp.setLocation(cp.getX(), cp.getY() - 8);
}
else {
op = e.getPoint();
first = false;
}
repaint();
}
#Override public void mousePressed(MouseEvent e) {}
#Override public void mouseReleased(MouseEvent e) {}
#Override public void mouseEntered(MouseEvent e) {}
#Override public void mouseExited(MouseEvent e) {}
}
All help appreciated!
Either 1) draw in a BufferedImage which is then displayed inside of your paintComponent override, or 2) put your data into an ArrayList or other collection, and then iterate through the collection inside of paintComponent. I'd do the latter if I needed the data for other purposes. Also, never ever do this:
public void update(Graphics g) {
paintComponent(g);
}
This is not how Swing graphics are supposed to be done and is potentially dangerous code. Please read:
Basic Swing Graphics Tutorial
Advanced Swing Graphics Information
Edit
More detail regarding option 1:
Create a BufferedImage using one of its constructors.
Do your drawing on the image.
When you need to draw, get a Graphics object from the BufferedImage using getGraphics() or createGrahpics() (for a Graphics2D object)
Draw with this Graphics object
Then dispose() the Graphics object.
Then call repaint() to ask the JVM to repaint the component.
Draw the image in your paintComponent method by calling g.drawImage(...), passing in your buffered image.
Benefits: often the drawing is quicker, and I often use this to draw background images.
Drawbacks: the data points are not available, and so if you need to do manipulation or animation of your data points, this is not the way to go.
You don't, nor should you.
paint in Swing is a destructive process, this is the way it was designed. That is, there is an expectation that when you component is requested to paint itself, it will clean up the Graphics context before painting anything (this is slightly different for transparent components though).
Swing has no concept of what was painted on your component before and because the Graphics context is shared amongst all the components been painted, unless you clear the graphics first, you could end up with unwanted paint artifacts
Possible solutions might include...
Painting to some kind of backing buffer (such as a BufferedImage), which you use the paintComponent method to draw. This is limited in the fact that it just acts like a paint program, painting pixels to the image. You will also need to provide functionality when the size of the viewable area changes, as the BufferedImage won't know.
Place each object you wanted painted into some kind of List and iterate this list when paintComponent is called. This is a little more flexible in that you can control the order of the objects drawn, remove objects and insert new ones where you like
So I'm working on a small exercise in programming Java, I've created a couple classes that simply draws a black background, and then, from the center, draws circles a radius apart from eachother in 90 degree rotations (code at bottom of post).
Now, just using a "random" seed to determine direction of travel, this always trends downward. Every time. So what I want to do now, is try and show my circles where the other circles are, and give them an inclination to bend their movement towards another circle (without existing ON another circle other than the parent). I can handle that part, but I want an efficient means of communicating location between my "nodes" or "circles". I could build a string array with x,y and read through that list each time I am constructing my seed, but this seems like an inefficient way of doing it. I would much rather find some way where a circle can look around it and find other circles within proximity. Is there any way to do this? I don't mind some reading and homework, I'm mostly looking for a good strategy to start looking at.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class FlowerOfLife extends JFrame {
private Circle Origin = null;
public FlowerOfLife() {
// Default layout manager is BorderLayout.
// Adding graphic component to center section will expand
// it to fill the available space so it does not need to
// provide any size hints for this parent container now.
GraphicPanel graphicPanel = new GraphicPanel();
add(graphicPanel); // default center section
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400,400);
setLocation(200,200);
setVisible(true);
}
/**
* Container method draws container and passes graphics context
* on to components for them to draw themselves. You can draw
* over everything in the content pane with this method...
*/
public void paint(Graphics gr)
{
Graphics2D g = (Graphics2D)gr;
// see what happens when you remove/move this next line
super.paint(g);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth();
int h = getHeight();
g.setPaint(Color.white);//First there is one...
Origin = new Circle(g, w/2, h/2, ((w+h)/2)/35);
g.setPaint(Color.white.darker());
Circle[] circleArray = new Circle[100];
for(int i=0;i<circleArray.length;i++){
circleArray[i] = new Circle(g,i>0?circleArray[i-1]:Origin);
}
}
public static void main(String[] args)
{
new FlowerOfLife();
}
}
class Circle{
public int x;
public int y;
public int r;
private Circle next;
private Circle last;
private int seed;
public Circle(Graphics2D g, Circle c){
c.next = this;
this.last = c;
this.r = c.r; //Copy radius
//We set these now, and modify the necessary ones.
this.x = c.x;
this.y = c.y;
this.r = c.r;
//Move the new circle into position based on seed.
this.seed = (int) (Math.random()*4);
if(this.seed==0){//Stay here, and grow a little...
// this.r+=1;
}else if(this.seed==1){//Move left, by r.
this.x-=c.r;
}else if(this.seed==2){//Move up, by r.
this.y+=c.r;
}else if(this.seed==3){//Move right,by r.
this.x+=c.r;
}else{//(this.seed==4) //Move down, by r.
this.y-=c.r;
}
sleep((int)(Math.random())*9000+100);//Random(ish) life.
//Draw the new circle
g.draw(new Ellipse2D.Double(x,y,r*2,r*2));
}
public Circle(Graphics2D g,int x, int y, int r){
/**
* The ellipse draws from the designated point, down and right.
* The below permits the center to be declared in a more natural
* way. Used for the first declaration.
**/
this.last = null; //Parent.
this.x = x-r;
this.y = y-r;
this.r = r;
this.seed = 0;
g.draw(new Ellipse2D.Double(x,y,r*2,r*2));
}
private void sleep(int ms){
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class GraphicPanel extends JPanel
{
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.black);
g2.fill(new Rectangle2D.Double(0,0,getWidth(),getHeight()));
}
}
Your circles can exist on a "board", which would be a two-dimensional array. You could then give each circle a view of the board. You could also have the board determine where to put the next circle.