What are the principle differences between using an AWT Frame and a Swing JFrame when implementing your own rendering and not using standard Java GUI components?
This is a follow on from a previous question:
AWT custom rendering - capture smooth resizes and eliminate resize flicker
The typical talking points on Swing vs AWT don't seem to apply because we're only using frames. Heavyweight vs Lightweight goes out the window (and JFrame extends Frame), for example.
So which is best, JFrame or Frame for this situation? Does it make any meaningful difference?
Note: this scenario is one where rendering in the EDT is not desirable. There is an application workflow which is not linked to the EDT and rendering is done on an as-needs basis outside of the EDT. To synchronize rendering with the EDT would add latency to the rendering. We are not rendering any Swing or AWT components other than the Frame or JFrame (or an enclosed JPanel/Component/etc if it is best).
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.Frame;
public class SmoothResize extends Frame {
public static void main(String[] args) {
Toolkit.getDefaultToolkit().setDynamicLayout(true);
System.setProperty("sun.awt.noerasebackground", "true");
SmoothResize srtest = new SmoothResize();
//srtest.setIgnoreRepaint(true);
srtest.setSize(100, 100);
srtest.setVisible(true);
}
public SmoothResize() {
render();
}
private Dimension old_size = new Dimension(0, 0);
private Dimension new_size = new Dimension(0, 0);
public void validate() {
super.validate();
new_size.width = getWidth();
new_size.height = getHeight();
if (old_size.equals(new_size)) {
return;
} else {
render();
}
}
public void paint(Graphics g) {
validate();
}
public void update(Graphics g) {
paint(g);
}
public void addNotify() {
super.addNotify();
createBufferStrategy(2);
}
protected synchronized void render() {
BufferStrategy strategy = getBufferStrategy();
if (strategy == null) {
return;
}
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
Graphics draw = strategy.getDrawGraphics();
Insets i = getInsets();
int w = (int)(((double)(getWidth() - i.left - i.right))/2+0.5);
int h = (int)(((double)(getHeight() - i.top - i.bottom))/2+0.5);
draw.setColor(Color.YELLOW);
draw.fillRect(i.left, i.top + h, w,h);
draw.fillRect(i.left + w, i.top, w,h);
draw.setColor(Color.BLACK);
draw.fillRect(i.left, i.top, w, h);
draw.fillRect(i.left + w, i.top + h, w,h);
draw.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
}
Expanding on #camickr's answer, the "missing detail" is JRootPane, which manages the contentPane. Note that for JFrame "add and its variants, remove and setLayout have been overridden to forward to the contentPane as necessary." JRootPane#createContentPane() "creates a new JComponent a[n]d sets a BorderLayout as its LayoutManager." As an implementation detail, that JComponent happens to be a new JPanel(). This has several consequences for the contentPane of JFrame:
The contentPane is double buffered by default.
The contentPane has a BorderLayout, although JPanel ordinarily defaults to FlowLayout.
The contentPane has a L&F specific UI delegate, typically derived from PanelUI, that may affect appearance and geometry.
Swing is double buffered by default so generally you only need to concentrate on your painting.
Here is the Swing version:
import java.awt.*;
import javax.swing.*;
public class SwingResize extends JPanel
{
protected void paintComponent(Graphics g)
{
int w = (int)(((double)(getWidth()))/2+0.5);
int h = (int)(((double)(getHeight()))/2+0.5);
g.setColor(Color.YELLOW);
g.fillRect(0, h, w,h);
g.fillRect(w, 0, w,h);
g.setColor(Color.BLACK);
g.fillRect(0, 0, w, h);
g.fillRect(w, h, w,h);
}
public static void main(String[] args)
{
// Toolkit.getDefaultToolkit().setDynamicLayout(true);
// System.setProperty("sun.awt.noerasebackground", "true");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.add( new SwingResize() );
frame.setSize(100, 100);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
Related
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setLayout(new FlowLayout());
frame.setSize(new Dimension(100, 100));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TestPanel panel = new TestPanel();
panel.setPreferredSize(new Dimension(50,50));
frame.add(panel);
frame.setVisible(true);
}
static class TestPanel extends JPanel implements ActionListener{
private static final long serialVersionUID = 8518959671689548069L;
public TestPanel() {
super();
Timer t = new Timer(1000, this);
t.setRepeats(true);
t.start();
}
int opacity = 10;
#Override
public void actionPerformed(ActionEvent e) {
if(opacity >= 250) {
opacity = 0;
}
else {
this.setBackground(new Color(255, 212, 100, opacity));
this.repaint();
opacity+=10;
System.out.println("opacity is " + opacity);
}
}
}
}
The rate the alpha changes is faster than it should be. After it reaches a certain point, the opacity drops, while the the opacity printed in the console is less than 250. Resizing the window "resets" it, making the alpha correct.
How do I make it actually draw the alpha correctly?
this.setBackground(new Color(255, 212, 100, opacity));
Swing does not support semi transparent backgrounds.
Swing expects a component to be either:
opaque - which implies the component will repaint the entire background with an opaque color first before doing custom painting, or
fully transparent - in which case Swing will first paint the background of the first opaque parent component before doing custom painting.
The setOpaque(...) method is used to control the opaque property of a component.
In either case this makes sure any painting artifacts are removed and custom painting can be done properly.
If you want to use tranparency, then you need to do custom painting yourself to make sure the background is cleared.
The custom painting for the panel would be:
JPanel panel = new JPanel()
{
protected void paintComponent(Graphics g)
{
g.setColor( getBackground() );
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
};
panel.setOpaque(false); // background of parent will be painted first
Similar code would be required for every component that uses transparency.
Or, you can check out Background With Transparency for custom class that can be used on any component that will do the above work for you.
Why doesn't JPanel (panel) get drawn on the green background (the jpanel)? I want to be able to do this without extending j panel to...
Furthermore, for java games should i use keybindings or keylistener in java.
import javax.swing.*;
import java.awt.event.*;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class Game {
JFrame window;
JPanel panel;
int charPosX = 0;
int charPosY = 0;
public Boolean createGui() {
window = new JFrame("Game");
window.setSize(1000,500);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
panel = new JPanel();
panel.setVisible(true);
panel.setLayout(null);;
panel.setBackground(new Color(65,130,92));
window.add(panel);
return true; //returns true if ran and will be ran by check status in Main.
}
public void paintComponent(Graphics g) {
panel.paintComponents(g);
g.setColor(Color.RED);
g.drawRect(100,10,30,40);
g.fillRect(10, 10, 20, 10);
}
}
Let's take your code for a second and add #Override to your paintComponent method...
public class Game {
//...
#Override
public void paintComponent(Graphics g) {
panel.paintComponents(g);
g.setColor(Color.RED);
g.drawRect(100, 10, 30, 40);
g.fillRect(10, 10, 20, 10);
}
}
And now we have a compiler error! This is because Game extends Object and does not have a paintComponent method. This means that there is no way that the method could be called by any part of the existing painting system, so, it never gets called.
Components make poor "game" entities, they have a lot of "plumbing" which doesn't make them very efficient for this kind of work, you're generally better off heading down a complete custom painting route
import javax.swing.*;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
public class Game {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game().createGui();
}
});
}
JFrame window;
GamePanel panel;
int charPosX = 0;
int charPosY = 0;
public Boolean createGui() {
window = new JFrame("Game");
window.setSize(1000, 500);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new GamePanel();
panel.setBackground(new Color(65, 130, 92));
window.add(panel);
window.setVisible(true);
return true; //returns true if ran and will be ran by check status in Main.
}
public class GamePanel extends JPanel {
private Rectangle entity = new Rectangle(100, 10, 30, 40);
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.draw(entity);
g2d.setColor(Color.BLUE);
g2d.fill(entity);
g2d.dispose();
}
}
}
Also note, I called window.setVisible(true); only after I had added the panel to the window, this is because Swing is lazy when it comes to adding/removing components. If you want to add/remove components after the UI has been realized on the screen, you'll need to call revalidate and repaint on the container to trigger a layout and paint pass
Also, beware, there is a difference between paintComponent and paintComponents ;)
I would highly recommend having a look at Painting in AWT Swing and Performing Custom Painting to gain a better understanding of how painting works in Swing and how you can take advantage of it
I have two separate class and driver files, and in the class file I create the paint method:
public void paint(Graphics g){
g.drawLine(......
....
//along with all of my other draw commands
}
Further down in the code, I create a JButton and within this button's action listener I don't know how to use a Graphics object to create more graphics in the JFrame. Should I be adding something to my driver to make this happen, or is there a way to use these graphics within my action listener? Thank you, and any help is appreciated.
You need to draw everything within the paint method. The actionPerformed should only change the state of something already in the paint method, and then call repaint. For example
boolean drawHello = true;
boolean drawWorld = false;
protected void paintComponent(Graphics g) {
super.paintCompoent(g);
if (drawHello)
g.drawString("Hello", 50, 50);
if (drawWorld)
g.drawString("World", 10, 10);
}
Then in your actionPerformed, you can change the state of drawWorld to true and call repaint().
public void actionPerformed(ActionEvent e) {
drawWorld = true;
repaint();
}
So as you can see, everything should be drawn in the paintComponent method. You can just hide and paint renderings, and make them "visible" from a action command. You should already have predefined what could posibly be drawn. Then just change the state of it rendering
And as #MadPrgrammer pointed out, you should not be painting on top-level containers like JFrame. Instead paint on a custom JPanel or JComponent and override the paintComponent method, instead of JFrame and paint
Here's an example where I draw a new square every time the button is pressed. If look at the code, you will see that in the paintComponent method, I loop through a list of Squares and draw them, and in the actionPerformed all I do is add a new Square to the List and call repaint()
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class AddSquares {
private int R = 0;
private int G = 0;
private int B = 0;
private int xLoc = 0;
private int yLoc = 0;
List<Square> squares = new ArrayList<>();
private JButton addSquare = new JButton("Add Square");
private RectsPanel panel = new RectsPanel();
public AddSquares() {
addSquare.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
Color color = new Color(R, G, B);
squares.add(new Square(xLoc, yLoc, color));
panel.repaint();
R += 10;
G += 20;
B += 30;
xLoc += 20;
yLoc += 20;
}
});
JFrame frame = new JFrame("Draw Squares");
frame.add(panel, BorderLayout.CENTER);
frame.add(addSquare, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private class RectsPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Square square : squares) {
square.drawSquare(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
}
private class Square {
int x = 0;
int y = 0;
Color color;
public Square(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void drawSquare(Graphics g) {
g.setColor(color);
g.fillRect(x, y, 75 ,75);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AddSquares addSquares = new AddSquares();
}
});
}
}
It's difficult to be 100%, but it would seem as you don't understand how custom painting is performed in Swing.
Start by taking a look at Performing Custom Painting and Painting in AWT and Swing.
Essentially, painting is arranged by the Repaint Manager, which decides what and when something should be painted. It then calls (through a chain of methods) the paint method of the components it thinks need to be updated, passing it a reference to a Graphics context that should be used to actually paint on.
Basically, when ever your paint method is called, you should create paint the current state of your painting.
You should avoid overriding paint and instead use paintComponent from classes the extend JComponent
Your question is a little on the vague side as to what you are actually wondering about but generally speaking:
We don't override paint in Swing, we override paintComponent.
If you are already aware of this, you may be overriding paint because you are doing it on a JFrame and you found that JFrame does not have a paintComponent method. You shouldn't override paint on a JFrame. Instead, create a JPanel or something to put inside the frame and override paintComponent on the panel.
Question about the ActionListener.
It sounds like you are wanting to do painting outside of paintComponent in which case probably the best way is to do painting to a separate Image. Then you paint the Image on to the panel in paintComponent. You can also put an Image in a JLabel as an ImageIcon. Here is a very simple drawing program using MouseListener that demonstrates this (taken from here):
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
class PaintAnyTime {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
final BufferedImage image = (
new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB)
);
final JFrame frame = new JFrame();
final JLabel label = new JLabel(new ImageIcon(image));
final MouseAdapter drawer = new MouseAdapter() {
Graphics2D g2D;
#Override
public void mousePressed(MouseEvent me) {
g2D = image.createGraphics();
g2D.setColor(Color.BLACK);
}
#Override
public void mouseDragged(MouseEvent me) {
g2D.fillRect(me.getX(), me.getY(), 3, 3);
label.repaint();
}
#Override
public void mouseReleased(MouseEvent me) {
g2D.dispose();
g2D = null;
}
};
PaintAnyTime() {
label.setPreferredSize(
new Dimension(image.getWidth(), image.getHeight())
);
label.addMouseListener(drawer);
label.addMouseMotionListener(drawer);
frame.add(label);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
#MadProgrammer has already linked to the articles that I was going to link to.
I have an image that is to be drawn on a JFrame. The dimensions of the image are dependent on the dimensions of the JFrame.
The JFrame is drawn significantly more often then the JFrame is actually re-sized. Thus I had the image re-sized then stored in the component re-size event and only drew the re-sized image in the draw method.
//called on componentResized
private void scaleImage(){
if((this.getHeight() * this.getWidth()) != 0)
scalledBackGroundImage = backGroundImage.getScaledInstance(this.getWidth(), this.getHeight(), Image.SCALE_FAST);
else
scalledBackGroundImage = null;
}
#Override
public void paint(Graphics g){
if(scalledBackGroundImage != null)
g.drawImage(scalledBackGroundImage, 0, 0, this);
super.paint(g);
}
However I would seem that the re-size event is called after paint when a component is redrawn. Thus the image displayed is the image for the previous frame size. This really becomes a problem with actions like maximize or minimize.
I am looking for a way to detect a JFrame re-size before paint is called.
(I know I could call repaint() on re-size but it seem a bit rude to ask for the component to be drawn twice when re-sizing)
Thanks for any help.
See The Perils of Image.getScaledInstance(). It really is not a very good choice.
Not only do you have the problem you described above, but I get a lot of flickering:
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.plaf.*;
import javax.swing.text.*;
import java.io.*;
public class ResizeSSCCE extends JPanel
{
Image original;
Image scaled;
public ResizeSSCCE()
{
original = new ImageIcon( "mong.jpg" ).getImage();
scaled = original;
scaleImage();
ComponentListener cl = new ComponentAdapter()
{
#Override
public void componentResized(ComponentEvent e)
{
scaleImage();
}
};
addComponentListener(cl);
}
#Override
public void paintComponent(Graphics g)
{
if (scaled != null)
g.drawImage(scaled, 0, 0, this);
// g.drawImage(original, 0, 0, getWidth(), getHeight(), this);
}
private void scaleImage()
{
if (getHeight() * getWidth() != 0)
scaled = original.getScaledInstance(getWidth(), getHeight(), Image.SCALE_FAST);
}
private static void createAndShowUI()
{
JFrame frame = new JFrame("ResizeSSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new ResizeSSCCE() );
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Change the code to use your image and then run using the scaled version to see the flickering. Then change the code to use the original image that is scale of the fly to see the difference. As the article suggests scaling on the fly is the better approach.
Add a component listener to JFrame as
jFrame.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
//Your resize method here
}
});
this is my first post here and I have a question that seems really nooby, but this has been troubling me for the past hour or so.
I'm making a simple JFrame with a JPanel in it, but the Windows 7 border frame appears to be blocking my view of parts of the panel. For instance, if I draw a little square at coordinate 0,0, it will not appear and I suspect it's behind the window frame.
I've tried messing around with pack, setsize, setpreferred size, setresizable, and different layouts, but I can't get it to show the top 20 pixels or so!
This is what I have:
public RedSunGame() {
super("Red Sun");
rs = new JPanel(new BorderLayout(), true);
rs.setPreferredSize(new Dimension(WIDTH, HEIGHT));
add(rs, "Center");
setPreferredSize(new Dimension(WIDTH, HEIGHT));
pack();
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
EDIT:
Thanks for all of your replies, sorry for the lack of info :)
I'm using a double buffer strategy I saw in a book. gameRender and paintScreen are in a standard game loop. My RedSunGame class extends JFrame. All the relevant code you could ask for in addition to above:
private static final int WIDTH = 500;
private static final int HEIGHT = 500;
private JPanel rs;
private Graphics2D g2d;
private Image dbImage;
private void gameRender() {
//create buffer
if (dbImage == null){
dbImage = createImage(WIDTH, HEIGHT);
g2d = (Graphics2D)dbImage.getGraphics();
}
//clear screen
g2d.setColor(Color.white);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
g2d.setColor(Color.blue);
g2d.setFont(font);
g2d.drawString("FPS: " + FPS, 0, HEIGHT);
g2d.fillRect(30, 30, 10, 10);
}
private void paintScreen() {
Graphics g;
try {
g = getGraphics();
if ((g != null) && (dbImage != null))
g.drawImage(dbImage, 0, 0, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
catch (Exception e)
{ System.out.println("Graphics context error: " + e); }
}
With my current settings it looks like this.
http://i.imgur.com/qaabC.png
This is what happens if I have g2d.fillRect(30, 30, 10, 10), the only change being the coordinates 30,30 instead of 0,0. It's definitely hiding behind the border up top.
http://i.imgur.com/uzfFe.png
Also, setting it to BorderLayout.CENTER doesn't seem to make a difference in any of these cases.
(sorry it won't let new users post images)
EDIT:
I figured it out. I was drawing directly to the JFrame. #Guillaume Polet I see why you shouldn't override the paint method of JFrames as it draws to the frame and not the panel that should actually display content!! Thanks
Here is a sample code that shows how your goal can be achieved. Try to spot the differences with your code to find what is wrong:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RedSunGame {
private static final int SQUARE_SIZE = 20;
private JPanel rs;
private JFrame frame;
private void initUI() {
frame = new JFrame("Red Sun");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
rs = new JPanel(new BorderLayout()) {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, SQUARE_SIZE, SQUARE_SIZE);
}
#Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
// Let's make sure that we have at least our little square size.
preferredSize.width = Math.max(preferredSize.width, SQUARE_SIZE);
preferredSize.height = Math.max(preferredSize.height, SQUARE_SIZE);
return preferredSize;
}
};
frame.add(rs);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
RedSunGame redSunGame = new RedSunGame();
redSunGame.initUI();
}
});
}
}
Verify that WIDTH and HEIGHT are > 0.
Try this:
//add(rs, "center");
add(rs, BorderLayout.CENTER);
you may got your answer but for a newbie to java swing i suggest that you should the Net-beans IDE. it graphically adds and lays out the GUI and you dont need to write any hand-written code. It's a great place to start, as stated here:
http://docs.oracle.com/javase/tutorial/uiswing/learn/index.html