I've made a JFrame with a canvas on it and I want to draw on that canvas. At a later date the canvas will be updating many times a second so I am using a buffer strategy for this. Here is the code:
package mainPackage;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class TickPainter {
//just some presets for a window.
public static JFrame makeWindow(String title, int width, int height) {
JFrame mainWindow = new JFrame();
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(width, height);
mainWindow.setVisible(true);
mainWindow.setLocationRelativeTo(null);
mainWindow.setTitle(title);
return mainWindow;
}
public static void main(String[] args) {
JFrame mainWindow = makeWindow("Practice", 800, 600);
Canvas mainCanvas = new Canvas();
mainWindow.add(mainCanvas);
mainCanvas.setSize(mainWindow.getWidth(), mainWindow.getHeight());
mainCanvas.setBackground(Color.white);
mainCanvas.createBufferStrategy(3);
BufferStrategy bufferStrat = mainCanvas.getBufferStrategy();
Graphics g = bufferStrat.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(250, 250, 250, 250);
g.dispose();
bufferStrat.show();
}
}
The program does not draw the black rectangle as intended, I feel like I've missed something really obvious here and I just can't see it. At the moment the program just makes a blank white canvas. I feel like part of the issue is that the buffer is just passing the frame with the rectangle faster than I can see, but there is no frame to load after that so I don't know why it's doing this.
A BufferStrategy has a number of initial requirements which must be meet before it can be rendered to. Also, because of the nature of how it works, you might need to repeat a paint phases a number of times before it's actually accepted by the hardware layer.
I recommend going through the JavaDocs and tutorial, they provide invaluable examples into how you're suppose to use a BufferStrategy
The following example uses a Canvas as the base component and sets up a rendering loop within a custom Thread. It's very basic, but shows the basic concepts you'd need to implement...
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TestCanvas canvas = new TestCanvas();
JFrame frame = new JFrame();
frame.add(canvas);
frame.setTitle("Test");
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas.start();
}
});
}
public class TestCanvas extends Canvas {
private Thread thread;
private AtomicBoolean keepRendering = new AtomicBoolean(true);
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
public void stop() {
if (thread != null) {
keepRendering.set(false);
try {
thread.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
public void start() {
if (thread != null) {
stop();
}
keepRendering.set(true);
thread = new Thread(new Runnable() {
#Override
public void run() {
createBufferStrategy(3);
do {
BufferStrategy bs = getBufferStrategy();
while (bs == null) {
System.out.println("get buffer");
bs = getBufferStrategy();
}
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
System.out.println("draw");
Graphics graphics = bs.getDrawGraphics();
// Render to graphics
// ...
graphics.setColor(Color.RED);
graphics.fillRect(0, 0, 100, 100);
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (bs.contentsRestored());
System.out.println("show");
// Display the buffer
bs.show();
// Repeat the rendering if the drawing buffer was lost
} while (bs.contentsLost());
System.out.println("done");
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} while (keepRendering.get());
}
});
thread.start();
}
}
}
Remember, the point of BufferStrategy is to give you full control over the painting process, so it works outside the normal painting process generally implemented by AWT and Swing
"At a later date the canvas will be updating many times a second so I am using a buffer strategy for this" - Before going down the "direct to hardware" solution, I'd consider using a Swing Timer and the normal painting process to see how well it works
Related
I am working on a simple 2D game. Each tick, I want to check an effects queue that will start a thread for a certain effect(fading transitions, audio fade in and out, etc). For example, pressing "Play" on the menu screen will add a "FadeOut" message to this queue, which will be processed and start a thread to draw a black rectangle with an increasing alpha value over my GamePanel.
I'm overriding paintComponent() and sending my Graphics object to my GameStateManager, which passes along the Graphics object to the current states' draw(). I currently don't have an effects state (which maybe I should) to route the paintComponent() graphics object to, but I do pass my gamepanel to my effects thread, where I can use getGraphics() to draw on it. Drawing a rectangle to the GamePanel directly just causes flickering, as the gameloop is still rendering the game.
I found I can draw a black rectangle with increasing alpha to a BufferedImage, set the composite to AlphaComposite.Src (which causes the new draw to replace the old) then draw the BufferedImage over the game panel. The problem is the BufferedImages drawn to the game panel don't get overridden each draw, so the fade out happens really quickly because these black BufferedImages of various alphas just stack on each other.
I wrote this short program to test composite settings and see what is getting overridden. All drawing is done in the draw(), which would be my run() in the effects thread.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ScratchPad extends JPanel implements Runnable
{
private JFrame oFrame = null;
private Thread oGameThread = null;
private Graphics2D oPanelGraphics = null;
private Graphics2D oImageGraphics = null;
private BufferedImage oImage = null;
public static void main(String args[]) throws Exception
{
new ScratchPad();
}
public ScratchPad()
{
createFrame();
initPanel();
addAndShowComponents();
oGameThread = new Thread(this, "Game_Loop");
oGameThread.start();
}
private void addAndShowComponents()
{
oFrame.add(this);
oFrame.setVisible(true);
}
private void initPanel()
{
this.setOpaque(true);
this.setBackground(Color.cyan);
}
private void createFrame()
{
oFrame = new JFrame("Fade");
oFrame.setSize(700, 300);
oFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
oFrame.setLocationRelativeTo(null);
}
public void run()
{
oImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
while(true)
{
try
{
draw();
Thread.sleep(100);
}
catch(InterruptedException e)
{
}
}
}
private void draw()
{
oPanelGraphics = (Graphics2D)this.getGraphics();
oImageGraphics = oImage.createGraphics();
oImageGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,90));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.setColor(new Color(0,0,0,60));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 220, 10, null);
oImageGraphics.setColor(new Color(0,0,0,30));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 430, 10, null);
// Drawing this image over location of first image, should overwrite first
// after setting composite to 'Src'
oPanelGraphics.setComposite(AlphaComposite.Src);
oImageGraphics.setColor(new Color(0,0,0,10));
oImageGraphics.fillRect(0, 0, oImage.getWidth(), oImage.getHeight());
oPanelGraphics.drawImage(oImage, 10, 10, null);
oImageGraphics.dispose();
oPanelGraphics.dispose();
}
} // end class
What's interesting is setting the composite on 'oPanelGraphics' causes any alpha to the BufferedImage to go away, resulting in a fully opaque black image being drawn over the image that was previously there. Even setting the color to something other than black doesn't have an effect.
What's also interesting is setting the composite for the BufferedImage to:
oImageGraphics.setComposite(AlphaComposite.SrcIn);
causes nothing to be shown. The Oracle documentation on compositing graphics in Java2D states this for 'SrcIn':
"If pixels in the source and the destination overlap, only the source pixels in the overlapping area are rendered."
So, I would expect this to have the same behavior I get with AlphaComposite.Src.
Maybe someone out there can shed some light on whats going on with these composites, and how I could achieve my desired effect.
There are a number issues with what you "seem" to be trying to do
Don't call getGraphics on a component. This can return null and only returns a snapshot of what was last painted during a Swing paint cycle. Anything you paint to it will be erased on the next paint cycle
You should also never dispose of Graphics context you did not create, doing so could effect other components that are painted by Swing
Painting is compounding, this means that painting to the same Graphics context (or BufferedImage) over and over again, will continue to apply those changes over the top of what was previously painted
You also don't seem to have a concept of how animation should work. Instead of trying to paint your fade effect in a single pass, where the results can't be applied to the screen, you need to apply a phase on each cycle and allow that to be updated to the screen before the next pass runs.
The following is a really basic example of what I'm talking about. It takes a "base" image (this could be the "base" state of the game, but I've used a static image) and the paints effects over the top.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Engine engine;
private Image frame;
public TestPane() {
engine = new Engine();
engine.setEngineListener(new EngineListener() {
#Override
public void updateDidOccur(Image img) {
frame = img;
repaint();
}
});
engine.start();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
engine.addEffect(new FadeOutEffect(Color.BLACK));
}
});
}
#Override
public Dimension getPreferredSize() {
return engine.getSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (frame != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(frame, 0, 0, null);
g2d.dispose();
}
}
}
public interface EngineListener {
public void updateDidOccur(Image img);
}
public class Engine {
// This is the "base" image, without effects
private BufferedImage base;
private Timer timer;
private EngineListener listener;
private List<Effect> effects = new ArrayList<Effect>(25);
public Engine() {
try {
base = ImageIO.read(new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/megatokyo_omnibus_1_3_cover_by_fredrin-d4oupef 50%.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int width = base.getWidth();
int height = base.getHeight();
BufferedImage frame = new BufferedImage(width, height, base.getType());
Graphics2D g2d = frame.createGraphics();
g2d.drawImage(base, 0, 0, null);
Iterator<Effect> it = effects.iterator();
while (it.hasNext()) {
Effect effect = it.next();
if (!effect.applyEffect(g2d, width, height)) {
it.remove();
}
}
g2d.dispose();
if (listener != null) {
listener.updateDidOccur(frame);
}
}
});
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public void addEffect(Effect effect) {
effects.add(effect);
}
public void setEngineListener(EngineListener listener) {
this.listener = listener;
}
public Dimension getSize() {
return base == null ? new Dimension(200, 200) : new Dimension(base.getWidth(), base.getHeight());
}
}
public interface Effect {
public boolean applyEffect(Graphics2D context, int width, int height);
}
public class FadeOutEffect implements Effect {
private int tick = 0;
private Color fadeToColor;
public FadeOutEffect(Color fadeToColor) {
this.fadeToColor = fadeToColor;
}
#Override
public boolean applyEffect(Graphics2D context, int width, int height) {
tick++;
float alpha = (float) tick / 100.0f;
if (alpha > 1.0) {
return false;
}
Graphics2D g2d = (Graphics2D) context.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g2d.setColor(fadeToColor);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
return true;
}
}
}
Remember, every effect or change should be applied within the same "main loop", this means you shouldn't have multiple threads, in fact, since Swing is not thread safe, you should avoid having any additional threads if possible. This example make use of a Swing Timer to act as the "main loop" because the ActionListers actionPerformed method is called within the context of the EDT, making it safe to update the UI from. It also provides a simple synchronisation method, as the UI can't be painted while the actionPerformed method is been called
I am very new to java. Very. Like I basically started today. I have previous programming knowledge in other languages, like c, c++, PHP, javascript, etc, but I can't figure this one out. I started watching tutorials on Youtube about how to make a video game in Java(videos from theChernoProject), but about 7 episodes in, I came across a problem, where we have our window, and we paint a black rectangle across the whole thing, and the application freezes my whole computer. Here is my code:
package com.darksun.theonetruemike.rain;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static int width = 300;
public static int height = width / 16 * 9;
public static int scale = 3;
private Thread thread;
private JFrame frame;
private boolean running = false;
public Game(){
Dimension size = new Dimension(width * scale, height * scale);
setPreferredSize(size);
frame = new JFrame();
}
public synchronized void start(){
running = true;
thread = new Thread(this, "Display");
thread.start();
}
public synchronized void stop(){
running = false;
try{
thread.join();
} catch(InterruptedException e){
e.printStackTrace();
}
}
public void run(){
while(running){
update();
render();
}
}
public void update(){
}
public void render(){
BufferStrategy bs = getBufferStrategy();
if(bs == null){
createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
g.dispose();
bs.show();
}
public static void main(String args[]){
Game game = new Game();
game.frame.setResizable(false);
game.frame.setTitle("Rain");
game.frame.add(game);
game.frame.pack();
game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.frame.setLocationRelativeTo(null);
game.frame.setVisible(true);
game.start();
}
}
I'm using eclipse to make this project(highly against my will), and when I press the Debug button, the window appears, and my computer freezes, resulting in having to force quit the entire computer. Please help if you can, and thanks for help ahead of time!
since i have low reputation, i will post my comment as answer, try not to -rep
i copied your code to netbeans, a black console like window appears, nothing happens, but it doesnt freeze, but the JVM is using about 50% of cpu
I have a JLabel with the paintComponent() overriden.I want it to be forcefully called since the code that updates my Label UI is this event.How can I force its calling and updating of UI? (by the way,repaint does not work!)
here is my code :
BufferedImage background;
String Uri;
public CustomClockLabel(String Uri){
init(Uri);
this.Uri = Uri;
}
public void init(String Uri){
try {
URL inp = CustomClockLabel.class.getResource(Uri);
background = ImageIO.read(inp);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if(background != null){
g2.drawImage(background, 0, 0,getWidth(),getHeight(), this);
}
g2.dispose();
super.paintComponent(g);
}
here is the code that updates labels and it is called recursively :
lblHour1 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(0)+".png");
lblHour1.repaint();
lblHour2 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(1)+".png");
lblHour2.repaint();
lblMin1 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(0)+".png");
lblMin1.repaint();
lblMin2 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(1)+".png");
lblMin1.repaint();
You may be under the false impression that creating new labels will update what's on the screen, doing this...
lblHour1 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(0)+".png");
lblHour2 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(1)+".png");
lblMin1 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(0)+".png");
lblMin2 = new CustomClockLabel("/icons/noa_en/"+Minute.charAt(1)+".png");
Will change the reference of the variables, so they will no longer be the same variables as those you added to the screen.
Assuming that the above variables have being added to the screen already, you could simply update them by using something like...
lblHour1.init(new ImageIcon("/icons/noa_en/"+Hour.charAt(0)+".png"));
lblHour2.init(new ImageIcon("/icons/noa_en/"+Hour.charAt(1)+".png"));
lblMin1.init(new ImageIcon("/icons/noa_en/"+Minute.charAt(0)+".png"));
lblMin2.init(new ImageIcon("/icons/noa_en/"+Minute.charAt(1)+".png"));
revalidate();
repaint();
If that fails, you should try setting one the labels border's properties so you can see if it's actually been added to the screen.
Updated
After some experimentation with what little you code you have made available, here are some more recommendations...
As has already being mentioned, make sure you are calling super.paintComponent first, as one of the jobs of this method is to clear the graphics ready for painting...
Make sure you provide a suitable sizing hint to the component, so the layout managers have some kind of idea of how big you might like the component to be. This ensures that the component is not sized to 0x0
The following example is very simple, but takes (what little) code you supplied and builds a runnable example from it...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PaintComponentTest {
public static void main(String[] args) {
new PaintComponentTest();
}
private int time = 0;
public PaintComponentTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final CustomClockLabel counter = new CustomClockLabel("/icons/0.png");
Timer timer = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
time++;
if (time > 9) {
time = 0;
}
counter.init("/icons/" + Integer.toString(time) + ".png");
counter.repaint();
}
});
timer.start();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(counter);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class CustomClockLabel extends JPanel {
BufferedImage background;
String Uri;
public CustomClockLabel(String Uri) {
init(Uri);
this.Uri = Uri;
}
public void init(String Uri) {
try {
URL inp = getClass().getResource(Uri);
background = ImageIO.read(inp);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 100);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (background != null) {
g2.drawImage(background, 0, 0, getWidth(), getHeight(), this);
}
g2.dispose();
}
}
}
Calling repaint() on the component in question will force it to paint again.
The problem you have does not appear to repainting, as you are actually changing the labels on the panel. Make sure you remove the old labels and add the new ones instead and call revalidate(). (The code you posted looks like you are just updating the label references and not actually changing them out on the panel.)
Overall, the design could be improved dramatically by making your CustomClockLabel class take in a parameter that changes the image and therefore allows you to just call repaint().
lblHour1 = new CustomClockLabel("/icons/noa_en/"+Hour.charAt(0)+".png");
The above code doesn't do anything. All is does is create a new component. But that component is not added to the GUI so it obviously doesn't repaint.
There is nothing in your class that would cause it to need repainting, so your question doesn't make sense. You have a design problem. I don't see any reason to create a custom label.
If you want to change the image, then just use a standard JLabel with an Icon. Then to change the image you just use the setIcon(...) method and the label will repaint itself automatically.
(by the way,repaint does not work!)
If it isn't, then the only thing i can suspect, the fault is in your painting order: where you are calling super.paintComponent(g); after you are drawing the image. If your label is non-opaque and has background color, then you will not see the image, as the later painting super.paintComponent(g) will be drawn above the previous painting.
Try changing the order:
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if(background != null){
g2.drawImage(background, 0, 0,getWidth(),getHeight(), this);
}
g2.dispose();
I have a swing application that contains an SVG canvas inside a JScrollPane.
The application modifies the displayed SVG document, which also leads to a change of the size of the document.
This size change needs to be reflected in the application. The SVG canvas is resized and the viewport of the JScrollPane is scrolled so that it displays the correct section of the canvas.
However, this leads to something like a "visual jumping", because the user first sees the change of the canvas size and after that sees the scrolling operation.
Is there a way to tell java to stop processing rendering events on a given component (and its subcomponents) and only resume after I have finished my modifications to only display the result of all modifications?
Here is my idea in pseudo code:
myScrollPane.suspendRendering();
svgDocument.changeSize();
svgCanvas.changeSize();
myScrollPane.getViewport().scrollToCorrectPosition;
myScrollPane.resumeRendering();
I tried with myScrollPane.setIgnoreRepaint(true), but it seems to not have any effect here (even if I never set ignoreRepaint to false again.
And here is an SSCCE that tries to simulate the effect:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class Jumping extends JFrame {
private JButton innerPanel= new JButton("Some silly, useless text, just for fun. And it goes on even longer. But that's not a problem.");
private JScrollPane scrollPane= new JScrollPane(innerPanel);
private JButton btnJump= new JButton("Jump");
private int lastWidth= 1024;
public Jumping(){
this.setLayout(new BorderLayout());
this.add(btnJump, BorderLayout.NORTH);
this.add(scrollPane, BorderLayout.CENTER);
this.scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
this.scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
this.innerPanel.setPreferredSize(new Dimension(1024, 768));
this.innerPanel.setSize(1024, 768);
this.innerPanel.setMinimumSize(new Dimension(1024, 768));
this.innerPanel.setMaximumSize(new Dimension(1024, 768));
this.setSize(640, 480);
this.btnJump.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try{
System.err.println("> actionPerfomed");
//resize the canvas
new Thread(){
public void run() {
System.err.println("> SwingWorker.doInBackground "+SwingUtilities.isEventDispatchThread());
SwingUtilities.invokeLater(new Runnable(){
public void run(){
System.err.println("> resize canvas "+SwingUtilities.isEventDispatchThread());
final int newWidth= (int) (lastWidth * 1.5);
innerPanel.setSize(newWidth, 768);
innerPanel.setPreferredSize(new Dimension(newWidth, 768));
innerPanel.setMinimumSize(new Dimension(newWidth, 768));
innerPanel.setMaximumSize(new Dimension(newWidth, 768));
lastWidth= newWidth;
System.err.println("< resize canvas "+SwingUtilities.isEventDispatchThread());
}
});
//scroll to correct position
SwingUtilities.invokeLater(new Runnable(){
public void run(){
System.err.println("> scroll to pos "+SwingUtilities.isEventDispatchThread());
try {
System.err.println("< sleep "+SwingUtilities.isEventDispatchThread());
Thread.sleep(500);
System.err.println("> sleep "+SwingUtilities.isEventDispatchThread());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
final Point viewPos= scrollPane.getViewport().getViewPosition();
scrollPane.getViewport().setViewPosition(new Point(viewPos.x + 50, viewPos.y));
System.err.println("< scroll to pos "+SwingUtilities.isEventDispatchThread());
}
});
System.err.println("< SwingWorker.doInBackground "+SwingUtilities.isEventDispatchThread());
}
}.start();
System.err.println("< actionPerfomed "+SwingUtilities.isEventDispatchThread());
}catch(Exception ex){
ex.printStackTrace();
}
}
});
}
public static void main(String[] args){
final Jumping frame= new Jumping();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Generally one possibility is using a custom RepaintManager. A simplistic, and very much unoptimized example follows:
public class FreezableRepaintManager extends RepaintManager {
final Set<Component> frozen = new HashSet<Component>();
public void freeze(Container c) {
frozen.add(c);
for (Component child : c.getComponents()) {
if (child instanceof Container) {
freeze((Container) child);
} else {
frozen.add(child);
}
}
}
public void thaw(final Container c) {
frozen.remove(c);
for (Component child : c.getComponents()) {
if (child instanceof Container) {
thaw((Container) child);
} else {
frozen.remove(child);
}
}
c.repaint();
}
#Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
if (!frozen.contains(c)) {
super.addDirtyRegion(c, x, y, w, h);
}
}
}
Set the repaint manager somewhere early in your code with RepaintManager.setCurrentManager() and then use freeze(componentTree) before staring the series of operations and follow with thaw(componentTree) once you're done.
That works for most components, but unfortunately JScrollPane is one for which it is not enough, as it does more complicated drawing than most. So you may need a JScrollPane whose createViewport() returns a view port that can suppress the blits, such as:
class FreezableViewport extends JViewport {
private boolean frozen;
public void freeze() {
frozen = true;
}
public void thaw() {
frozen = false;
}
#Override
protected boolean computeBlit(int dx, int dy, Point blitFrom,
Point blitTo, Dimension blitSize, Rectangle blitPaint) {
if (frozen) {
return false;
}
return super.computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint);
}
}
That would need also freezing before the series of modifications, and with the above repaint manager, thawing before the thaw method of the repaint manager is called.
Im newish to java, and im writing a pong game.
Ive got the ball to move (Thats my first priority) and make it apear on screen.
But for some Reason its leaving a black line behind it, I dont know if im supposed to errase this or something, but heres the code of my 2 classes (The Ball Class is just a class to save the info of the ball)
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class Main extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static int Width=650;
public static int Height=600;
public boolean Running=false;
public Thread thread;
public Ball ball = new Ball();
public static void main(String[] args){
Main game = new Main();
JFrame frame = new JFrame();
frame.setSize(Width+25, Height+49);
frame.setTitle("Pong By Poo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
frame.add(game);
game.start();
}
public void start(){
if(Running==true){
return;
} else {
Running=true;
thread = new Thread(this);
thread.start();
}
}
public void run(){
while(Running==true){
try{
Thread.sleep(5);
Draw();
Update();
ball.YVelocity();
ball.XVelocity();
} catch(Exception e){}
}
}
public void Draw(){
BufferStrategy bs = this.getBufferStrategy();
if(bs==null){
createBufferStrategy(2);
} else {
Graphics g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillOval(ball.BallLocationX, ball.BallLocationY, 20, 20);
g.dispose();
bs.show();
}
}
public void Update(){
if(ball.BallLocationX==0) {
ball.BallMovementX=true;
System.out.println("Ball has hit the Left");
}
if(ball.BallLocationX==Width) {
ball.BallMovementX=false;
System.out.println("Ball has hit the Right");
}
if(ball.BallLocationY==0){
ball.BallMovementY=true;
System.out.println("Ball has hit the Top");
}
if(ball.BallLocationY==Height){
ball.BallMovementY=false;
System.out.println("Ball has hit the bottom");
}
}
}
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Ball extends JPanel{
public int BallLocationX;
public int BallLocationY;
public boolean BallMovementY; //true makes the ball go up, false makes it go down
public boolean BallMovementX; //True makes the ball go right, false makes it go left.
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillOval(BallLocationX, BallLocationY, 10, 10);
}
//moves the ball left to right
public int YVelocity(){
if(BallMovementY==true){
BallLocationY++;
} else {
BallLocationY--;
}
return BallLocationY;
}
//Moves the ball up and down
public int XVelocity(){
if(BallMovementX==true){
BallLocationX+=2;
} else {
BallLocationX-=2;
}
return BallLocationX;
}
}
Please help!
Essentially, everywhere your ball moves stays permanently Color.BLACK on your Canvas. If you want to get rid of that, you need to refresh every time the ball moves and repaint the Canvas to Color.WHITE (or whatever) before painting the ball's position again.
Specifically, look at your code here:
public void Draw(){
BufferStrategy bs = this.getBufferStrategy();
if(bs==null){
createBufferStrategy(2);
} else {
Graphics g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillOval(ball.BallLocationX, ball.BallLocationY, 20, 20);
g.dispose();
bs.show();
}
}
There is no logic here that overwrites your previous changes to the Canvas indicating the ball's location.
Also, as a side nitpick, Java standards are to have method names in camelCase.
And to answer the question in the comments: there is nothing in the Canvas API which automatically understands what you want the default background of the Canvas to be and can reset all other graphics attributes to that. To get this functionality, however, all you'll need to do is repaint your default layout (whether it's all one color, or a basic background image, or anything else) before painting the ball's position on top of it.