Understanding paintComponent - java

UPDATE 19/01/14(for people who has issues like this one)
The problem I described under originated from the fact that my circle class extends JPanel. So every circle I create from this class is in fact its own panel. Therefore by adding a layout I could see both circles. I've learned now that its Wiser to have a separate "board class" that extends JPanel, and the let the circles, in this case, extend nothing. That way I can implement a drawMySelf method in the circleclass and draw them in my board. This way I can have various geometric objects in the same panel!
UPDATE END.
I'm doing a task where I need to draw two circles. These circles are supposed to relocate randomly when I resize my frame. And needs to have a line between their centers which states the distance. I'm getting trouble with my class for the circles. When I add my custom circles to my testprogram, only one of them appears. I can't figure out why. I think there's an error in my code making the program skip some of it. because I only get one circle to appear. Can anyone see what is wrong with this code?
Keep in mind I'm supposed to use the tools I have learned so far. JPanel, JFrame, Overriding paintComponent().
The circle Class:
package oppgave4;
import javax.swing.*;
import java.awt.*;
public class Circle extends JPanel {
public static final int OVAL = 1;
public static final int ANOTHEROVAL = 2;
public int OVALCENTER = 0;
public int ANOTHEROVALCENTER = 0;
private int type = 1;
private boolean filled = false;
public Circle(){
}
public Circle(int type){
this.type = type;
}
public Circle(int type, boolean filled){
this.type = type;
this.filled = filled;
}
protected void paintComponent(Graphics g){
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
int randomWidth = (int)(width * Math.random());
int randomHeight = (int)(height * Math.random());
switch (type){
case OVAL:
g.setColor(Color.BLUE);
if(filled)
g.fillOval(randomWidth, randomHeight, 30, 30);
else
g.drawOval(randomWidth, randomHeight, 30, 30);
case ANOTHEROVAL:
g.setColor(Color.RED);
if(filled)
g.fillOval(randomWidth, randomHeight, 30, 30);
else
g.drawOval(randomWidth, randomHeight, 30, 30);
break;
}
}
}
And the test program: When I run this, only the red circle will appear.
package oppgave4;
import javax.swing.*;
import java.awt.*;
public class TestProgram extends JFrame {
public TestProgram(){
add(new Circle(Circle.OVAL));
add(new Circle(Circle.ANOTHEROVAL));
}
public static void main(String[] args) {
TestProgram sirkel = new TestProgram();
sirkel.setSize(400, 300);
sirkel.setLocationRelativeTo(null);
sirkel.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
sirkel.setVisible(true);
}
}

