The goal here is to use jlabels with an image icon that contains a BufferedImage. Those jlabels can then be easily moved around with the mouse without having to go searching a ton of different BufferedImages on the screen to find out which one is being clicked on.
This is easy to do in a standard JFrame. I've been searching around here for an hour trying to figure out how to implement this in a game loop where a paintComponent is overridden.
import javax.swing.*;
import java.awt.*;
public class Main {
public Main() {
}
public static void main(String[] args) {
JFrame window = new JFrame();
GamePanel gamePanel = new GamePanel();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(true);
window.setLayout(new FlowLayout());
window.setTitle("FX Test");
window.add(gamePanel);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
gamePanel.startGameThread();
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class GamePanel extends JPanel implements ActionListener {
private Timer gameLoopTimer;
public int screenWidthPixels = 640;
public int screenHeightPixels = 480;
private int counter;
private int x = 1;
private float alpha = 1;
private final int DELAY = 15;
private final int INITIAL_DELAY = 200;
public GamePanel() {
this.setPreferredSize(new Dimension(screenWidthPixels, screenHeightPixels));
this.setBackground(Color.black);
this.setDoubleBuffered(true);
this.setFocusable(true);
this.requestFocus();
counter = 0;
JButton testButton = new JButton("Button Test");
this.add(testButton);
JLabel label = new JLabel(new String("Label test"));
label.setVisible(true); // Doesn't seem to be needed.
this.add(label);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.WHITE);
g2.drawString("Game Panel Testing: " + counter,128,129);
g2.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
update();
}
void startGameThread() {
gameLoopTimer = new Timer(DELAY,this);
gameLoopTimer.setInitialDelay(INITIAL_DELAY);
gameLoopTimer.start();
}
}
That code draws "Game Panel Testing: " and the incrementing counter, but no button and no label.
If I comment out the entire paintComponent I'm overriding, the button and label appear as expected.
What I can't wrap my head around is how to get the label and button to appear again once paintComponent is overridden. I thought the super.paintComponent(g) would take care of that automatically, but clearly I'm missing something here. How on earth can I add a bunch of JLabels to this game loop instead of having to manually handle moving of g2 drawn BufferedImages on mouse drag?
The jlabels are not drawn since you have overridden the paintComponent method.
The call to super is on the super class, so you have misunderstood how that call works.
If you put your in a class that inherits from your class with jlabels it will work.
Related
I would like to draw a new random shape every 2 seconds.
I already have a window, that shows immediately some shapes. I tried to mess around with Timer to make new things appear in the window after a few seconds, but it didn't work, or the whole program freezes. Is it a good idea to use Timer? How should I implement it, to make it work?
import javax.swing.*;
import java.awt.*;
import java.util.Random;
class Window extends JFrame {
Random rand = new Random();
int x = rand.nextInt(1024);
int y = rand.nextInt(768);
int shape = rand.nextInt(2);
Window(){
setSize(1024,768);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(new Color(0, 52, 255));
switch(shape) {
case 0:
g.fillOval(x, y, 50, 50);
break;
case 1:
g.fillRect(x,y,100,100);
break;
}
repaint();
}
}
public class Main {
public static void main(String[] args) {
Window window = new Window();
}
}
I would also like to draw some random shapes. Is it ok, to use switch in paint method for this purpose? I would make a random variable, if it's 1 it would paint rectangle, if it's 2 it would paint oval etc.
First of all, do not change the way JFrame gets painted (with other words, do not override paintComponent() of a JFrame). Create an extending class of JPanel and paint the JPanel instead. Secondly, do not override paint() method. Override paintComponent(). Thirdly, always run Swing applications with SwingUtilities.invokeLater() since they should run in their own thread named EDT (Event dispatch thread). Finally, javax.swing.Timer is what you are looking for.
Take a look at this example. It paints an oval shape in random X,Y every 1500ms.
Preview:
Source code:
import java.awt.BorderLayout;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class DrawShapes extends JFrame {
private ShapePanel shape;
public DrawShapes() {
super("Random shapes");
getContentPane().setLayout(new BorderLayout());
getContentPane().add(shape = new ShapePanel(), BorderLayout.CENTER);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 500);
setLocationRelativeTo(null);
initTimer();
}
private void initTimer() {
Timer t = new Timer(1500, e -> {
shape.randomizeXY();
shape.repaint();
});
t.start();
}
public static class ShapePanel extends JPanel {
private int x, y;
public ShapePanel() {
randomizeXY();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(x, y, 10, 10);
}
public void randomizeXY() {
x = (int) (Math.random() * 500);
y = (int) (Math.random() * 500);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new DrawShapes().setVisible(true));
}
}
First, don't subclass JFrame; subclass JPanel instead, and place that panel in a JFrame.
Second, don't override paint() - override paintComponent() instead.
Third, create a Swing Timer, and in its actionPerformed() method make the changes you want and then call yourPanel.repaint()
I have a little game with little Rect, which are moving and I need to update the Graphics by doing this.update(MyGraphics) in my onUpdate method, which gets called every 50 millisekonds. But when I do this this.update(MyGraphics) all my buttons and textfields are glitched.
Does somebody have an idea how to fix it?
when I do this this.update(MyGraphics) all my buttons and textfields are glitched.
Don't invoke update(...) directly. That is not the way custom painting is done.
Instead when you do custom painting you override the paintComponent(...) method of the JPanel:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
// add your custom painting here
}
I have a little game with little Rect, which are moving
If you want animation then you should use a Swing Timer to schedule the animation. Then when the Timer fires you invoke a method on your custom class to change the location of the rectangle and then you invoke repaint(). This will cause the panel to be repainted.
Read the Swing Tutorial. There are sections on:
Performing Custom Painting
How to Use Swing Timers
to get your started with basic examples.
Here is one of the examples how to update JPanel by a timer.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class MainClass extends JPanel {
static JFrame frame = new JFrame("Oval Sample");
static MainClass panel = new MainClass(Color.CYAN);
static Color colors[] = {Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW};
static Color color;
static int step = 0;
public MainClass(Color color) {
this.color = color;
}
final static Timer tiempo = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// paintComponent();
System.out.println("Step: " + step++);
if (step % 2 == 0) {
color = Color.DARK_GRAY;
} else {
color = Color.BLUE;
}
panel.repaint();
}
});
#Override
public void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
g.setColor(color);
g.drawOval(0, 0, width, height);
}
public static void main(String args[]) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(2, 2));
panel = new MainClass(colors[2]);
frame.add(panel);
frame.setSize(300, 200);
frame.setVisible(true);
tiempo.start();
}
}
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'm having a major problem with this school assignment; lucky I started it early for once. We've been asked to make a children's math game using a JApplet. So far so good. I have managed to create a JPanel, which is then added to the JApplet and holds all the drawings (the JPanel contents are continually being redrawn). However, whenever I try to add a Swing component such as a JLabel to the JApplet content pane, it does not show or show signs of ever existing. I am completely new to JApplets so please don't be too harsh if it's obvious.
Below is the code:
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CountingSheep extends JApplet
{
final int BOARDWIDTH = 800;
final int BOARDHEIGHT = 500;
final int SCREENWIDTH = 800;
final int SCREENHEIGHT = 800;
Dimension boardDim = new Dimension(BOARDWIDTH, BOARDHEIGHT);
Dimension screenDim = new Dimension(SCREENWIDTH, SCREENHEIGHT);
Graphics bufferGraphics;
Image offScreen;
Image backgroundImage;
Image[] sheepImage = new Image[2];
JPanel gameBoard = new JPanel(true);
List<Sheep> sheepArray = new ArrayList<>();
Timer myTimer;
public void init()
{
loadImages();
initScreen();
initBufferGraphics();
initBoard();
initTimer();
sheepArray.add(new Sheep(sheepImage));
myTimer.start();
}
private void loadImages()
{
sheepImage[0] = getImage(getDocumentBase(), "sheep.png");
sheepImage[1] = getImage(getDocumentBase(), "sheep2.png");
backgroundImage = getImage(getDocumentBase(), "bg.jpg");
}
private void initScreen()
{
setSize(800, 600);
setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
}
private void initBoard()
{
gameBoard.setPreferredSize(new Dimension(BOARDWIDTH, BOARDHEIGHT));
getContentPane().add(gameBoard);
}
private void initBufferGraphics()
{
offScreen = createImage(BOARDWIDTH, BOARDHEIGHT);
bufferGraphics = offScreen.getGraphics();
}
private void initTimer()
{
myTimer = new Timer(80, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
timerTick(e);
}
});
}
private void timerTick(ActionEvent e)
{
repaint();
}
public void paint(Graphics g)
{
bufferGraphics.clearRect(0, 0, BOARDWIDTH, BOARDHEIGHT);
bufferGraphics.drawImage(backgroundImage, 0, 0, null);
drawSheepHerd();
moveSheepHerd();
gameBoard.getGraphics().drawImage(offScreen, 0, 0, this);
}
public void drawSheepHerd()
{
for (Sheep s : sheepArray)
{
s.draw(bufferGraphics);
}
}
public void moveSheepHerd()
{
for (Sheep s : sheepArray)
{
s.move();
}
}
}
Thanks in advance, hope you guys can figure it out because I'm stumped.
To summarize some of my recommendations:
Create your own ContentPane class that extends JPanel, that overrides paintComponent(...) and that draws your background image and shows the animation.
Call setContentPane(...) on the JApplet in the init method, passing in an object of this class.
Experiment with different layouts and positionings for the ContentPane.
Make sure that the very first line of the paintComponent(Graphics g) method is: super.paintComponent(g) so that your drawing will be reset each time it paints.
JPanels are opaque by default, and you should leave it as such since contentPanes must be opaque. If you add components on top of the contentPane and want to see the image behind the added components, you may have to make them non-opaque.