in which order are components added to a JPanel drawn? - java

I have a small application that should demonstrate how the opaque property works in Swing.
however, what throws me off is the order in which paintComponent() is called. I'd come to think components are drawn in the order they were added (whats added first, gets drawn first) however in this example it appears that paintComponent() methods are drawn in reverse order(what's added last got painted first)
can someone explain this behavior, thanks
public class TwoPanels {
public static void main(String[] args) {
JPanel p = new JPanel();
// setting layout to null so we can make panels overlap
p.setLayout(new BorderLayout());
CirclePanel topPanel = new CirclePanel("topPanel1");
// drawing should be in blue
topPanel.setForeground(Color.blue);
// background should be black, except it's not opaque, so
// background will not be drawn
topPanel.setBackground(Color.black);
// set opaque to false - background not drawn
topPanel.setOpaque(false);
topPanel.setBounds(50, 50, 100, 100);
// add topPanel - components paint in order added,
// so add topPanel first
p.add(topPanel);
CirclePanel bottomPanel = new CirclePanel("buttomPanel1");
// drawing in green
bottomPanel.setForeground(Color.green);
// background in cyan
bottomPanel.setBackground(Color.cyan);
// and it will show this time, because opaque is true
bottomPanel.setOpaque(true);
bottomPanel.setBounds(30, 30, 100, 100);
// add bottomPanel last...
p.add(bottomPanel);
// frame handling code...
JFrame f = new JFrame("Two Panels");
f.setContentPane(p);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
// Panel with a circle drawn on it.
private static class CirclePanel extends JPanel {
String objName;
public CirclePanel(String objName) {
this.objName = objName;
}
// This is Swing, so override paint*Component* - not paint
protected void paintComponent(Graphics g) {
System.out.println(objName);
// call super.paintComponent to get default Swing
// painting behavior (opaque honored, etc.)
super.paintComponent(g);
int x = 10;
int y = 10;
int width = getWidth() - 20;
int height = getHeight() - 20;
g.fillArc(x, y, width, height, 0, 360);
}
}
}

From Swing Internals: Paint Order:
What's going wrong under the hood?
A container holds an array with all child components. For painting, Swing (more precise JComponent#paintChildren()) iterates over the array in reverse order - this means the first added component will be painted at last. Z-order modifies the child position within this array. In case that the layout manager makes use of Container#getComponents() (as many Swing core layout managers do) there's no guarantee that the array order represents the order in which the components have been added to the container.
Generally in Swing you can specify the paint order by applying component Z-Order (see Container#setComponentZOrder). This method is useful as long as you're using a null layout or a layout manager which makes use of constraints.
The disadvantage of using #setComponentZOrder is that it can affect component position.

Related

How do I make the lines draw without an actionlistener?

I am writing a GUI that is supposed to write lines and circles to a panel and I am supposed to use sliders to change how fast they add to the panel. I am supposed to add a clear button that will clear the entire panel and then when I move the sliders they should make the circles and lines begin to write on the panel again. There should be a specific stop point at the beginning of the sliders. We have been told to do this without actionlisteners on the sliders. I am having some trouble understanding how to make that work.
Below are the requirements for the assignment:
Write a Swing program that provides the following functionality:
Draw random length lines of random color at random coordinates with pauses between the drawing of each line.
Allow the user to set the length of the pause between lines with a slider. Have the slowest value actually stop drawing lines (i.e., it slows to a stop once it is at that value on the slider).
Have a clear button that clears all the lines & circles. Be sure that the clear button is operational at all times.
Draw random size circles of random color at random coordinates with pauses between the drawing of each circle. (Use draw, not fill.)
Allow the user to set the length of the pause between circles with a slider. Have the slowest value actually stop drawing circles (i.e., it slows to a stop once it is at that value on the slider). This is independent of the lines' speed.
The circles and lines are both drawn independently, each in their own Thread.
Do not use Timer for this, extend Thread and/or Runnable.
public class OhMy extends JFrame
{
private static final int MAX_COLOR = 225;
private static final long STOP_SLEEP = 0;
public OhMy()
{
this.setTitle("Oh My Window");
Container canvas = this.getContentPane();
canvas.setLayout(new GridLayout(2,1));
JPanel panControl = new JPanel(new GridLayout(1,1));
JPanel panDraw = new JPanel(new GridLayout(1,1));
canvas.add(panControl);
canvas.add(panDraw);
panControl.add(createPanControl());
panDraw.add(createPanDraw());
this.setSize(800, 600);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private JPanel createPanControl()
{
JPanel panControl = new JPanel();
JLabel lines = new JLabel("Lines");
panControl.add(lines);
lines.setForeground(Color.RED);
JSlider sldSpeedLines = new JSlider(1, 30, 5);
panControl.add(sldSpeedLines);
JButton btnClear = new JButton("Clear");
panControl.add(btnClear);
btnClear.setForeground(Color.RED);
JSlider sldSpeedCircles = new JSlider(0, 30, 5);
panControl.add(sldSpeedCircles);
JLabel circles = new JLabel("Circles");
panControl.add(circles);
circles.setForeground(Color.RED);
btnClear.addActionListener((e)->
{
repaint();
});
return panControl;
}
private JPanel createPanDraw()
{
JPanel panDraw = new JPanel();
class LinesThread extends Thread
{
#Override
public void run()
{
try
{
Graphics g = panDraw.getGraphics();
while(g == null)
{
Thread.sleep(STOP_SLEEP);
g = panDraw.getGraphics();
}
Random rand = new Random();
int red = rand.nextInt(MAX_COLOR);
int green = rand.nextInt(MAX_COLOR);
int blue = rand.nextInt(MAX_COLOR);
Color color = new Color(red, green, blue);
int x1 = rand.nextInt(panDraw.getWidth());
int y1 = rand.nextInt(panDraw.getHeight());
int x2 = rand.nextInt(panDraw.getWidth());
int y2 = rand.nextInt(panDraw.getHeight());
g.setColor(color);
g.drawLine(x1, y1, x2, y2);
}
catch(InterruptedException e1)
{
//awake now
}
}
}
return panDraw;
}
/**
* #param args
*/
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new OhMy();
}
});
}
}
You state:
"We have been told to do this without actionlisteners on the sliders..."
Good, because JSliders won't accept an ActionListener.
JSliders will accept a ChangeListener though, but you likely don't even need to use this.
Instead, give the clear button an ActionListener (you've no way to get around using ActionListeners at all).
In that ActionListener, reset the drawing and get the values from the JSliders by simply calling getValue() on it.
Don't get your Graphics object by calling getGraphics() on the JPanel since the Graphics object thus obtained will not be stable risking a broken image, or worse, a NullPointerException (to see what I mean, minimize and restore your current application while its drawing).
Instead either draw on a BufferedImage that is displayed in the JPanel's paintComponent method or draw directly in paintComponent itself.
Avoid using a Thread and Thread.sleep, but instead use a Swing Timer -- it's much easier this way to be sure that your code is threading appropriately.
Use this value to adjust the speed of your Swing Timer.
Edit
Thanks to Abishek Manoharan for pointing out problems in my answer...
If the JSliders need to change the speed of drawing while the drawing is proceeding, then you will in fact need to use ChangeListener on the slider.
In that listener change a field that will tell the Thread how long to sleep.
I see that you're also required to use background threads. If so, then be sure to make all Swing calls on the Swing event thread. So if you're in the background thread and need to make a Swing call, then queue it on the Swing event thread by calling SwingUtilities.invokeLater(...) and pass in a Runnable that has your Swing call.

Size of JFrame odd after pack()

I have got this piece of code:
frame.setTitle("SideScroller");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
Dimension dim = new Dimension(1000, 500);
frame.setContentPane(gamePanel); //gamePanel is an extended JPanel
gamePanel.setPreferredSize(dim);
frame.pack();
After I run this code and fill the gamePanel with parts of 50x50p, which should fill the gamePanel and thus the frame completely, there are some empty rows of pixels at the right and bottom of the screen.
frame.getSize(); //gives us: 1016 x 539
frame.getInsets(); //gives: Top: 31 | Left: 8 | Bottom: 8 | Right: 8
An image of the result:
As you can see, there is still some empty room in the JFrame.
The code to fill the gamePanel:
//Fill the panel with landscape
//One part : 50x50 px
//Panel : 1000x500 px
//Width : 20 prts
//Height : 10 prts
#Override
public void paint(Graphics g){
super.paint(g);
for(int i = 0; i < 20; i++){
for(int j = 0; j < 10; j++){
//Decide which landscape-img should be used
switch(levelOne[j][i]){
case 0:g.drawImage(new ImageIcon("resources\\landscape-img\\air.png")
.getImage(), 50 * i, 50 * j, null);
break;
case 1:g.drawImage(new ImageIcon("resources\\landscape-img\\ground.png")
.getImage(), 50 * i, 50 * j, null);
break;
}
}
}
g.dispose();
}
I do not see why the frame isn't filled fully. Hope someone can help me!
Few hints to help you to solve your problem.
1) If you need your game panel have a preferred size, then just override getPreferredSize() like this:
public class GamePanel extends JPanel {
...
#Override
public Dimension getPreferredSize() {
return isPreferredSizeSet()
? super.getPreferredSize() : new Dimension(1000, 500);
}
...
}
See also Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
2) Be aware that you should not override paint() method but paintComponent() instead. See A Closer Look at the Paint Mechanism.
public class GamePanel extends JPanel {
...
#Override
protected void paintComponent(Graphics g) {
// Your implementation here
}
...
}
3) As already suggested just add your gamePanel to the frame's content pane directly:
frame.getContentPane().add(gamePanel);
This way you will take advantage of its default Layout Manager: BorderLayout to make your panel fill the whole center area.
4) Finally, while you can perfectly use Swing to make games it's not the best API to do so because it is designed for desktop applications. You might want to consider use some library intended to games development such as libgdx
Found the answer here. I called frame.setResizable(false) after I called frame.pack(), apparently that messes the frame up. You have to call that before pack() (or `setVisible(true)).

graphics not showing on resized subpanel?

I am having issues with a small project im working on. I am trying to create a Moveable message panel when holding down the mouse button but i am stuck on one part.
I want to place the A small panel with a size of 50x30 pixels that contains the message "java" in it and have this small panel in a larger panel and place that panel into my JFrame.
However, when i do so the message "java" disappears and only the the small panel in the larger panel appears. I added borders to my panels to make sure that my panels were actually visible. Please help and here is my code:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
public class MovingPanel extends JFrame {
private String message;
private int x = 100;
private int y = 100;
public MovingPanel() {
JPanel panel = new JPanel();
MessagePanel p1 = new MessagePanel("Java");
panel.setBorder(new LineBorder(Color.RED, 2));
panel.setLayout(null);
p1.setLocation(x, y);
p1.setSize(50, 30);
p1.setBorder(new LineBorder(Color.BLACK, 2));
p1.setLayout(new BorderLayout());
panel.add(p1);
add(panel);
}
public static void main(String[] args) {
MovingPanel frame = new MovingPanel();
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Test Message Panel");
frame.setVisible(true);
}
class MessagePanel extends JPanel {
public MessagePanel(String s) {
message = s;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString(message, x + 20, y + 10);
}
}
}
Maybe you can try to use a simple JLabel component instead of your "MessagePanel".
First thing you need to understand is this.
The second and third arguments of this g.drawString(message, x + 20, y + 10); method are the x and y location of the panel.
With the above being said, you have to remember that it is the x and y location of the containing panel, which is MessagePanel.
You have the size of your MessagePanel object set at 50, 30, yet you are trying to access a point 120 (x + 20) and 110 (100 + 10), which does not exist since you size the size of the panel.
So now that's understood, let's say you want to paint the message at the very left corner of the MessagePanel, so you try and do this g.drawString(message, 0, 0);. This still would show anything as the point starts from the bottom left corner of the message, so the message would actually be riding just above the visible area.
When drawing strings, you need to consider the FontMetrics, which allows you to get the size of the string you are trying to draw, so you can position the message exactly on the screen where you want it.
A simple fix would be just set an x and y a little above 0, 0, like 15, 15. Though this might get your message to draw, it wouldn't be centered. You can keep on changing and getting different numbers to check if it is aligned in the middle, but the proper way is to use FontMetrics
As a said a simple (but maybe not desired) fix is to just change this
g.drawString(message, x + 20, y + 10);
To
g.drawString(message, 15, 15);
And you will see the message.
Instead of what you are doing though, this is how I would do it.
Instead of using two panels, I would just use one - the one that's doing the painting.
Don't set the size of it, instead override getPrefferedSize inside that class, to whatever size you want the main panel to me.
When you draw, just draw a rectangle the size you want at the specified coordinates.
Also draw the message in the same paintComponent method.
call pack() on the JFrame.
If you do the above, there's no need to try and move the location of the MessagePanel. Instead move the x and y coordinates when you call repaint, You can have offsets for the message. Like
int boxX = 100;
int boxY = 100;
int messageOffset = 15;
Then you can paint like this
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawRect(boxX, boxY, 50, 30);
g.drawString(message, boxX + messageOffset, boxY + messageOffset);
}
Now in your action methods, just alter the boxX and/or boxY and call repaint.
Also, if you want a thicker line, look into Graphics2D API, you can setStroke.

Elements not drawing when paint() is directly invoked, but drawing when added to content pane

A Graph object is being added to a JFrame. This object draws axes followed by a graph plot. When the object's paint() is invoked implicitly through the JFrame using:
this.getContentPane().add(new Graph());
both the axes and the function draw. However, when the paint() method is explicitly invoked, via:
Graph g = new Graph();
g.paint(this.getContentPane().getGraphics());
the axes do not draw, however the function does. The full constructor for the JFrame is as follows:
public GraphFrame() {
super("");
setSize(800, 800);
setVisible(true);
//One of the above blocks is called here
}
The function paint in object Graph is as follows:
public void paint(Graphics w) {
w.setColor(Color.WHITE);
w.fillRect(0, 0, 800, 800); //Clears the screen
w.setColor(Color.BLACK);
w.drawLine(100, 0, 100, 800);
w.drawLine(0, 700, 800, 700); //(Should) Draw the axes
for(int i = 1; i < 650; i++) {
//Draws the function
//This is just a repeated drawLine call.
}
}
Why would the axes draw when implicitly called when components paint, but not draw when explicitly invoked? Remember that the function draws (the block in the for loop), while the axes preceding the for loop do not.
Don't call paint directly on a component. Also for custom painting in Swing use paintComponent rather than paint and remember to call super.paintComponent(g). Also getGraphics returns a transient Graphics reference so should not be used for custom painting. In contrast the Graphics reference in paint (and paintComponent) is always initialized correctly and will display graphical output as expected.
Performing Custom Painting
Painting in AWT and Swing

Java Swing - Scroll Pane With Custom Graphics

I currently have the code below.
public class cRunningView extends JInternalFrame {
static final int xOffset = 30, yOffset = 30;
public cRunningView() {
// Get name;
super("RUNNING", true, // resizable
false, // closable
true, // maximizable
true);// iconifiable
System.out.println("##" + "p.getName()");
// ...Then set the window size or call pack...
setSize(500, 200);
// Set the window's location.
setLocation(xOffset * 0, yOffset * 0);
JScrollPane scrollPane = new JScrollPane();
}
}
My aim is to have a JInternalFrame with a number of buttons and a box/rectangle on half of the screen.
Within this box i want to be able to draw graphics for e.g. Draw oval from x,y to x,y.
I've tried looking at examples but see to get my self more confused than i did to begin. All my code is working e.g. Showing the main GUI window and my internal frame opening but i cant seem to find a good tutuirol/starting point to do graphics within a JScrollPane.
Please note i dont have to use a JScrollPane i just thought i would be a good idea cause it would give the graphics a border round it.
Before anyone moans about the question i think it is valid AND I DONT want the code to be given to me on a plate, i'd rather know and understand what im doing so i can advance my knowledge and be able to help others !
Do i have to make another class and do
JScrollPane myPane = JScrollPane(graphicsClass)
then do everything with paint() then or is there someway to create a graphic and do it without another class?
If i do :
JScrollPane scrollPane = new JScrollPane();
Graphics temp = scrollPane.getGraphics();
temp.setColor(new Color(1, 22, 33));
temp.fillOval(60, 0, 120, 60);
scrollPane.paint(temp);
It throws errors.
Thanks
You don't do Graphics in a scrollpane. Also, don't use the getGraphics() method to do custom painting.
Custom painting is done by overriding the paintComponent() method of a JPanel or JComponent. Then if required you can add the panel to a scrollpane and add the scrollpane to your frame. Don't forget to set the preferred size of the panel so scrolling will work.
Start by reading the Swing tutorial on Custom Painting.

Categories