Try adding them with a layout manager besides the default BorderLayout and see what happens
import java.awt.*;
public TestProgram(){
setLayout(new GridLayout(1, 2);
add(new Circle(Circle.OVAL));
add(new Circle(Circle.ANOTHEROVAL));
}
Another solution would just be to use the default BorderLayout of the JFrame. When you just .add(something), it will get added automatically to the BorderLayout.CENTER, unless you specify the position. A BorderLayout and only hold one component in each position. So when you try and add a second one, Only the second one will appear in the CENTER position.
If you did
public TestProgram(){
setLayout(new GridLayout(1, 2);
add(new Circle(Circle.OVAL), BorderLayout.CENTER);
add(new Circle(Circle.ANOTHEROVAL), BorderLayout.SOUTH);
}
it would also work

First off, by making each Circle a JPanel, you are going to have unexpected results because two JPanels cannot draw to the same place. Rather, they are placed my the LayoutManager. In this case, I would imagine that one of your panels is on top of the other, so you're only seeing one.
If you want the circles to be in the same "panel" (they can overlap) then you will need one JPanel which can draw multiple circles.
If you want them to be "side-by-side" in separate panels, I'd look at GridLayout

Related

Which coordinates does fillRect() use?

This is my first project with AWT/Swing. I'm trying to design a simple cellular automaton. I had some problems choosing a layout manager, now I'm using GridLayout because is the closest I got to what I want. However, when a try to place a cell in the JPanel, the coordinates do not work as I expected. Maybe I should not be extending from JComponent and using fillRect()? Or maybe GridLayout is not the layout I need? The main problem is that the point (0,0) seems to be "moving". Is fillRect conflicting with GridLayout?
Note 1: I've tried GridBagLayout but did not work (because I have no idea how to configure it). I've also tried the add(component, x, y) method but it did not work.
Note 2: I did not post the code regarding the State of the Cell because it was not relevant.
Edit: Ok, I wrote an example in a single public class, I don't think I can be more concise and reproduce the same results.
Solution: https://docs.oracle.com/javase/tutorial/uiswing/painting/refining.html
This is my code:
public class Example{
class Cell extends JComponent{
private int x = 0; //Cell position ?
private int y = 0;
public Cell(int x, int y){
this.x = x;
this.y = y;
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
//draw cell
g.setColor(Color.white);
g.fillRect(x,y,15,15);
}
}
Example(){
JFrame frame = new JFrame("title");
frame.setBackground(Color.black);
frame.getContentPane().setPreferredSize(new Dimension(300,300));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
JPanel box = new JPanel(new GridLayout(20,20)){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
setBackground(Color.black);
//draw grid
for(int i = 0; i <= this.getHeight(); i += 15){
g.drawLine(0,0+i,getWidth(),0+i);
}
for(int i = 0; i <= this.getWidth(); i += 15){
g.drawLine(0+i,0,0+i,getHeight());
}
}
};
/*box.add(new Cell(0,0)); //TEST 1
box.add(new Cell(0,0));
box.add(new Cell(0,0));
box.add(new Cell(0,0));*/
box.add(new Cell(0,0)); //TEST 2
box.add(new Cell(15,0));
box.add(new Cell(30,0));
box.add(new Cell(45,0));
frame.add(box);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args){
new Example();
}
}
And this are the results corresponding to TEST 1 and TEST 2:
TEST 1
TEST 2
All painting is done relative to the component that contains the custom painting, not the panel you add the component to.
So in your case just do the painting from (0, 0).
The layout manager will position the Cell at the location determined by the layout manager.
Note:
A painting method is for painting only. It should NEVER create a component, as your current Box class is doing.
The basic logic is to:
create your panel with the desired layout.
add components to the panel.
the size/location of the components added to the panel will be determined by the layout manager. So in your Cell class you need to implement the getPreferredSize() method so the layout manager can use this information to position each component that is added to the panel.
If you want to manage the painting at different locations on the panel, then don't use real components. Instead you keep an ArrayList of shapes that you want to paint. Each shape will contain the location it should be painted. Then you iterate through the ArrayList in the paintComponent() method to paint each shape. For an example of this approach check out the Draw On Component example found in Custom Painting Approaches.

Only One Thread Paints in my JFrame object, why not the other? [duplicate]

This question already has answers here:
Why does the first panel added to a frame disappear?
(2 answers)
Closed 5 years ago.
I've been trying all day long to make this happen with no success. What can be going wrong?
I want 2 threads printing simultaneously in my JFrame:
Thread-1: Prints Squares
Thread-2: Prints Circles
I'am ending up with only one thread printing on the JFrame. The other get executed but don't print in the JFrame.
Look, only squares are getting printed:
This is my main class:
public class Main {
public static void main(String[] args) {
FigurePlacer circle = new FigurePlacer("circle");
FigurePlacer square = new FigurePlacer("square");
JFrame window = new JFrame();
window.add(circle);
window.add(square);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setTitle("Task");
window.setSize(700, 700);
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
This is the Threading Class:
import java.awt.Graphics;
import javax.swing.JPanel;
import java.awt.Color;
import java.util.Random;
public class FigurePlacer extends JPanel implements Runnable{
String figure;
final int width = 700;
final int height = 700;
int x_pos = 0;
int y_pos = 0;
int x_width = 50;
int y_height = 50;
public FigurePlacer(String str){
figure = str;
randomCoord();
Thread th = new Thread (this);
th.start();
}
private void randomCoord(){ //this ramdomize x,y coord to place a new object
Random random = new Random();
x_pos = random.nextInt(width);
y_pos = random.nextInt(height);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK); //sets the black color in background
g.fillRect(0, 0, 700, 700);
System.out.println(figure);
switch (figure){
case "square":
g.setColor(Color.GREEN);
g.fillRect(x_pos, y_pos, x_width, y_height);
break;
case "circle":
g.setColor(Color.BLUE);
g.fillOval(x_pos, y_pos, x_width, y_height);
break;
}
}
#Override
public void run(){ //paints the objects
while (true){
randomCoord();
paintImmediately(x_pos, y_pos, x_width, y_height);
try{
Thread.sleep (50);
}
catch (InterruptedException ex){}
}
}
}
JFrame window = new JFrame();
window.add(circle);
window.add(square);
The default layout manager for a JFrame is the BorderLayout. When you add a component to a panel using the BorderLayout and don't specify a constraint then the component goes to the CENTER. However only one component can ever be displayed in the CENTER, so only the last component added is painted.
You could use the OverlayLayout, which allows you to stack two panels on top of one another. Of course you will need to make the top panel non-opaque.
The easier solution is to not attempt to use two panels, just create on panel that can display circles or squares.
Also, your painting code is wrong. You should not be using paintImmediately(...) to do painting. The paintComponent() method should paint every object every time the method is invoked. Try resizing your frame and you will see that all your object disappear since the paintComponent() method will clear all the old paintings.
See Custom Painting Approaches for the two common approaches for this kind of painting.

(Java) Can anyone explain to me why my code does not draw an oval in my GUI

I am designing a GUI that has a circle in the centre which will be filled with a different colour everytime the program is run. I have used the paint(graphics g) method to do this. When I run the following code I am just left with the blank window and no circle, can anyone explain to me why this is? I based my code off a video tutorial.
package weekThree;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class taskTwo {
static Random rand = new Random();
public static void main(String[] args) {
JFrame window = new JFrame("Task Two");
JPanel pane = new JPanel();
pane.setLayout(new FlowLayout());
window.setContentPane(pane);
pane.paint(null);
window.setBackground(Color.WHITE);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(300,300);
window.setVisible(true);
}
public void paint(Graphics g) {
float red = rand.nextFloat();
float green = rand.nextFloat();
float blue = rand.nextFloat();
Color randomColor = new Color(red, green, blue);
g.drawOval(50, 50, 25, 25);
g.setColor(randomColor);
g.fillOval(50, 50, 25, 25);
}
}
Thanks
Let us make class taskTwo extend class JPanel and over-ride it's paintComponent() function. You don't need to call this function explicity. It will be called by default when a new JPanel object is created.
Make the following changes and let me know if this doesn't work out:
public class taskTwo extends JPanel
{ //extended JPanel so that we can over-ride the paintComponent() function in it.
//all your code for creating JFrame and adding panel to it.
//replace public void paint() with painComponent()
public void paintComponent(Graphics g)
{
float red=rand.nextFloat();
float green=rand.nextFloat();
float blue=rand.nextFloat();
Color randomColor=new Color(red,green,blue);
g.drawOval(50,50,25,25);
g.setColor(randomColor);
g.fillOval(50,50,25,25);
}
}
Time to clear some doubts.
What is paintComponent()?
The paintComponent() by default contains the design features for any swing component. paintComponent() is a function available for Swing Components. JPanel is a swing component.
Why not use JFrame?
The paintComponent() wouldn't effect JFrame because JFrame is not a component.
Why extend JPanel?
Simple Inheritance. Everytime you create a JPanel object, the default paintComponent() function in the JPanel class is called. The thing is you don't actually see it. When you extend JPanel, the paintComponent() which you have created is called instead of the default one (over-riding).
You forgot to call your method.
paint();

How to draw vertical line in Swing

I am able to draw a horizontal line but unable to draw a vertical line. Please help me.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
class Success extends JFrame{
public Success(){
JPanel panel=new JPanel();
getContentPane().add(panel);
setSize(450,450);
JButton button =new JButton("press");
panel.add(button);
}
public void paint(Graphics g) {
super.paint(g); // fixes the immediate problem.
Graphics2D g2 = (Graphics2D) g;
Line2D lin = new Line2D.Float(20, 40, 850, 40);
g2.draw(lin);
}
public static void main(String []args){
Success s=new Success();
s.setVisible(true);
}
}
Thanks in advance.
Keep the x co-ordinates the same and change value of y co-ordinates as shown below
Line2D lin = new Line2D.Float(20, 40, 20, 150);
First two values are the (x1,y1) value of the starting point of the line and last two values (x2,y2) end point of the line. Now I hope you understand why your code produced a horizontal line and what needs to be done to draw vertical line.
I noticed a couple things, some of them were already pointed out:
To answer your question directly, this is what the (x, y) coordinates look like for Swing components
keep x coordinates the same for a vertical line. If you don't know where the x coordinates are in your line constructor when you create it, look at the documentation for the constructor. If you're using Eclipse, this means you should just hover your mouse over the code that contains the constructor.
Your line goes outside the range of your JFrame; instead, if you want it to go from end to end, use the getWidth() and getHeight() methods.
You shouldn't be creating a new line every time you repaint your components. Instead, you should create the line somewhere in you Success class, implement ActionListener so you can update your code every frame, and in that update, resize your line, then leave just the repainting to paintComponent.
You shouldn't override JFrame in this case, and you usually shouldn't have to.
You should override the paintComponent method, not the paint method.
I don't think you're double-buffering correctly, but I can't help you there.
Overriding the getPreferredSize method of JPanel is handy if you want to control its size, but it's not even necessary in this case, because adding it to the JFrame will automatically size it for you.
There's a lot of stuff that goes on in Swing behind the scenes, and it can get confusing because normally you have to say stuff explicitly, but keep playing with this example, and you should be safe for a while.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
class Success extends JPanel implements ActionListener{
private final Timer timer = new Timer(20, this); // Create a timer that will go off every 20 ms
Line2D horizontalLine; // Declare your variables here, but don't initialize them
Line2D verticalLine; // That way, they can be accessed later in actionPerformed and repaint
// You might want to try frame.setResizable(false) if you want your frame
// and your panel to stay the same size.
private final Dimension prefPanelSize = new Dimension(450, 450);
public Success(){
super(); // Call the constructor of JPanel, the class this subclasses.
JButton button =new JButton("press");
this.add(button);
this.setSize(prefPanelSize);
horizontalLine = new Line2D.Float(0, 40, prefPanelSize.width, 40);
verticalLine = new Line2D.Float(prefPanelSize.width / 2, 0,
prefPanelSize.width / 2, prefPanelSize.height);
timer.start(); // Start the timer
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // fixes the immediate problem.
Graphics2D g2 = (Graphics2D) g;
g2.draw(horizontalLine);
g2.draw(verticalLine);
}
#Override
public Dimension getPreferredSize()
{
return prefPanelSize;
}
public static void main(String []args){
Success s = new Success();
JFrame frame = new JFrame();
frame.setVisible(true);
frame.setSize(new Dimension(450, 450));
frame.add(s);
}
// This method is called ever 20 ms because of the timer.
#Override
public void actionPerformed(ActionEvent e) {
int currWidth = getWidth();
int currHeight = getHeight();
horizontalLine.setLine(0, 40, currWidth, 40);
verticalLine.setLine(currWidth / 2, 0, currWidth / 2, currHeight);
}
}

Slide JPanel Content in a JForm on Java

I have a question. I want to make a swing form that, when clicking in a button he slides a panel (with his content) to the left so the panel on the right replaces it with a smooth effect.
I Have tried to do a while how checks the size of the panel and then minimize it and shows the next one like this :
while (jpanelprincipal1.getWidth() < 439 || jpanelprincipal1.getHeight() > 250)
{
int panel1width = jpanelprincipal1.getWidth();
int panel2height = jpanelprincipal1.getHeight();
jpanelprincipal1.setSize(panel1width -- , panel2height --);
jpanelprincipal2.setSize(440,250);
}
I used this trick in C# but with the Application.DoEvent(); (how obviously it's not available on java).
Is there anyway i can make a slide effect of 2 or more panels?
BTW : Sorry for my very bad english !
Thanks In Advance,
Luis Da Costa
he slides a panel (with his content) to the left so the panel on the right replaces it with a smooth effect
You question mentions you want the panel to "slide", but the code looks like you are trying to get the panel to "shrink", so it is replaced by another panel.
Assuming you have two panels each with the same size, then you can "slide" one out of view while the other slides into view.
To do this you an use a panel with a GridLayout. This way each component will be the same size. Then you add the panel to a scrollpane without any scrollbars. The size of the scrollpane will need to be set to the size of the first compnoent. Then you can "slide" the two panels by changing the position of the viewport. So in your Timer you would have code something like:
JViewport viewport = scrollPane.getViewport();
Point position = viewport.getViewPosition();
position.x += 5;
viewport.setViewPosition( position );
You would then stop the Timer when the position is greater than the size of the component.
As suggested by #HFOE, javax.swing.Timer is a good choice for animation. The setDividerLocation() method of JSplitPane can be called from the ActionListener. See How to Use Split Panes for additional options.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
/** #see http://stackoverflow.com/questions/5069152 */
public class SplitPaneTest {
double ratio = 0.5;
double delta = ratio / 10;
private void create() {
JFrame f = new JFrame("JSplitPane");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyPanel p1 = new MyPanel(Color.red);
MyPanel p2 = new MyPanel(Color.blue);
final JSplitPane jsp = new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT, true, p1, p2);
Timer timer = new Timer(200, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ratio += delta;
if (ratio >= 1.0) {
ratio = 1.0;
delta = -delta;
} else if (ratio <= 0) {
delta = -delta;
ratio = 0;
}
jsp.setDividerLocation(ratio);
}
});
f.add(jsp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
timer.start();
}
private static class MyPanel extends JPanel {
Color color;
public MyPanel(Color color) {
this.color = color;
this.setPreferredSize(new Dimension(300, 300));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new SplitPaneTest().create();
}
});
}
}
I would probably do this with a Swing Timer. Change a class field representing the x, y position of the sliding JPanel in the timer's ActionListener and then call repaint on the container holding the JPanels. A JLayeredPane could work well as the container for the sliding JPanels.
Edit 1: regarding your request for code, I think the best thing is for you to try to create a very small compilable runnable program that attempts to do this, and then post your code with an explanation of your program's behavior as an edit to your original post. Also send us a comment to notify us of your changes. Then we can inspect your code, test it, modify it, and help you mold it into a working program. This is called creating a "Short, Self Contained, Correct (Compilable), Example" or SSCCE (please check the link).

Categories