JPanel Object Oriented Graphics - java

In my computer science class we are learning Java and we have come to the point in the course where we are learning about object orientation and how it is useful. We started a project a couple days ago, however instead of being tasked with basic object orientation, my teacher decided to challenge me and a few others by creating graphics right away without really teaching us.
We were tasked with creating a "Molecules" program that would accept an integer to create an array, and for each available section in the array, create a random x and y coordinate, as well as a size for the radius. Using these variables, a oval would be created. My teacher also told us that we should do this using either JFrame or JPanel, I chose JFrame.
In the code below you can see my attempt at this and where I am getting stuck. I have commented out the portion of setting the different colours because it is not important to this case.
/**
* Name: Dylan Eisen
* Date: May 1, 2017
* Project: Object Oriented
* Program: Molecules.java
*/
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class Molecules extends JFrame
{
static Graphics g;
public Molecules(Graphics g, int x, int y, int size)
{
super.paint(g);
}
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("Enter the number of elements: ");
int num = in.nextInt();
int x = 20, y = 20, size = 20;
object elements[] = new object[num];
for(int i = 0; i < elements.length; i++)
{
x = (int)(Math.random()*1600);
y = (int)(Math.random()*900);
size = (int)(Math.random()*100);
elements[i] = new object(g, x, y, size);
}
Molecules f = new Molecules(g, x, y, size);
f.setSize(1600, 900);
f.setVisible(true);
//f.getContentPane().setBackground(Color.BLACK);
f.setTitle("Molecules Program - Dylan Eisen");
}
}
class object
{
Graphics g;
int x, y, size;
public object(Graphics g, int x, int y, int size)
{
this.x = x;
this.y = y;
this.size = size;
}
void paint(Graphics g)
{
//g.setColor(Color.WHITE);
g.fillOval(x, y, size, size);
}
}
If anyone could help me but also explain to me where I am going wrong, and how to fix this in the future, it would be really helpful!

If anyone could help me but also explain to me where I am going wrong, and how to fix this in the future, it would be really helpful!
The first problem is, you don't seem to understand how painting actually works in Swing. Painting is a little bit of black magic, it seems like Swing "magically" calls the paint methods. It's not overly complex, but I would highly recommend having a look at Painting in AWT and Swing and Performing Custom Painting to better understand how the painting process works and how you can intergrate with it.
My teacher also told us that we should do this using either JFrame or JPanel, I chose JFrame.
I would recommend using a JPanel as the base component, you can add this to whatever container you want later, which provides for a much more flexible solution.
As general recommendation, you should override the paintComponent of JPanel. If you find yourself overriding paint, you're probably doing something wrong.
You may also find that you have other issues with JFrame, as it's painting process is not double buffered and could also be interfered with by other components contained within the frame itself
A JFrame is actually a container for the JRootPane, which contains the contentPane, JMenuBar and glassPane
When you override paint of JFrame, the other components can paint over your stuff without you been notified (they can can be painted independently)
Generally speaking, a Graphics context is a abstract concept of a series of routines which can generate a image. Swing passes the Graphics context used to paint the window through the paint methods, you should never maintain a reference to this context and you should only use it when the paint method is called. This means that your object class won't need a constructor which needs a Graphics context, it should only ever be painted within the context of a paint method/cycle.
object is a really, really bad name of a class, as Java defines a Object class of it's own, which all other classes extended from by default (if they don't specify a parent class)
I'd also highly recommend that you take a look at Code Conventions for the Java Programming Language. It will make it easier for other people to read your code and for other people to read yours

Related

Graphics 2D, values from a different class and calling the paint function within my frame

In my main class i have built a JFrame and added functionality to a button which gets stock values from yahoos sites. Im currently storing the stock values in an arraylist and passing it to the other class, but thats not the problem. My problem is i dont know how to call the "paintComponent" method from my frame, because of the "Graphics g" variable. I think im missing the big picture here, am i on the right tracks or should i be looking to solve this problem some other way?
here is my graph class:
public class graph {
private static Main main;
public void paintComponent(Graphics g) {
Graphics2D graph2 = (Graphics2D)g;
graph2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
ArrayList<String> first = main.getFirst();
int x1 = 0;
int x2= 1;
for (String x : first) {
String[] parts = x.split(",");
int y1 = Integer.parseInt(parts[0]);
int y2= Integer.parseInt(parts[1]);
Shape drawLine = new Line2D.Float(y1, x1, y2, x2);
graph2.draw(drawLine);
}
}
}
You are not supposed to call this method by yourself. graph (which should be called Graph) has to extend a Swing component so that you just have to add it as a child of one of your containers to display it.

