I was using Java JRE 1.6.7 and had a JComponent and a JScrollPane. I couldn't get double buffering to work on this which always resulted in a flicker. I had the buffering taken care of if I used a Canvas, but this resulted in problems when used in conjunction with a JScrollPane.
So I downloaded JRE 1.6.18 in hopes that one of these problems would be fixed. Well now the JComponent inside the JScrollPane is not drawing properly at all. It draws only the outside region of the JComponent, as if the JScrollPane is drawing on top of it except for the borders.
Here is an example of code that is not drawing..this results in a 1-pixel-wide white outline of the area where drawing should occur:
public void paint(Graphics arg0) {
Graphics2D graphics = (Graphics2D) arg0;
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, (int) getWidth(), (int) getHeight());
Any help is greatly appreciated!
-Craig
try to make an override from paintComponent(Graphics g) instead of paint(Graphics g).
paintComponent is the method you have to override for costum drawing.
Are you sure you can see the white rectangle, try to use red or something else you can see good.
It looks like you're making progress, but you might like to see the tutorial examples, too.
Martijn Courteaux's analysis is correct: you should override paintComponent(). Also, it's a bad idea to mix AWT and Swing components. Both ideas are discussed in Painting in AWT and Swing.
Scrolling shouldn't cause flickering. Here's an example that scrolls a grid of components and paints a checkerboard on the background.
import java.awt.*;
import javax.swing.*;
public class Scrolling extends JFrame {
private static final int MAX = 8;
private static final int SIZE = 480;
private static final Color light = new Color(0x40C040);
private static final Color dark = new Color(0x408040);
private static class MyPanel extends JPanel {
public MyPanel() {
super(true);
this.setLayout(new GridLayout(MAX, MAX, MAX, MAX));
this.setPreferredSize(new Dimension(SIZE, SIZE));
for (int i = 0; i < MAX * MAX; i++) {
this.add(new JLabel(String.valueOf(i), JLabel.HORIZONTAL));
}
}
#Override
public void paintComponent(final Graphics g) {
int w = this.getWidth()/MAX;
int h = this.getHeight()/MAX;
for (int row = 0; row < MAX; row++) {
for (int col = 0; col < MAX; col++) {
g.setColor((row + col) % 2 == 0 ? light : dark);
g.fillRect(col * w, row * h, w, h);
}
}
}
}
public Scrolling() {
this.setLayout(new BorderLayout());
final MyPanel panel = new MyPanel();
final JScrollPane scrollPane = new JScrollPane(panel);
scrollPane.getHorizontalScrollBar().setUnitIncrement(16);
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
this.add(scrollPane, BorderLayout.CENTER);
this.pack();
this.setSize(SIZE - SIZE / 3, SIZE - SIZE / 3);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
}
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new Scrolling().setVisible(true);
}
});
}
}
Ok, I've come up with a slight answer.
Instead of calling
scrollPane.add(containerCanvas);
I'm calling
new JScrollPane(containerCanvas);
This works in a sense. However, it now is disallowing the JScrollPane bars from showing up. I have no idea why this is, but am currently looking into it. But at least the component is drawing again.
Related
I'd like to use a ScrollPane to display an image in its Viewport, and also have a grid (or box, or any other type of registration/location marker) overlay on the image. I need the overlay to remain fixed when scrolling (meaning the image seems to move "under" the overlay). I will be scrolling the View in the Viewport at a fixed rate to provide a smooth motion, and the overlay is to provide a reference to a certain location within the Viewport. Conceptually, think of a large map scrolling in a Viewport, and the Viewport having a rectangle that does not move (relative to the Viewport itself) that marks a region that can be zoomed into based on some user action.
I presume (but have not yet confirmed) that the ScrollPane implementation handles rendering of the View efficiently (from backing store, not having to repaint the entire View (or even ViewPort) for each new partial exposure) and so would prefer not to have to Override the paint() method.
I've looked at LayerPane, and while not having mastered it, it seems that this is a brute force approach. The ScrollPane is one Component of a SplitPane and will be moved/resized. I expect that I'd have to manually maintain the correct relationship between the ViewPort and the LayerPane using absolute positioning. I'm far from an expert in GUI design & implementation and I'm sure I'm missing possibilities ranging from obvious to obscure and elegant that an experienced person would know.
I'm open to any suggestions, not just the path I've started down. Should I (can I) add a JPanel as one component in my SplitPane, then add a LayerPane to that containing my ScrollPane and the overlay (another JPanel) on different layers on the LayerPane? Is there functionality in ScrollPane to support this directly? I've looked at the Java Swing Tutorials and the API documentation but haven't seen anything yet.
Thanks.
UPDATE:
Thanks for the link, trashgod. This was much easier than I had expected. No need for LayerPane, GlassPane, xor-based compositing... I don't know why it didn't occur to me to try overriding JViewport.paint() to draw my overlay - but with the code below I validated the concept will give me what I'm looking for. Just use the subclass of JViewport and it does what I wanted (in this case overlaying a rectangle in the center of the Viewport while the image scrolls underneath). I'm no GUI expert, and the Java API is incredibly wide; I have no idea how you guys keep this all in your head - but thanks!
class MyViewport extends JViewport {
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D)g;
g2.setPaint(Color.BLACK);
g2.drawRect(getWidth()/4,getHeight()/4,getWidth()/2,getHeight()/2);
}
}
Ordinarily, "Swing programs should override paintComponent() instead of overriding paint()," as mentioned in Painting in AWT and Swing: The Paint Methods. Based on ScrollPanePaint, which draws below the scrolling content, the following example overrides paint() to draw above the scrolling content.
import java.awt.*;
import javax.swing.*;
/**
* #see https://stackoverflow.com/a/10097538/230513
* #see https://stackoverflow.com/a/2846497/230513
* #see https://stackoverflow.com/a/3518047/230513
*/
public class ScrollPanePaint extends JFrame {
private static final int TILE = 64;
public ScrollPanePaint() {
JViewport viewport = new MyViewport();
viewport.setView(new MyPanel());
JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewport(viewport);
this.add(scrollPane);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
private static class MyViewport extends JViewport {
public MyViewport() {
this.setOpaque(false);
this.setPreferredSize(new Dimension(6 * TILE, 6 * TILE));
}
#Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.blue);
g.fillRect(TILE, TILE, 3 * TILE, 3 * TILE);
}
}
private static class MyPanel extends JPanel {
public MyPanel() {
this.setOpaque(false);
this.setPreferredSize(new Dimension(9 * TILE, 9 * TILE));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.lightGray);
int w = this.getWidth() / TILE + 1;
int h = this.getHeight() / TILE + 1;
for (int row = 0; row < h; row++) {
for (int col = 0; col < w; col++) {
if ((row + col) % 2 == 0) {
g.fillRect(col * TILE, row * TILE, TILE, TILE);
}
}
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ScrollPanePaint();
}
});
}
}
Note: Setting an opaque component (for instance JTable) as a view for the scroll pane will give strange visual bugs, for instance moving the fixed blue box when scrolling. Use setOpaque(false) on the view component to fix that.
I've been trying to make this work so that there are 20 boxes each with 3 different sizes and 3 different colours that chosen at random, but i cant make them come out at different times and they just glitch into eachother and the colours are glitching together and stuff like that, anyone know how to fix it? Heres what i got so far:
import java.awt.*;
import javax.swing.*;
public class testwork extends JPanel { //JPanel is a class
int l = 0;
private int x = 10;
private int y = 500;
private void move()
{
x++;
}
boolean red = false;
boolean blue = false;
boolean green = false;
#Override
public void paint(Graphics g) { //JPanel is a class defined in
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color rectColor = new Color(0, 66, 89);
g2d.setColor(rectColor);
//belt
g2d.setColor(Color.lightGray);
g2d.fillRect(0,450,1500,200);
g2d.fillRect(700,0,200,1000);
g2d.setColor(Color.orange);
for (int i = -10000; i<10000; i=i+50) {
int m= i++;
g2d.fillRect(m, 450, 25, 200);
}
g2d.setColor(Color.DARK_GRAY);
g2d.fillRect(700, 450, 200, 200);
//boxes
while (l<=20) {
if (Math.random() < 0.5)
{g2d.setColor(Color.RED);;}
else if (Math.random() < 0.5) {g2d.setColor(Color.GREEN);}
else {g2d.setColor(Color.BLUE);}
if (Math.random() < 0.5)
{g2d.fillRect(x,y,50,50);}
else if (Math.random() < 0.5) {g2d.fillRect(x,y,50,100);}
else {g2d.fillRect(x,y,100,50);}
l++;
}
}
public static void main(String[] args) throws InterruptedException {
JFrame frame = new JFrame("Frame"); //Add our JPanel to the frame
frame.add(new attempt());//instantiate a new object
frame.setSize(1500, 1000);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
testwork p = new testwork();
frame.add(p);
while (true)
{
p.move(); //Updates the coordinates
p.repaint();
Thread.sleep(10); //Pauses for a moment
}}
}
Unfortunately, you are doing a number of things incorrectly.
Override paintComponent and not paint.
Don't use Thread.sleep. Use a Swing timer and an ActionListener
You are doing too much in the painting method. All event handling including calls to repaint() is done on the Event Dispatch Thread(EDT). So all your updating are done inside of paintComponent so only the last painted objects will be shown when you exit. Update your coordinates, data structure and anything else that needs to be painted outside of your paint method.
Put your boiler plate code inside your Testwork class constructor. Here is an example.
public Testwork() {
setPreferredSize(new Dimension(1000, 700));
Timer timer = new Timer(0, this);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
// sizes the frame and jpanel and organizes the components.
frame.pack();
// centers the window in the screen
frame.setLocationRelativeTo(null);
// sets the delay in milliseconds
timer.setDelay(100);
// starts the timer
timer.start();
}
Here would be your actionListener code.
public void actionPerformed(ActionEvent ae) {
// update any variables that need to be used int he
// paint routine here. That means if you want to move something
// update the coordinates here and then use them in the paint method.
}
when you start up, your app, do it like this
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Testwork());
}
There is much more to painting. I recommend checking out Custom Painting in the Java tutorials. Here is another example on this (SO) site.
I currently have the code for creating a JOptionPane that tiles an image to the background no matter the size I set it to :)
package test;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class TiledImage extends JPanel {
BufferedImage tileImage;
public TiledImage(BufferedImage image) {
tileImage = image;
}
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
for (int x = 0; x < width; x += tileImage.getWidth()) {
for (int y = 0; y < height; y += tileImage.getHeight()) {
g.drawImage(tileImage, x, y, this);
}
}
}
public Dimension getPreferredSize() {
return new Dimension(240, 240);
}
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File("./resource/patterngrey.png"));
TiledImage test = new TiledImage(image);
JOptionPane.showMessageDialog(null, test, "", JOptionPane.PLAIN_MESSAGE);
}
}
the problem I am having is using the same code to add an image to a JPanel background in a JFrame
here is what I have:
package test;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class TiledImage extends JPanel {
BufferedImage tileImage;
static JFrame mainFrame = new JFrame("Program Name");
static JPanel userDetailsPanel = new JPanel();
public TiledImage(BufferedImage image) {
tileImage = image;
}
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
for (int x = 0; x < width; x += tileImage.getWidth()) {
for (int y = 0; y < height; y += tileImage.getHeight()) {
g.drawImage(tileImage, x, y, this);
}
}
}
public static void main(String[] args) throws IOException {
mainFrame.setSize(400,400);
mainFrame.setLayout(new BorderLayout());
mainFrame.add(userDetailsPanel, BorderLayout.CENTER);
BufferedImage image = ImageIO.read(new File("./resource/patterngrey.png"));
TiledImage backgroundImage = new TiledImage(image);
// userDetailsPanel.setComponent(backgroundImage); //i know this line is wrong
//but i dont know how to correct it
mainFrame.setVisible(true);
}
}
Any and all help is appreciated if there is a better way of doing it that is a lot less code that would also be great. I do need to add labels and buttons on top of the background once i have my background sorted.
The background needs to be tiled as the application will have a couple of different JPanels in the JFrame with different pattern backgrounds and i would like to make the frame resizable
An instance of java.awt.TexturePaint provides a convenient way to tile a BufferedImage. Related examples may be seen here. Given a TexturePaint, you can fill a component's background fairly easily, as shown here.
private TexturePaint paint;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(paint);
g2d.fillRect(0, 0, getWidth(), getHeight());
}
You state:
Any and all help is appreciated if there is a better way of doing it that is a lot less code that would also be great.
That's not a lot of code actually. The only thing else I could suggest is that if the JPanel is not going to vary in size, create a background BufferedImage, draw your tiled images in that, and then draw the one background image in either your JPanel's paintComponent method, or in a JLabel's icon. If you go the latter route, then give the JLabel a layout manager so that it can act as a well-behaved container for your components. And make sure that anything on top of your tiled containers is not opaque if the image needs to show through, especially JPanels.
To correct the issue you're currently having, you can set your TiledImage object as the content pane of your JFrame, and then ensure any panels that get added onto it are not opaque.
That is,
public static void main(String[] args) throws IOException {
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BufferedImage image = ImageIO.read(new File("./resource/patterngrey.png"));
TiledImage backgroundImage = new TiledImage(image);
// Make backgroundImage the content pane.
mainFrame.setContentPane(backgroundImage);
mainFrame.setLayout(new BorderLayout());
// Make the userDetailsPanel not opaque.
userDetailsPanel.setOpaque(false);
mainFrame.add(userDetailsPanel, BorderLayout.CENTER);
mainFrame.setSize(400,400);
mainFrame.setVisible(true);
}
This question is based on a problem I had a while back with a simple Swing dice program. The original question I posted is here and has an accepted answer, but I'd like to know exactly what is happening, why the problem occurs, and why the solution works.
I managed to whittle down the original code to get to the core of the problem and it now looks very different:
I have two ColorPanels that each draw a coloured square
when you click on a panel the box should change colour in this order: start at black, then >red>green>blue>red>green>blue> etc
once a box has changed colour it should never be black again
However when I just call repaint() in the MouseListener, the program behaves very strangely:
I click on one panel and the square's colour changes
I then click on the other and it's square changes colour, but the first square also changes, back to black
you can see this behaviour in the gif below:
If you use getParent().repaint() instead this behaviour goes away and the program behaves as expected:
The problem only seems to occur if the panels/squares start off 'overlapping'.
If you use a layout that stops this or don't set the size small then the problem does not seem to occur.
the problem doesn't happen every time which initially made me think that concurrency problems might be involved.
The code that I had problems with in my original question did not seem to cause problems for everybody and so my IDE, jdk etc might be relevant as well: Windows 7, Eclipse Kepler, jdk1.7.0_03
The code minus imports etc is as follows:
public class ColorPanelsWindow extends JFrame{
static class ColorPanel extends JPanel {
//color starts off black
//once it is changed should never be
//black again
private Color color = Color.BLACK;
ColorPanel(){
//add listener
addMouseListener(new MouseAdapter(){
#Override
public void mousePressed(MouseEvent arg0) {
color = rotateColor();
repaint();
//using getParent().repaint() instead of repaint() solves the problem
//getParent().repaint();
}
});
}
//rotates the color black/blue > red > green > blue
private Color rotateColor(){
if (color==Color.BLACK || color == Color.BLUE)
return Color.RED;
if (color==Color.RED)
return Color.GREEN;
else return Color.BLUE;
}
#Override
public void paintComponent(Graphics g){
g.setColor(color);
g.fillRect(0, 0, 100, 100);
}
}
ColorPanelsWindow(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(1,0));
add(new ColorPanel());
add(new ColorPanel());
//the size must be set so that the window is too small
// and the two ColorPanels are overlapping
setSize(40, 40);
// setSize(300, 200);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
new ColorPanelsWindow();
}
});
}
}
So my question is, what on earth is going on here?
but I'd like to know exactly what is happening,
I see the same problems using JDK7u60 on Windows 7. Definitely seems like a bug to me.
My best guess is that it is a problem with the double buffering.
I added so debug code to the paintComponent() method.
1) When you click on the right component only its paintComponent() method is called and the panel is painted the proper color.
2) When you click on the left component only its paintComponent() method is called and the panel is painted the proper color, however the panel on the right reverts back to the black color, without invoking the paintComonent() method on the right panel. This leads me to believe that somehow an old buffer is being used (this would be the bug and I have no idea how to fix it).
The reason that getParent().repaint() works is because this forces both components to be repainted no matter which panel you click on.
I'm not sure the cause of your problem, since I cannot reproduce the misbehavior with your code, but your paintComponent override neglects to call the super's paintComponent method. Put that in and see what happens.
#Override // method should be protected, not public
protected void paintComponent(Graphics g) {
// ******* add
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, 100, 100);
}
Note that if this were my program, and this were the only painting behavior I desired, I'd simplify my program my not overriding paintComponent but rather simply calling setBackground(color); I'd also create a Color array or List and iterate through it:
public static final Color[] COLORS = {Color.RED, Color.Blue, Color.Green};
private int colorIndex = 0;
// .....
#Override
public void mousePressed(MouseEvent mEvt) {
colorIndex++;
colorIndex %= COLORS.length;
color = COLORS[colorIndex];
setBackground(color);
}
I would also override the getPreferredSize method.
e.g.,
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class MyColorsPanelDemo extends JPanel {
private static final int GAP = 20;
public MyColorsPanelDemo() {
setLayout(new GridLayout(1, 0, GAP, GAP));
setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
add(new ColorPanel());
add(new ColorPanel());
}
private static class ColorPanel extends JPanel {
public static final Color[] COLORS = {Color.red, Color.green, Color.blue};
private static final int PREF_W = 100;
private static final int PREF_H = PREF_W;
private static final Color INIT_BACKGROUND = Color.black;
private int colorIndex = 0;
public ColorPanel() {
setBackground(INIT_BACKGROUND);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
setBackground(COLORS[colorIndex]);
colorIndex++;
colorIndex %= COLORS.length;
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
private static void createAndShowGui() {
MyColorsPanelDemo mainPanel = new MyColorsPanelDemo();
JFrame frame = new JFrame("MyColorsPanelDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
I am building a small chess board for tactics, it would be a fun way to reflect upon on interests (programming and chess).
One problem I have currently face, although solved, is maintaining the board aspect ratio of 1:1.
The Board extends JPanel. Due to a problem with constraints, I have opted towards maintaining the board's physical size rather than it's rendered size. This would lead to faster operations when actually being used.
What I want it to look like, and have achieved:
The way I achieved this though seemed very hackish and is poor code by Skeet standards (thank you based Skeet).
public Frame() {
final JFrame frame = new JFrame("Chess");
final JPanel content = new JPanel(new BorderLayout());
final JPanel boardConfine = new JPanel(new BorderLayout());
final Board board = new Board();
boardConfine.addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int min = Math.min(boardConfine.getWidth(), boardConfine.getHeight());
int xBuffer = (boardConfine.getWidth() - min) / 2;
int yBuffer = (boardConfine.getHeight() - min) / 2;
board.setBounds(xBuffer, yBuffer, min, min);
}
});
boardConfine.add(board, BorderLayout.CENTER);
content.setBackground(new Color(205, 205, 205));
content.add(boardConfine, BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setContentPane(content);
frame.pack();
frame.setVisible(true);
}
As seen above, I manually set the board's size and location. Even I have stated exhaustively that this shouldn't ever be done, but I couldn't find a solution to work. I need the board to fill the maximum possible area, yet maintain the aspect ratio.
If there are any suggestions (either code or concepts) you can provide, I really thank you for taking the time to help me with this elitist conundrum.
Although not a complete solution, the example below scales the board to fill the smallest dimension of the enclosing container. Resize the frame to see the effect.
Addendum: The ideal solution would be Creating a Custom Layout Manager, where you have access to the enclosing container's geometry, and setBounds() can maintain the desired 1:1 aspect ratio. A variation of GridLayout may be suitable. Grid coordinates can be calculated directly, as shown here.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/19531648/230513
*/
public class Test {
private static class MyPanel extends JPanel {
private static final int N = 8;
private static final int TILE = 48;
#Override
public Dimension getPreferredSize() {
return new Dimension(N * TILE, N * TILE);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.gray);
int w = this.getWidth();
int h = this.getHeight();
int tile = Math.min(w, h) / N;
for (int row = 0; row < N; row++) {
for (int col = 0; col < N; col++) {
if ((row + col) % 2 == 0) {
g.fillRect(col * tile, row * tile, tile, tile);
}
}
}
}
}
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new MyPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}