This question is similar to Paint on a glass pane without repainting other components. But I can't figure out how to apply the solution offered there.
My problem is that we have a very complicated RootPane, lots of components, and repainting it is expensive. We also have an animation running on the JFrame's GlassPane. I've noticed that every tick of the animation is repainting the animation as it should, but then also causing everything on the underlying RootPane to also be repainted.
I've tried overriding the paint() method for the RootPane with various code, but it always causes the components on the RootPane to be wiped and lots of flickering, probably from when child components try to repaint themselves as things update during the animation:
pnlContent = new JRootPane() {
#Override
public void paint(Graphics g) {
OurGlassPaneWithAnimation glass = ...
if (glass != null && glass.animation != null && glass.animation.isAlive()) {
//Animation running, do something cool so root pane still looks good without repainting out the wazoo
} else { super.paint(g); }
}
};
Maybe putting the animation in a GlassPane is a bad idea? We've though about changing it to be in centered JPanel instead. Or is there a good way to do this using GlassPane and keeping the repaints done to the background RootPane to a minimum?
#MadProgrammer Thanks for the suggestion doing a repaint(rect) instead of just a repaint() solved the problem. Now we can crank up the animation fps as high as we want and it doesn't noticeably affect the load time of the RootPane behind it. Here's the code snippet.
while (keepOnAnimating) {
BufferedImage bi = vFrames.elementAt(0);
if (bi != null) {
// This only repaints area of image rect
// Prevents lots of repaints from happening in rootpane behind glasspane.
int x = getWidth() / 2 - bi.getWidth() / 2;
int y = getHeight() / 2 - bi.getHeight() / 2;
jc.repaint(x, y, bi.getWidth(), bi.getHeight());
} else {
jc.repaint();
}
...
}
Related
As the title suggests, my problem is that I want to be able to drag an image.
In this specific case, I want to drag an image from one JPanel (or rather my own subclass) into another (different) subclass of JPanel. Therefore, I added an MouseListener to my JPanel subclass, so that upon clicking a certain area in the panel, an image is chosen to be painted on the JFrame (subclass). Here's some code so you'll understand my problem:
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
if (x >= 10 && x < 42 && y >= 10 && y < 42) {
image = barracks; //barracks is a predefined image, created in the constructor
dragBuilding = true;
PixelMain.pixelMain.repaint(); //pixelMain is an instance of the JFrame subclass
}
}
//irrelevant code, e.g mouseMoved, ...
public void mouseDragged(MouseEvent e) {
if (dragBuilding) {
//System.out.println("GPanel mouseDragged");
PixelMain.pixelMain.repaint();
}
}
the JFrame subclass only contains the constructor and the following code:
public void paint(Graphics g) { //i would have used paintComponent, but it seems like JFrame does not have this method ...?
super.paint(g);
if (PixelMain.panelOffense.getDragBuilding()) { //panelOffense is an instance of the JPanel subclass, getDragBuilding returns a boolean that depends on whether the mouse is held down at the moment
Graphics2D g2 = (Graphics2D) g;
Rectangle2D tr = new Rectangle2D.Double((int)getMousePosition().getX(), (int)getMousePosition().getY(), 16, 16); //size of the texture
TexturePaint tp = new TexturePaint(PixelMain.panelOffense.getImg(), tr);
g2.setPaint(tp);
Rectangle2D r = (Rectangle2D) new Rectangle((int)getMousePosition().getX(), (int)getMousePosition().getY(), 16, 16); //area to fill with texture
g2.fill(r);
System.out.println("test");
}
}
Before you ask - I did move some code to other classes so it's called less often, but that's not the problem. Even if the paint method only draws a rectangle (directly on Graphics g, not Graphics2D), the rectangle flickers.
If anyone could help me figure out a solution, I'd be very thankful!
Note: I know it's probably not very elegant to draw on a JFrame or a subclass of JFrame, but I personally don't know an alternative.
Note 2: According to google/stackoverflow results or threads that I read, I should use a JPanel, which seems to be double-buffered (whatever that is, I didn't really understand that. but then again, it's almost 11 pm here). Hence, I could probably move all my components to a JPanel to solve the issue, but I wanted to try to solve the problem without doing that.
Note 3: Yes, the code belongs to a (strategy) game I'm writing, but considering that the problem is not really related to game development exclusively, I decided to post it here and not at game development stack exchange.
I have been working on a basic Java Swing application, that paints some objects into a opaque JPanel. I have been coding this app on a University MAC-PC. Yesterday I tested the program on my macbook air(after exporting the project), the behaviour of the application totally change.
The logic of the application is as follows, a Jframe that contains a JLayeredPaneL with Jpanel, in each Jpanel I paint some objects.The application is working correctly on the University laptop.
1)The JPanel is not longer transparent
//Creating Layered Panel
JLayeredPane lpane = new JLayeredPane();
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
this.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, screenSize.width, screenSize.height);
//creating Jpanel
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
this.setBackground(new Color(0, 255, 0, 0));//makes JFrame invisible
this.setVisible(true);
this.setResizable(false);
2) The JPanel does not remember what is previously drawn(it actually creates two Jpanel one what is being drawn at the moment and what was previously drawn)
I paint lines, whenever a new line is added to an array I call paintAgain(), here is the code of the paintComponent
public void paintLines(Point p)
{
arrayLines.add(p);
repaint();
//Only the point is displayed the other points are not visible,
//the other points are in another JPANEL?
}
public void delete()
{
delete = true;
arrayLines.clear();
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(delete)
{
g.clearRect(0,0,screenSize.width,screenSize.height);
delete =false;
}
else
{
g2d = (Graphics2D) g.create();
g2d.setStroke(new BasicStroke(10));
g2d.setColor(pickedColor);
//It just paints the new lines, it does not iterate through all the points
g2d.draw(new Line2D.Float(arrayLines.get(arraySize-1).getx1(), arrayLines.get(arraySize-1).gety1(),arrayLines.get(arraySize-1).getx2(),arrayLines.get(arraySize-1).gety2()));
}
}
I do not know why the behaviour of the program changes. Maybe the JRE version that i'm using?I have literally no idea since, it has never happened before
Thank you in advance
In your paintComponent method, your code as written just draws one line, and so one line is all that you see. Instead you need to use a for loop to iterate through your Point collection, arrayLines, drawing a line between points.
// note that i *must* start at 1, not at 0
for (int i = 1; i < arrayLines.size(); i++) {
int x1 = arrayLines.get(i - 1).x;
int y1 = arrayLines.get(i - 1).y;
int x2 = arrayLines.get(i).x;
int y2 = arrayLines.get(i).y;
g2.drawLine(x1, y1, x2, y2);
}
Also, where do you set delete to true ever?
Edit
Regarding your comments:
...The paint method is an example, I call paintLines() several times.
This won't matter if paintComponent draws one and only one line.
Do I have to repaint everything , everytime i make a modification in the array?
At this point, probably, yes. Later consider doing your static background drawing to a BufferedImage and then displaying that in the paintComponent almost first thing, right after the super.paintComponent(g) call, and then drawing your non-static, your moving sprites directly in paintComponent. If you know for a fact that you've only altered a portion of a component, you can call one of the repaint(...) overload methods that suggest repainting a rectangular area of the component.
The problem is this code is working in another computer and in mine it misbehaves.
The issue for me is that I have no idea why the code would have a prayer of working on another system since it is broken code.
One of the other issues you are having is the fact that you are using an alpha based color as the background to an opaque component...
myGlassPane = new JPanel()
myGlassPane.setBackground(new Color(0, 255, 0, 0));
myGlassPane.setBounds(0, 0, 400, 400);
myGlassPane.setOpaque(true);
myGlassPane.setVisible(true);
//adding item
lpane.add(myGlassPane);
Swing only knows how to paint opaque or transparent components and makes these decisions based on the opaque state of the components.
When transparent, the API knows that it must first prepare the graphics context properly and secondly, paint all components that might be beneath this one.
When using an alpha based background color, the component is unable to clear the Graphics context for painting (as filling with a transparent color doesn't do anything), this tends to mean that the Graphics context still contains what ever was painted to it previously (as the Graphics context is a shared resource).
Instead, remove...
myGlassPane.setBackground(new Color(0, 255, 0, 0));
and use
myGlassPane.setOpaque(false);
which will give you the same effect.
It will also mean you won't need
g.clearRect(0,0,screenSize.width,screenSize.height);
and can simply remove the all the elements from the arrayLines instead, which will give you the same effect...just longer lasting...
Hey :) So I'm making buttons for a game I'm making. The graphics work, at least to the extent that I don't have to fix them yet. However, click detection is a bit iffy. For example pressing where the black line is in the first picture below, triggers a response. Now obviously that point is not on the button. I have tested the buttons bounding box by drawing a rectangle around it, using it's getBounds() method (which is also used for click detection) and it draws a perfect rectangle around it. So then I tested the mouse click points and it turns out that even though the button is placed at y = 100, at the black line, the mouse point is also equal to 100... Now I have no idea why that is happening, especially because, if I place the button in the top left corner, the mouse detection correctly detects the top pixels and there is no offset...
This is rather interesting, and during my times in have had similar problems. This all really depends on why the Mouse Listener is attached to. Many people attach the listener to the frame, but draw on a panel. This can have the effects you are describing so it is usually better to either draw directly onto the frame, or attach the listener to the panel. In 99.99% of cases, I would always choose the latter. Really, no one should ever choose the latter UNLESS it's something very small.
Panels are exactly that; they're boxes which hold things, hence 'panel'. In my experiences it has always been more effective to use a panel. Frames are just the container to hold multiple panels.
Hope I could help, report your findings in a comment and/or post update.
Jarod.
Got bored so I whipped up an example of what I think is going on.
In essence, I do full rendering to a buffer (BufferedImage here). And then draw the render to the canvas. This may or may not be what you do, but I did it merely for example.
Seeing as you did say that it works fine in the top-left corner, I came to the hypothesis that scaling is the issue, since the x,y-values near the top left approach 0, and 0 * scale = 0, even a scaling of 1000 won't have any offset. The issue is when those components are not at the top-left corner, which you demonstrated for us.
Hopefully this answers your question. As for solving it, you can either accommodate for scaling, or use a letterboxing technique. Beyond those two, there are certainly many other ways to deal with this (such as fixing the screen size).
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author Obicere
*/
public class GraphicScale {
public GraphicScale(){
final JFrame frame = new JFrame("Graphic Scale Example");
final MyPanel panel = new MyPanel();
final Timer repaintTimer = new Timer(50, e -> frame.repaint());
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
repaintTimer.start();
}
public static void main(final String[] args){
SwingUtilities.invokeLater(GraphicScale::new);
}
public class MyPanel extends JPanel {
private final Rectangle box = new Rectangle(100, 100, 100, 50);
private final Dimension size = new Dimension(500, 500);
private final BufferedImage render = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
render.flush();
render();
g.drawImage(render, 0, 0, getWidth(), getHeight(), this); // Trick is that this gets rescaled!
}
public void render(){
final Graphics2D g = (Graphics2D) render.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, render.getWidth(), render.getHeight());
final Point mouse = getMousePosition();
if(mouse != null){
if(box.contains(mouse)) {
g.setColor(Color.GREEN);
g.fill(box);
}
g.setColor(Color.DARK_GRAY);
g.drawOval(mouse.x - 3, mouse.y - 3, 6, 6);
}
g.setColor(Color.BLACK);
g.draw(box);
}
#Override
public Dimension getPreferredSize(){
return size;
}
}
}
Ok, so it turns out that there was some scaling going on with the frame, however I have no idea where it came from. I prepped the game to be scalable so I did all the painting to the optimal size BufferedImage and then I scale that image to the frame. However, even when I removed that the mouse location was still offset. In the end I overcame it by finishing the scaling optimization which required finding the scale of the frame by dividing the current width and height by the optimal width and height. And then dividing the mouse location by that value.I just figured this out. Setting the size of a component and packing the frame after adding the component, results in the actual frame being that size (counting the border), yet when you retrieve the size of the frame, it disregards the border... Why does this happen?
Solved
When I did the game screen scaling, I used the actual frame's height and width to scale the screen, instead of the canvas's height and width. I changed that and now it works perfectly!
import java.awt.*;
import javax.swing.JFrame;
public class GraphicsDemo1 extends Canvas
{
public void paint( Graphics g )
{
g.setColor(Color.green);
g.drawRect(50,20,100,200); // draw a rectangle
g.fillOval(160,20,100,200); // draw a filled-in oval
g.setColor(Color.blue);
g.fillRect(200,400,200,20); // a filled-in rectangle
g.drawOval(200,430,200,100);
g.setColor(Color.black);
g.drawString("Graphics are pretty neat.", 500, 100);
int x = getWidth() / 2;
int y = getHeight() / 2;
g.drawString("The first letter of this string is at (" + x + "," + y + ")", x, y);
}
public static void main( String[] args )
{
// You can change the title or size here if you want.
JFrame win = new JFrame("GraphicsDemo1");
win.setSize(800,600);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsDemo1 canvas = new GraphicsDemo1();
win.add( canvas );
win.setVisible(true);
}
}
Thanks. awt and swing are very confusing to me.
why does it extend canvas?
Because who ever wrote it chose to do so. Only those classes that extend from Component can actually be painted to the screen and only when they are attached to a valid, visible window
When is paint called in this program?
Painting is the responsibility of the RepaintManager. It will decide when components need to be repainted and schedule a repaint event on the Event Dispatching Thread. This in turn calls (in your case update which calls) paint on your behalf.
You might like to have a read through Painting in AWT and Swing for more details on the subject
paint() is called whenever the control is invalidated and needs to repaint itself. Think of moving the app partially off screen and then back. Paint would get called to redraw...
I suppose a number of different controls could be extended to achieve the same goal which is basically to create a custom-drawn control. An existing control is extended to get the ability to draw on its surface, be placed in a JFrame, get repainted automatically etc.
I've created a custom swing component. I can see it (the grid from the paint method is drawn), but the buttons that are added (verified by println) aren't shown. What am I doing wrong?
Background information: I'm trying to build a tree of visible objects like the Flash/AS3 display list.
public class MapPanel extends JComponent { // or extends JPanel, same effect
private static final long serialVersionUID = 4844990579260312742L;
public MapPanel(ShapeMap map) {
setBackground(Color.LIGHT_GRAY);
setPreferredSize(new Dimension(1000,1000));
setLayout(null);
for (Layer l : map.getLayers()) {
// LayerView layerView = new LayerView(l);
// add(layerView);
System.out.println(l);
JButton test = new JButton(l.getName());
add(test);
validate();
}
}
#Override
protected void paintComponent(Graphics g) {
// necessary?
super.paintComponent(g);
// background
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
// grid
g.setColor(Color.GRAY);
for (double x = 0; x < getWidth(); x += 10) {
g.drawLine((int)x, 0, (int)x, getHeight());
}
for (double y = 0; y < getHeight(); y += 10) {
g.drawLine(0, (int)y, getWidth(), (int)y);
}
}
}
Setting null as the layout manager and then adding buttons will not have any effect. A layout manager is responsible for computing the bounds of the children components, and setting layout manager to null effectively leaves all your buttons with bounds = (0,0,0,0).
Try calling test.setBounds(10, 10, 50, 20) as a quick test to see if the buttons appear. If they do, they will be shown at exactly the same spot. From there you can either install a custom layout manager that gives each button the required bounds, or use one of the core / third party layout managers.
It would be easier for us to diagnose your problem if you gave us a SSCCE. As it stands, we may not have enough information to fix your problem.
I can see it (the grid from the paint
method is drawn),
I don't know what that means, there is no paint() method in the posted code. (But I suppose it is easy enough to assume that you meant paintComponent(g))
However, it looks like the problem is that you are uisng a "null layout". The children will not paint unless you manually set the size and location of the children.
You should probably read a quick tutorial on LayoutManagers. It may make things easier for you when drawing components.