Graphical Components

I have done a program that numerically solves a set of differential equations which describes how an "arbitrary" illness move in an isolated and constant population, it was a programming assignment from a class I took a while ago. What I've done to extend it is to add some graphical components that can pause, reset and "play" the simulation, as well as some components that allows me to change some constants in the equations.
All this was an exercise in programming as I find it to be fun and exciting and want to become better.
However, at the moment I'm stuck, what I want to do now is to make a very simple form of animation of it. I want to visualize the data I get for the number of infected, susceptibles and resistants in a grid as points. I managed to create the grid and have an idea of how to place the dots.
The problem I have is how to draw the dots as the program is working, I can draw one dot in the grid but only as the grid is created, that's it. I need to be able to create a dot at a specific place in the grid, this goes on until the number of dots reaches a finite number, say 30. At that points I want to have the first dot, the one the left, removed, all the dots shifted to the left and place the new dot at the furthest right of the grid, the whole thing is then repeated.
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason. I've read through my course literature on Java, but despite the extensive sections where he explains most of the different graphical components he does not say that much about those methods, only that you don't call for the paintComponent() method, it is done automatically.
If there is something unclear let me know and I'll try to clarify it.
Thanks in advance.
//
Fox Mulder
I think I will be able to figure it out with some help/hints about the paintComponent() method and whether I need to use repaint() method at all, I can't get my head around these for some reason.
Basically, say you create a custom component by extending JPanel. When you #Override the paintComponent() method, it get implicitly called for you, so you never have to call it. So what ever you paint inside the method, gets drawn on your surface. For example
public class DrawingPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
When you call repaint() you are basically causing the paintComponent method to be call implicitly. So to answer your question, Yes you will need to call it if you want to animate, as you will need to update some kind of variable (like the x and y) in the paintComponent() method, to see any change in the drawing.
You can see more at Performing Custom Painting
Not to handle the actual animation, you'll want to use a javax.swing.Timer. You can see more at How to use Swing Timers. Here's the basic construct
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the time to delay between ticks(in this case animations) and the ActionListener listens for "ticks". Each tick, the actionPerformed of the ActionListener is called. There, you can put the code to update any variables you use for animation.
So for example you update the x and y, in the actionPerformed, then call repaint()
public class DrawingPanel extends JPanel {
int x = 50;
int y = 50;
public DrawingPanel() {
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
y += 5;
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
}
Now this was just a simple example. But in your case you want to animate a scatter plot. So what you can do is have a list of Points and in the actionPerformed you can add pull points from that list and push them into another list that is to be drawn. So say you have this
List<Point> originalPoints;
List<Point> pointsToDraw;
...
#Override
protected void paintComponent(Grapchics g) {
super.paintComponent(g);
for (Point point : pointsToDraw) {
g.fillOval(point.x - 5, point.y - 5, 10, 10);
}
}
Basically all the points in pointsToDraw list will be drawn. Initially it will be empty. And in the timer, you can add to the list, until the originalPoints list is exhausted. For example.
List<Point> originalPoints;
List<point> pointsToDraw;
private int currentIndex = 0;
public DrawingPanel(List<Point> originalPoints) {
this.originalPoints = originalPoints;
pointsToDraw = new ArrayList<>();
Timer timer = new Timer(40, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
if (currentIndex == originalPoints.size() - 1) {
((Timer)e.getSource()).stop();
} else {
pointsToDraw.add(originalPoints.get(currentIndex));
currentIndex++;
}
repaint();
}
});
timer.start();
}
So basicall you just keep a current index. When the index reaches the size of the original list, you stop the timer. Otherwise you just pop from the originalPoints and push to the pointsToDraw. For every point you add the pointsToDraw, a repaint() is called, and there will be another point for the paintComponent to draw a circle with.
The END
UDPATE
I just reread your question, and I think I have have misunderstood it. If you want all the points drawn, then basically just have one list. And draw all the points initially. with each tick, just remove the first index, advance all the rest up an index, and add a new one to the end. Though this is the implementation of a LinkedList so you may just want to use that

Filling circles with color

