What I am trying to do is create an animation that creates a 'running' motion. Whenever I draw it on the screen, the last frame in the animation is left behind (So there is a trail of animation frames left behind when the sprite moves). I've tried if statements and changing the image's draw position when the frame changes:
if(a2.sceneNum() == 0)
spectre_Draw1 = (screenWidth() / 2 - 120 / 2 + 120 - 6);
else
spectre_Draw1 = 0;
g.drawImage(pic[2], spectre_Draw1, (screenHeight() / 2 - 180 / 2), null);
if(a2.sceneNum() == 1)
spectre_Draw2 = (screenWidth() / 2 - 120 / 2 + 120 - 6);
else
spectre_Draw2 = 0;
g.drawImage(pic[3], spectre_Draw2, (screenHeight() / 2 - 180 / 2), null);
if(a2.sceneNum() == 2)
spectre_Draw3 = (screenWidth() / 2 - 120 / 2 + 120 - 6);
else
spectre_Draw3 = 0;
g.drawImage(pic[4], spectre_Draw3, (screenHeight() / 2 - 180 / 2), null);
Is there a way to do this while removing the trailing images?
I know it says "Avoid responding to other answers" but as a new registrant (but long-time user) I am only eligible to write "answers" or suggest edits.. I can't add comments to an answer until I am upvoted ! :-)
However, there are just too many errors in the source code in peeskillet's answer to qualify simply as "suggested edits" :-)
Accidental interchange / confusion of ROWS and COLUMNS :
SPRITE_ROWS = 5;
SPRITE_COLUMNS = 2;
DIM_W = img.getWidth() / SPRITE_ROWS;
DIM_H = img.getHeight() / SPRITE_COLUMNS;
Further down the code, the program is clearly trying to treat rows and columns of the animation pictures in the grid correctly, but it is only because both of these are backwards that the program works. (ie there are 5 Columns and 2 Rows in the grid of images for the sample nerdgirl).
Accidental interchange of DIM_H and DIM_W (1 occurrence) :
if (x1Src >= img.getWidth() - DIM_H - 5) { // 5 to take care of precisi
"DIM_H" should be "DIM_W"
As listed, this code would cause a premature jumping to the next row of animation images in the grid (not showing the last image in each row) in the case where the individual images are either significantly taller than they are wide, or else there are many images in each row of the original grid. With the nerdgirl sample (833 x 639), the last image in each row would not have been shown if the overall grid had been just 10 pixels taller. (DIM_H = 319, img.getWidth()-DIM_H-5 = 503 and the last frame shows when x1Src = 498 ..only just made it !)
Current code as shown only processes 2 rows of animation frames (even thought it correctly calculates the SIZE of each frame by changing eg SPRITE_COLUMNS (sic) to 4 for the running Man example).
This is because the following if-test :
if (y1Src >= DIM_H - 5) { // 5 to take care of lack of prec
y1Src = 0;
... should be :
if (y1Src >= imag.getHeight() - DIM_H - 5) { // 5 to take car
y1Src = 0;
..as the current code only ever displays 2 rows of sprite images even if there were more. (This explains why the flapping bird sample works "ok" without showing the final all-white frame... look carefully and it only shows the first 2 rows of images).
Use of hard-coded 5 for "loss of precision" (meaning when the source images have not been composed evenly into the combined grid and they are not an exact multiple of the frame size) should actually refer to the number of frames present :
if (x1Src >= img.getWidth() - DIM_W - SPRITE_COLUMNS) { // - SPRITE_COLUMNS to take care of non-aligned source images (see samples)
if (y1Src >= img.getHeight() - DIM_H - SPRITE_ROWS) { // - SPRITE_ROWS to take care of non-aligned source images (see samples)
This is because the possible "loss of precision" is no more than 1 per frame in each row and column - resulting from the integer rounding that occurs when the animation image width and height are calculated. eg The nerdgirl sample at 833 x 639 calculates an animation frame size of 166 x 319, so after 5 images we have shown up to 830,638 of the original image.
However, I'd suggest the following would make both those if-statements much clearer and simpler :
if (x2Src > img.getWidth()) { // Beyond end of Row
if (y2Src > img.getHeight()) { // Beyond Last Row
... and there are a few other little tweaks as shown below.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class NerdGirl extends JPanel {
private static int SPRITE_COLUMNS = 5; // Number of columns of sprite images in source file
private static int SPRITE_ROWS = 2; // Number of rows of sprite images in source file
private static final int DELAY = 100; // inter-frame pause in milliseconds
private int dim_W; // Width of an individual animation frame
private int dim_H; // Height of an individual animation frame
private int x1Src; // top-left coordinates of current frame
private int y1Src;
private int x2Src; //bottom-right coordinates of current frame
private int y2Src;
private BufferedImage img;
public NerdGirl() {
try {
img = ImageIO.read(getClass().getResource("images/nerdgirl.jpg"));
SPRITE_ROWS = 2;
// Other sample files
//img = ImageIO.read(getClass().getResource("images/explosion.png"));
//SPRITE_ROWS = 3;
//img = ImageIO.read(getClass().getResource("images/birdflight.png"));
//SPRITE_ROWS = 3;
//img = ImageIO.read(getClass().getResource("images/running_man.png"));
//SPRITE_ROWS = 4;
//SPRITE_COLUMNS = 6;
} catch (IOException ex) {
Logger.getLogger(NerdGirl.class.getName()).log(Level.SEVERE, null, ex);
}
dim_W = img.getWidth() / SPRITE_ROWS;
dim_H = img.getHeight() / SPRITE_COLUMNS;
x1Src = 0;
y1Src = 0;
x2Src = x1Src + dim_W;
y2Src = y1Src + dim_H;
Timer timer = new Timer(DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Move right to next frame (next column)
x1Src += dim_W;
x2Src += dim_W;
if (x2Src > img.getWidth()) { // Past end of current row.
x1Src = 0; // Back to start of row
x2Src = dim_W;
y1Src += dim_H; // Move down to next Row
y2Src += dim_H;
if (y2Src > img.getHeight()) { // Below bottom of source grid of images
y1Src = 0; // Back to Top.
y2Src = dim_H;
}
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, getWidth(), getHeight(), x1Src, y1Src, x2Src, y2Src, this);
}
#Override
public Dimension getPreferredSize() {
return (img == null) ? new Dimension(300, 300) : new Dimension(dim_W, dim_H);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new NerdGirl());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Finally, please note that independent of these issues, the sample animation source grids are poorly put together (the sprite-frame alignment is not always consistent) so that in the samples on this page you will sometimes see pieces of the neighbouring frames showing. Watch the bird-frame animation above carefully and see explanatory images here and here.)
If the grids were all compiled as a whole number multiple of the individual frame-size (as is the case with the running man png image # 900x600 - even though the sample provided has picture "overlap" - see image link above), then the program code for the coordinate-checking could be even easier :
Timer timer = new Timer(DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Move right to next frame (next column)
x1Src += dim_W;
if (x1Src == img.getWidth()) { // At end of current row.
x1Src = 0; // Go Back to start of row
y1Src += dim_H; // Move down to next Row
if (y1Src == img.getHeight()) { // Past all images
y1Src = 0; // Back to Top.
}
}
x2Src += dim_W;
y2Src += dim_H;
repaint();
}
});
timer.start();
}
However, as input samples grids are unreliable in overall size and composition, the earlier code is recommended as it will cope with these inconsistencies.
I hope my answer saves some others valuable time in the long run !
Cheers,
Note: Code below in example program has some logic errors. Please see answer from Warren K for explanation and fixes
I noticed that you're trying to use different images for the animation image. You know you could use a single animation sprite (given it is formatted in a grid like pattern) and just change the locations of the certain x y positions in the drawImage method
public abstract boolean drawImage(Image img,
int dx1,
int dy1,
int dx2,
int dy2,
int sx1,
int sy1,
int sx2,
int sy2,
ImageObserver observer)
img - the specified image to be drawn. This method does nothing if img is null.
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
observer - object to be notified as more of the image is scaled and converted.
See full description API
That being said, You can use a javax.swing.Timer to animate and change the locations of the source image.
here are some examples using this same code for all the example you see below. I just changed the image and change the SPRITE_ROWS, SPRITE_COLUMNS, and DELAY accordingly. See more at How to Use Swing Times
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class NerdGirl extends JPanel {
private static final int SPRITE_ROWS = 5;
private static final int SPRITE_COLUMNS = 2;
private static final int DELAY = 150;
private int DIM_W;
private int DIM_H;
private int x1Src;
private int y1Src;
private int x2Src;
private int y2Src;
private BufferedImage img;
public NerdGirl() {
try {
img = ImageIO.read(getClass().getResource("/resources/nerd-girl.jpg"));
} catch (IOException ex) {
Logger.getLogger(NerdGirl.class.getName()).log(Level.SEVERE, null, ex);
}
DIM_W = img.getWidth() / SPRITE_ROWS;
DIM_H = img.getHeight() / SPRITE_COLUMNS;
x1Src = 0;
y1Src = 0;
x2Src = x1Src + DIM_W;
y2Src = y1Src + DIM_H;
Timer timer = new Timer(DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (x1Src >= img.getWidth() - DIM_H - 5) { // 5 to take care of precision loss
x1Src = 0;
x2Src = x1Src + DIM_W;
if (y1Src >= DIM_H - 5) { // 5 to take care of precision loss
y1Src = 0;
y2Src = y1Src + DIM_H;
} else {
y1Src += DIM_H;
y2Src = y1Src + DIM_H;
}
} else {
x1Src += DIM_W;
x2Src = x1Src + DIM_W;
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(img, 0, 0, getWidth(), getHeight(), x1Src, y1Src, x2Src, y2Src, this);
}
#Override
public Dimension getPreferredSize() {
return (img == null) ? new Dimension(300, 300) : new Dimension(DIM_W, DIM_H);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new NerdGirl());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
If anyone still have this problem I found the answer, you can use paintComponent() or if you insist on using that function, you can just use repaint() for your contentpane in a loop or timer. like code below:
public void run(){
getContentPane().repaint();
}
Related
I am having trouble getting my histogram to fill correctly.
I was given a large data file full of doubles that represented GPAs, about 5500 of them. I created a method to calculate the count, mean, and standard deviation of the data, however my last problem is to graph the data.
I am to make a histogram for each of the possible grades (12 of them) and graph them about the total of each grade.
I think I have coded the total for each grade correctly, but when it comes to actually drawing the histogram I cannot figure out the 4 arguments needed for fillRect.
I've been playing around with different variables, but nothing seems to get me close.
Any help is appreciated.
private static int[] gradeCounts(double[] stats) throws Exception{
double stdv = 0;
double sum = 0;
double sum2 = 0;
double variance = 0;
Scanner fsc = new Scanner(new File("introProgGrades.txt"));
while (!fsc.hasNextDouble())
fsc.nextLine();
int[] binCounts = new int[NUM_OF_GRADE_CATEGORIES];
double x = 0;
while (fsc.hasNextDouble()){
stats[2]++;
x = fsc.nextDouble();
sum += x;
sum2 += x * x;
if (x == 0.0)
binCounts[0]++;
else if (x == 0.6666667)
binCounts[1]++;
else if (x == 1.0)
binCounts[2]++;
else if (x == 1.3333333)
binCounts[3]++;
else if (x == 1.6666667)
binCounts[4]++;
else if (x == 2.0)
binCounts[5]++;
else if (x == 2.3333333)
binCounts[6]++;
else if (x == 2.6666667)
binCounts[7]++;
else if (x == 3.0)
binCounts[8]++;
else if (x == 3.3333333)
binCounts[9]++;
else if (x == 3.6666667)
binCounts[10]++;
else
binCounts[11]++;
}
stats[0] = sum/stats[2];
variance = (stats[2] * sum2 - sum * sum) / (stats[2]*(stats[2]-1));
stdv = Math.sqrt(variance);
stats[1] = stdv;
return binCounts;
}
What I am having trouble with:
private static void plotHistogram(int[] binCounts){
int max = Arrays.stream(binCounts).max().getAsInt();
DrawingPanel panel = new DrawingPanel (800,800);
Graphics2D g = panel.getGraphics();
g.fillRect(0, 0, 800/binCounts.length,max);
}
I think I have to iterate through the data with a for loop, but it's the parameters of fillRect that I am clueless on.
but when it comes to actually drawing the histogram I cannot figure out the 4 arguments needed for fillRect.
The JavaDocs are quite explicit in the properties and their meanings, it's just a box, with a position (x/y) and size (width/height)
public abstract void fillRect​(int x,
int y,
int width,
int height)
Fills the specified rectangle. The left and right edges of the rectangle are at x and x +
width - 1. The top and bottom edges are at y and y + height - 1. The
resulting rectangle covers an area width pixels wide by height pixels
tall. The rectangle is filled using the graphics context's current
color. Parameters: x - the x coordinate of the rectangle to be filled.
y - the y coordinate of the rectangle to be filled. width - the width
of the rectangle to be filled. height - the height of the rectangle to
be filled.
I've been playing around with different variables, but nothing seems to get me close.
So, two things come to mind, the first comes from the JavaDocs above...
The rectangle is filled using the graphics context's current
color
This is something that's easy to forget
The second is it seems that you misunderstand how painting works in Swing.
Painting in Swing has a very specific and well documented workflow. The first thing you should do is go read Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how painting works and how you should work with it.
There is never a good reason to call JComponent#getGraphics, apart from been able to return null, this is just a snapshot of the last paint cycle and will be wiped clean on the next paint pass (which may occur at any time for any number of reasons).
Instead, you will need a custom component and override it's paintComponent method instead.
You should then have a read through the 2D Graphics trail to get a better understanding of how the API works and what features/functionality it can provide.
For example....
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.Arrays;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
Random rnd = new Random();
int[] binCounts = new int[10];
for (int index = 0; index < binCounts.length; index++) {
binCounts[index] = rnd.nextInt(100);
}
JFrame frame = new JFrame();
frame.add(new DrawingPanel(binCounts));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class DrawingPanel extends JPanel {
private int[] binCounts;
private int max;
public DrawingPanel(int[] binCounts) {
this.binCounts = binCounts;
max = Arrays.stream(binCounts).max().getAsInt();
System.out.println(max);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int barWidth = 800 / binCounts.length;
for (int i = 0; i < binCounts.length; i++) {
int barHeight = (int)((binCounts[i] / (double)max) * getHeight());
// I personally would cache this until the state of the component
// changes, but for brevity
Rectangle rect = new Rectangle(i * barWidth, getHeight() - barHeight, barWidth, barHeight);
g2d.setColor(Color.BLUE);
g2d.fill(rect);
g2d.setColor(Color.BLACK);
g2d.draw(rect);
}
g2d.dispose();
}
}
}
Okay so I'm trying to make things snap to a grid I have.
This is how I snap to the screen itself:
int finalCalcX = (mouseX / Handler.gridSpace32) * Handler.gridSpace32;
int finalCalcY = (mouseY / Handler.gridSpace32) * Handler.gridSpace32;
The variable names speak for themselves I think.
Mouse coordinates divided by my tiles being 32x32 pixels, times that again to get the snap-to-grid functionality.
Now this works fine for the screen, but when I want to add it to the "map" itself, I can't just add my map x and y offsets to that, it gets messed up.
I've played around with it for about two days, and I also got it to snap to the map itself, but when I'm say, halfway in the map on both axis, the mouseX and mouseY messes the grid thing up.
It's kind of hard for me to explain, but the offset from the 0, 0 (every origins position, even the screen) PLUS the maps offset when you move away from the origin, gets added to the distance between the cursor itself and the transparent snap-to-grid tile that I'm using to test.
Basically the offset between the maps origin, and the camera, is for some reason the same offset between the cursor and the transparent tile. So the further into the map i move, the further away the tile gets from the cursor, and eventually moves outside the screen width and height...
When I move further into the map, I want the snap-to-grid functionality to stay corret, no matter where on the map I am.
Render method:
for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) {
gridSpace(graphics, (int) (x * Handler.gridSpace32 - handler.getCamera().getOffsetX()),
(int) (y * Handler.gridSpace32 - handler.getCamera().getOffsetY()));
checkHighlight(graphics);
}
}
The gridSpace is the grid itself.
Here is what's in the highlight at the moment:
int finalCalcX = (mouseX / Handler.gridSpace32) * Handler.gridSpace32;
int finalCalcY = (mouseY / Handler.gridSpace32) * Handler.gridSpace32;
graphics.setColor(new Color(100, 200, 100, 3));
graphics.fillRect(finalCalcX, finalCalcY, Handler.gridSpace32, Handler.gridSpace32);
Sorry for my terrible explanation skills, but that's the best I can do.
What am I doing wrong?
I think I recreated what you are getting, and the problem lies with usage of incorrect sign in calculations of camera position and translation. Executing code below you should get similar behavior to what you described, while uncommenting commented lines (while commenting out the ones that follow them) should give you correct behavior.
package test;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.*;
public class SnapToGrid extends JPanel implements MouseMotionListener{
private int[] camera;
private int[] mouse;
private final int gridSize = 16;
SnapToGrid() {
camera = new int[2];
mouse = new int[2];
setFocusable(true);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//g2.translate(-camera[0], -camera[1]);
g2.translate(camera[0], camera[1]);
//draw background
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
{
Color c = ((j*9) + i) % 2 == 0 ? Color.black : Color.white;
g2.setColor(c);
g2.fillRect(i*gridSize, j*gridSize, gridSize, gridSize);
}
g2.setColor(Color.blue);
int[] snappedPos = getSnappedMousePosition();
g2.fillRect(snappedPos[0], snappedPos[1], gridSize, gridSize);
}
private int[] getSnappedMousePosition() {
return new int[] {
camera[0] + mouse[0] - ((camera[0] + mouse[0]) % gridSize),
camera[1] + mouse[1] - ((camera[1] + mouse[1]) % gridSize)
};
}
public static void main (String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SnapToGrid());
frame.pack();
frame.setVisible(true);
}
#Override
public void mouseDragged(MouseEvent e) {
//camera[0] -= e.getX() - mouse[0];
//camera[1] -= e.getY() - mouse[1];
camera[0] += e.getX() - mouse[0];
camera[1] += e.getY() - mouse[1];
mouse[0] = e.getX();
mouse[1] = e.getY();
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
mouse[0] = e.getX();
mouse[1] = e.getY();
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(gridSize * 18, gridSize * 18);
}
}
One thing that I really don't like with your code, is that you are not using transformations. Using transformations allows you to separate world from viewport, submits to far easier debugging of issues like this here, and most importantly, if you want to add things like scaling or rotating later on, you only need to add few lines, as opposed to rewriting half of your render method.
Here's the application I'm building https://github.com/chrisbramm/LastFM-History-Graph.
Below is part of the controller class in src/lastfmhistoryclasses. When OutputGUIView is created it creates three components, a JPanel graphPanel, this is added to a JScrollPane graphScrollPanel and another JPanel autocompletePanel. These are then all added to the JFrame OutputGUIView. The listeners below then change the preferred size of graphPanel and the first time you press a button the UI updates to show that graphPanel has increased in size and the scrollbars of graphScrollPanel have change.
However now, if you press another button, the preferred size changes but the UI doesn't update, it will do it if you change the window dimensions by lets say maximising it.
class Zoom2000 implements ActionListener{
public void actionPerformed(ActionEvent e){
outputGUIView.graphPanel.setPreferredSize(new Dimension(screenWidth, 2000));
outputGUIView.graphScrollPanel.updateUI();
}
}
class ZoomDefault implements ActionListener{
public void actionPerformed(ActionEvent e){
outputGUIView.graphPanel.setPreferredSize(new Dimension(screenWidth, screenHeight));
outputGUIView.graphScrollPanel.updateUI();
}
}
class Zoom6000 implements ActionListener{
public void actionPerformed(ActionEvent e){
outputGUIView.graphPanel.setPreferredSize(new Dimension(screenWidth, 6000));
outputGUIView.graphScrollPanel.updateUI();
}
}
I've tried different things as well such as invalidate/validate/revalidate (and repaint) on various components all the while graphPanel just sits there and only repaints when you change the window dimensions.
Any ideas what I'm doing wrong?
EDIT UPDATE:
Here is the GraphPanel class:
package lastfmhistoryguis;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import javax.swing.*;
import de.umass.lastfm.*;
import lastfmhistoryclasses.*;
SuppressWarnings("serial")
public class GraphPanel extends JPanel {
private LastFMHistory graphData;
private int graphHeight;
private int graphWidth;
private int zoom;
private final int PAD = 20;
public GraphPanel(LastFMHistory model, int zoom){
this.graphData = model;
if (zoom != 1){
this.zoom = zoom;
}else{
this.zoom = 1;
System.out.println("getHeight() returning:" + getHeight());
}
System.out.println("Width" + getWidth() + "Height" + getHeight());
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Drawing");
Graphics2D graph = (Graphics2D) g;
if (graphData == null) {
System.err.println("No data found");
} else {
System.out.println("paintComponent Width" + getWidth() + "Height" + getHeight());
graphWidth = getWidth() - 5 * PAD;
//graphHeight = getHeight() - 2 * PAD;
graphHeight = 6000 - 2* PAD;
System.out.println(graphWidth + ", " + graphHeight);
int x0 = graphWidth + PAD;
graph.draw(new Rectangle2D.Double(PAD, PAD, graphWidth, graphHeight));
double xInc = (double) (graphWidth) / (graphData.dayMax);
double secondHeight = (double) (graphHeight) / 86400;
for (int i = 0; i <= 86400; i++) {
if (i % 3600 == 0) {
graph.draw(new Line2D.Double(x0, (i * secondHeight) + PAD,
x0 + 10, (i * secondHeight) + PAD));
String hour = Integer.toString(i / 3600);
graph.drawString(hour, x0 + 15,
(int) ((i * secondHeight) + PAD));
}
}
for (Track t : graphData.history) {
if (t.getPlayedWhen() != null) {
Color color = t.getColour();
int duration = t.getDuration();
int day = Math.abs(t.getDay());
double dayOrigin = x0 - ((day + 1) * xInc);
double timeOrigin = t.getGraphHeight() * secondHeight + PAD;
double trackHeight = duration * secondHeight;
graph.setColor(color);
// System.out.println("PLOTTING TRACK, " + day + ", " +
// dayOrigin + ", " + t.getGraphHeight() + ", " + timeOrigin
// + ", " + trackHeight);
// graph.draw(new Rectangle2D.Double(dayOrigin, timeOrigin,
// xInc, trackHeight));
graph.fillRect((int) dayOrigin, (int) timeOrigin,
(int) xInc, (int) trackHeight);
}
}
for (int i = 0; i < graphData.dayMax;){
graph.draw(new Line2D.Double(x0 - i * xInc, PAD, x0 - i * xInc, graphHeight + PAD));
i = i + 7;
}
}
}
public void zoom(int zoom){
this.zoom = zoom;
repaint();
}
}
It creates a Graphics2D object onto which a large outlining rectangle is drawn and each track rectangle is then drawn onto somewhere on this Graphics2D graph object. Now I've removed the use of setPreferredSize as recommended here but now I have the problem of when I manually set graphHeight in GraphPanel to a height say 6000, graphScrollPanel doesn't realise that graphPanel that it, containing is actually bigger as all graphHeight is doing is drawing a rectangle that is ~6000px high. So in this case should I be using setPreferredSize or is there another way to set the size of a Graphics2D object?
Don't call updateUI this is related to the UI Look and Feel API and has little to do with repainting.
What layout managers have you tried/are using?
Calls to invalidate(), repaint() should effect the parent containers layout managers and cause them to re-layout the components accordingly, but you need to remember, layout managers only use the min/max/pref size information as guides.
My best guest from your example is that you should be trying to call either
outputGUIView.graphPanel.invalidate();
outputGUIView.graphPanel.repaint();
and/or
outputGUIView.invalidate();
outputGUIView.repaint();
Calling invalidate in the scroll pane is probably not going to achieve a lot as the viewport is responsible for layout out the contents, not the scroll pane.
If you're really desperate you could try
outputGUIView.graphScrollPanel.getViewport().invalidate();
outputGUIView.graphScrollPanel.getViewport().repaint();
Ok so I am trying to add a JPanel to a JFrame as so:
gameClasses[2] = new a2();
gameClasses[2].setSize(100, 100);
menu.add(gameClasses[2]);
menu.setVisible(true);
a2() is a separate class that acts as a JPanel which I use the paintComponent to paint images to it. "menu" is the JFrame. My problem is when I call "gameClasses[2].setSize(100, 100);" it does not resize the JPanel but it stays the same size. Does anyone know what I am doing wrong or how this is supposed to be done because no one else seems to have any issues with this on the internet. Thanks.
EDIT: Here is the code related to menu and a2:
menu.setSize(swidth / 2 + swidth / 5, sheight / 2 + sheight / 5);
menu.setLocation((swidth - menu.getWidth()) / 2, (sheight - menu.getHeight()) / 3);
menu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
menu.setResizable(true);
menu.remove(main);
menu.add(gameClasses[0] = new a3());
menu.add(gameClasses[1] = new a4());
gameClasses[2] = new a2();
gameClasses[2].setSize(100, 100);
gameClasses[2].validate();
menu.add(gameClasses[2]);
menu.setVisible(true);
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class a2 extends JPanel {
public static int size = 48;
public static Image grsX = Toolkit.getDefaultToolkit().getImage("tiles/grsX.png");
public static Image grsY = Toolkit.getDefaultToolkit().getImage("tiles/grsY.png");
public static Image grsX1 = Toolkit.getDefaultToolkit().getImage("tiles/grsX1.png");
public static Image grsY1 = Toolkit.getDefaultToolkit().getImage("tiles/grsY1.png");
public a2() {
System.out.println("a2 loaded...");
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
//draw interface
for(int y = 0; y < a6.ay; y++) {
for(int x = 0; x < a6.ax; x++) {
g.drawImage(a5.Tile_Img.get(a5.ID_Tile.get(a6.area[x][y])), x * size, y * size, size, size, this);
if(x > 0) {
if(a6.area[x - 1][y].equals("00") && a6.area[x][y].equals("01")) {
g.drawImage(grsX, x * size, y * size, size, size, this);
}
}
if(x < a6.ax - 1) {
if(a6.area[x + 1][y].equals("00") && a6.area[x][y].equals("01")) {
g.drawImage(grsX1, x * size, y * size, size, size, this);
}
}
if(y > 0) {
if(a6.area[x][y - 1].equals("00") && a6.area[x][y].equals("01")) {
g.drawImage(grsY, x * size, y * size, size, size, this);
}
}
if(y < a6.ay - 1) {
if(a6.area[x][y + 1].equals("00") && a6.area[x][y].equals("01")) {
g.drawImage(grsY1, x * size, y * size, size, size, this);
}
}
}
}
repaint();
}
}
a3 and a4 are a KeyListener class and a MouseListener class that both extend JPanel
A layout is more likely to respect the preferred size than the size.
A call to pack() on the frame will make it become the minimum size needed to display the components inside. Call it after everything is added.
Don't call setLayout(null) (mentioned in comment as 'non-relevant' code). Use layouts.
I am implementing a Gantt component for SWT and this takes a bit to repaint (like, 200 ms for the whole visible part of the diagram).
Now, when I scroll, I only repaint what is needed regarding the clipping rectangle. This makes the application look very bad when I scroll fast, because then the still visible part after scrolling seems to be moved by the OS first, and when I finished painting the remaining part (the part which has become visible during scrolling), immediatly a new scrolling step begins, moving half of my diagram to the right and lets me repaint the other half. This effectively looks like my diagram flickers in the middle during scrolling.
This doesn't look really nice. Is there a way to get around this? Is this question understandable?
EDIT: Here is a "small" test program that shows exactly the behaviour described. You only need SWT in the classpath to run it.
package de.ikoffice.gui;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class SlowRepaintProblem {
public Color[] colors = new Color[501];
public SlowRepaintProblem() {
Display display = Display.getDefault();
for( int i=0; i<=500; i++ ) {
int r = ( i * 10 ) % 256;
int g = ( i * 20 ) % 256;
int b = ( i * 30 ) % 256;
colors[i] = new Color(display,r,g,b);
}
Shell shell = new Shell(display);
shell.setText("SlowRepaintTest");
ScrolledComposite comp = new ScrolledComposite(shell,
SWT.H_SCROLL | SWT.V_SCROLL | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND);
SlowRepaintingCanvas canvas = new SlowRepaintingCanvas(comp,SWT.NONE| SWT.NO_BACKGROUND);
comp.setContent(canvas);
canvas.setSize(5000,5000);
// Layouting
shell.setLayout(new GridLayout());
comp.setLayoutData(new GridData(GridData.FILL_BOTH));
shell.setBounds(50, 50, 800, 600);
// Showing the control
shell.open();
while (!shell.isDisposed()) {
try {
if (!shell.getDisplay().readAndDispatch()) {
shell.getDisplay().sleep();
}
} catch (Throwable e) {
String message = e.getMessage();
if( message == null || !e.getMessage().equals("Widget is diposed") ) {
e.printStackTrace();
}
break;
}
}
}
public static void main(String[] args) {
new SlowRepaintProblem(); // Evil constructor call to start main program flow.
}
class SlowRepaintingCanvas extends Canvas {
public SlowRepaintingCanvas(Composite parent, int style) {
super(parent, style);
addPaintListener(new PaintListener() {
#Override
public void paintControl(PaintEvent e) {
GC gc = e.gc;
Rectangle r = gc.getClipping();
gc.setAlpha(255);
// gc.setBackground(ColorUtils.WHITE);
// gc.fillRectangle(r);
int x = r.x - (r.x % 10);
int width = (r.width + r.x - x) - (r.width + r.x - x) % 10 + 10;
int y = r.y - (r.y % 10);
int height = (r.height + r.y - y) - (r.height + r.y - y) % 10 + 10;
gc.setAlpha(128);
for( int i = x; i < x+width; i+= 10 ) {
gc.setBackground(colors[i/10]);
gc.fillRectangle(i, r.y, 10, r.height);
}
for( int j = y; j < y+height; j+= 10 ) {
gc.setBackground(colors[j/10]);
gc.fillRectangle(r.x, j, r.width, 10);
}
}
});
}
}
}
SWT painting is very fast and lacking UI can be usually tracked down to slow paint methods. Hence, try to optimize the algorithm that draws your diagram! One approach could be caching - draw the diagram contents into an Image:
Image cache = new Image(Display.getCurrent(), width, height);
GC gc = new GC(cache);
and repaint only the necessary image parts when scrolling:
gc.drawImage(cache, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight);
Once the diagram changes - and only then - repaint the cache image using your complex paint method.
HTH
I took Henrik's suggestion to use an Image to buffer the painting and implemented it in your SSCCE. I see much less flickering now on my system.
addPaintListener(new PaintListener() {
#Override
public void paintControl(PaintEvent e) {
GC gc = e.gc;
Rectangle r = gc.getClipping();
int x = r.x - (r.x % 10);
int width = (r.width + r.x - x) - (r.width + r.x - x) % 10 + 10;
int y = r.y - (r.y % 10);
int height = (r.height + r.y - y) - (r.height + r.y - y) % 10 + 10;
Image image = new Image(gc.getDevice(),width,height);
GC igc = new GC(image);
// Without buffering, this code was necessary to prevent "ghost"
// scrollbars on window resize, but with buffering it is no longer
// required...it does affect the visual results however.
//igc.setAlpha(255);
//igc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_BLACK));
//igc.fillRectangle(image.getBounds());
igc.setAlpha(128);
for( int i = x; i < x+width; i+= 10 ) {
igc.setBackground(colors[i/10]);
igc.fillRectangle(i-x, 0, 10, height);
}
for( int j = y; j < y+height; j+= 10 ) {
igc.setBackground(colors[j/10]);
igc.fillRectangle(0, j-y, width, 10);
}
gc.drawImage(image, x, y);
igc.dispose();
image.dispose();
}
});
This issue is due to SWT.NO_BACKGROUND is used in your graphical SWT components.