I am trying to run a program that fills the circles with color I am not sure what I am doing incorrectly I am getting a cannot find symbol error for my fillOval command here is my code. Also the fill should be the same color as the drawn circle.
import javax.swing.JApplet;
import java.awt.*;
import java.util.Random;
public class Drawing extends JApplet
{
public void paint(Graphics page)
{
Random generator=new Random();
float r = generator.nextFloat();
float g = generator.nextFloat();
float b = generator.nextFloat();
Color randomColor = new Color(r, g, b);
int random,randomx,randomy;
int x,y;
int width, height;
setBackground(Color.white);
random=generator.nextInt(24)+8;
randomx=generator.nextInt(24);
randomy=generator.nextInt(24);
x=randomx;
y=randomy;
width=random*2;
height=random*2;
page.drawOval(x, y, width, height);
page.setColor(randomColor);
fillOval(x, y,width,height);
}
}
fillOval is a method of Graphics rather than your custom Drawing class.
fillOval(x, y, width, height);
should be
page.fillOval(x, y, width, height);
Problems:
You're calling fillOval by itself and not on any variable, such as the page variable.
You should not override the paint method of any component except in unusual circumstances.
You should not draw directly in a top-level window such as a JApplet.
You're posting un-formatted all left-justified code, making it somewhat difficult to read and understand.
I suggest:
Create a class that extends JPanel and override its paintComponent(Graphics g) method.
Don't forget to call the super's paintComponent method, super.paintComponent(g), in your paintComponent override method, so that the component does all its housekeeping painting before doing your painting.
Draw with its Graphic object that is passed into the method's parameter.
Add the JPanel to your JApplet to display it.
When posting code here, please format your posted code by giving it proper indentations, usually 3 spaces per block, and making sure that all code on the same block is on the same indentation level. Your cooperation in this would be greatly appreciated and will likely improve your chances of getting a decent and prompt answer. Remember that we are all volunteers and thus you should strive to make helping you as easy as possible.

Keeping the rectangle within JFrame

I know I am being an idiot and that's why I can't figure it out but I am trying to paint a bunch of rectangles with randoms size and position using paintComponent. I am trying to make sure that all of them are painted within the frame. I am able to do it with the following code (snippet) but I am wondering if there is a better way to do it than me hardcoding numbers into the program. Is there a method that I should take a look at that might be what I'm looking for?
Here's the inner class that overrides the paintComponent() method:
class DrawPanel extends JPanel {
public void paintComponent(Graphics g) {
int red = (int)(Math.random()*256);
int blue = (int)(Math.random()*256);
int green = (int)(Math.random()*256);
g.setColor(new Color(red, blue, green));
//The following 4 lines keep the rects within the frame
//The frame is 500,500
int ht = (int)(Math.random()*400);
int wd = (int)(Math.random()*400);
int x = (int)(Math.random()*100);
int y = (int)(Math.random()*100);
g.fillRect(x,y,ht,wd);
}
}
You should base your co-ordinates & sizes on the DrawPanel component size. Also using Random.nextInt instead of Math.random() will make it easier to keep within range based on the current size of the panel:
Random random = new Random();
int ht = random.nextInt(getHeight());
int wd = random.nextInt(getWidth());
int x = random.nextInt(getWidth() - wd);
int y = random.nextInt(getHeight() - ht);
I am wondering if there is a better way to do it than me hardcoding numbers into the program.
Yes there is
call getSize() on the enclosing JPanel, the DrawPanel, so you can see the actual boundaries of the component that is being drawn upon. (edit: or getWidth() and getHeight() as recommended by Lucas -- 1+ to his answer!).
Also, you will usually want to call the super's paintComponent(...) method within the child's override.
Also you will usually want to do your randomization elsewhere, such as in the DrawPanel's constructor so as not to change your rectangles each time you re-size the GUI.
There is a method to get the length and width of the panel you are working in.
getHeight();
getWidth();
These will return the current size of the JPanel you are working in, meaning if you resize the window it'll actually still draw them inside.
http://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#getWidth()

How can I repaint efficiently when using big custom component in Swing?

I have made a custom component (derived from JComponent) which represents
a draggable Bezier-curve.
(looks like a hanging cable, someone might know it
from Bender or Cubase)
My problem is: The curve may become really long,
let's say from top left to bottom right corners of the desktop.
This makes Swing's repaint functionality inefficient:
The area of the curve is perhaps few hundred pixels, but the area of
the component (being mostly 'transparent') is millions of pixels big.
My subjection impression is:
The longer the curve, the more flicker I get when dragging it.
I hope I made myself clear about the problem.
Perhaps it would help when I somehow could choose by myself, which regions
of the component needs repainting at all.
EDIT:
Such a mess! I'm profiling the application using Netbeans, which helps to
find inefficient code normally, but this Swing framework is making hundreds
of nested calls! I just can't figure out, what is slow and why.
By the way, disabling super.paint(...) or super.paintComponent(...) doesn't help.
Check out Filthy Rich Clients by Chet Haase and Romain Guy. They address these very optimizations among others along the way to producing responsive and graphically impressive UI.
Doing all of your bezier mathematics on the paint thread everytime the component is refreshed is (as you've gathered) a bad idea. Does your curve change often? If not then why not paint it to a BufferedImage as and when it changes, and change your paint() code to simply draw the buffered image to the component instead.
class CurveComponent extends JComponent {
private BufferedImage image;
#Override
public void paintComponent( Graphics g ) {
if ( image == null ) {
return;
}
g.drawImage( image, 0, 0, this );
}
private void updateCurve() {
image = new BufferedImage( getWidth(), getHeight(), BufferedImage.ARGB );
Graphics g = image.getGraphics();
// draw the curve onto image using g.
g.dispose();
}
}
Only call updateCurve() when you need to and all that expensive mathematics won't be needlessly repeated. Painting should be pretty responsive, even for a fullscreen window. drawImage() will be doing a straightforward memory copy and should be lightning fast.
Try writing a tiny test app, which consists of nothing except what you need to reproduce this problem. This will make profiling easier. Then post that app here, so we can take a look at possible solutions.
I found your question interesting so I wrote a test app myself. This draws a Bezier curve which is continually resized as you drag. I created a gradient background to ensure this works well with a nasty background. I get good performance and low flicker, although I use top-notch machine.
It pays to read "Filthy Rich Clients" to learn all the tricks of writing custom Swing components that perform really well.
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
public class CustomComponent extends JComponent {
private Point2D start = new Point2D.Double(0, 0);
private Point2D end = new Point2D.Double(300, 200);
private CustomComponent() {
this.setOpaque(true);
final MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
setEnd(e.getPoint());
}
};
this.addMouseListener(mouseAdapter);
this.addMouseMotionListener(mouseAdapter);
}
public void setStart(Point2D start) {
this.start = start;
repaint();
}
public void setEnd(Point2D end) {
this.end = end;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw gradient background
final int width = getWidth();
final int height = getHeight();
g2.setPaint(new GradientPaint(0, 0, Color.WHITE, width, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);
// draw Bezier curve
final Shape shape = new CubicCurve2D.Double(start.getX(), start.getY(), start.getX(), end.getY(), end.getX(), start.getY(), end.getX(), end.getY());
g2.setColor(Color.BLACK);
g2.draw(shape);
g2.drawString("Click and drag to test for flickering", 100, 20);
}
public static void main(String[] args) {
final CustomComponent component = new CustomComponent();
final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
final Dimension size = new Dimension(screenSize.width - 20, screenSize.height - 100);
component.setPreferredSize(size);
final JFrame frame = new JFrame();
frame.add(component);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Some things to note:
only overwrite paintComponent(Graphics g), not the other paintXXX() methods
set custom component to opaque if possible
only use repaint() to request repainting. Never directly order a repaint directly in your code. This lets Swing handle it well.
There is no efficient way to create lots of small clip rectangles for a diagonal structure which leaves you with two strategies to avoid flickering:
Double buffering. This needs an enormous amount of memory but the memory copy is very fast (it usually happens in the time the "electron beam" goes back from lower right to upper left ... if there was still a beam in your LCD).
Don't call super.paint() (which draws or "erases" the background) and draw the curve a second time with the background color to erase it.
For more details, see this document.
[EDIT] If fillRect() wasn't abstract, you could set a break point :) Set a break point in paint(), check who calls it and whether the background got cleared at that time. It should be since rendering would be completely wrong. Then set break points further up in the call chain.
You can redraw a smaller portion of the screen using repaint(Rectangle r)
http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)
Then you mention flicker. Since you are using swing, which uses double buffering your flickering must be coming from something else. Are you clearing the screen in paintComponent(...)? I.e. call to fillRect(...)? Don't do that, it's not needed (IIRC).
Which method do yo use to paint your curve? paint or paintComponent?
My solution was a partial re-design:
Now I don't represent each "cable"-element by a component.
Now, cables are just dummy objects (with no involved JComponent).
The repaint takes place "globally", on the content pane of the parent JFrame.
Now it's efficient, and flickers less.
just use getVisibleRect(); inside paintComponent(Graphics g) to get the area you actually need to redraw

Categories