I've been working on an program that draws custom JComponents onto a JLayeredPane however all calls to repaint() on the components seem to do nothing yet the paintComponent method is invoked automatically when the window is re-sized.
I have been following some of the advice given here:
Why is paint()/paintComponent() never called?
But none of the solutions seem to fix my problem, update swing components on the EDT, setting component size manually before calling repaint(), calling super.paintComponent(g) in the overridden paintComponent() and calling revalidate() on the frame after adding new components (although this is clearly no the issue in this case)
Any ideas what could be stopping the call? Thanks in advance :)
Here is the code for the View and the SVGElementContainer, view.setFile() is the entry-point as it is invoked when a new document needs to be displayed.
public class View extends JLayeredPane implements SVGViewport {
private SVGDocument document;
//Array list of the SVGElementContainer components
private ArrayList<SVGElementContainer> elemContainers;
private SVGFrame frame;
private int elemCount;
private Border viewBorder;
private int borderWidth = 1;
//panels displayed on the JLayeredPane
private JPanel backgroundPanel;
/** Creates a new view */
public View(SVGFrame frame) {
super();
this.frame = frame;
elemCount = 0;
elemContainers = new ArrayList<SVGElementContainer>();
viewBorder = BorderFactory.createLineBorder(Color.BLACK, borderWidth);
}
public float getViewportWidth() {
return getWidth();
}
public float getViewportHeight() {
return getHeight();
}
// paints all elements and adds them to the JLayeredPane
public void paintAllElements(){
System.out.println("Painting all elements");
// Paint document
for (SVGElement elem : document) {
//only paint stylable (rect, line, circle) elements
if (elem instanceof SVGStylable){
//create a new SVGElementContainer
SVGElementContainer newElemCont = new SVGElementContainer();
//add component to JLayeredPane
elemCount++;
this.add(newElemCont, new Integer(elemCount + 1));
//set the current element within its container and calls repaint() on the component
System.out.println("Painting element #" + elemCount);
newElemCont.setElement(elem);
newElemCont.repaint();
}
else {
System.out.println("Skip painting group element!");
}
}
}
/** Gets the document currently being displayed by the view. */
public SVGDocument getDocument() {
return document;
}
/** Sets the document that the view should display.
*
* #param document the document to set
*/
public void setDocument(SVGDocument document) {
this.document = document;
//paintBackground();
paintAllElements();
revalidate();
}
public void revalidate(){
//calls validate() on the frame in order to display newly added components
frame.getContentPane().validate();
}
}
public class SVGElementContainer extends JPanel{
private SVGElement elem;
public SVGElementContainer(){
super();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("PAINT METHOD CALLED!");
paint2D((Graphics2D) g);
}
//paint the element onto this JComponent
public void paint2D(Graphics2D g){
if (!(elem instanceof SVGStylable)){
System.out.println("Skipping non-stylable element!");
return;
}
setOpaque(false);
Shape shape = elem.createShape();
// get fill stroke and width properties
SVGStylable style = (SVGStylable) elem;
SVGPaint fillPaint = style.getFill();
SVGPaint strokePaint = style.getStroke();
SVGLength strokeWidth = style.getStrokeWidth();
// Fill the interior of the shape
if (fillPaint.getPaintType() == SVGPaint.SVG_PAINTTYPE_RGBCOLOR) {
g.setPaint(fillPaint.getRGBColor());
g.fill(shape);
}
// Stroke the outline of the shape
if (strokePaint.getPaintType() == SVGPaint.SVG_PAINTTYPE_RGBCOLOR) {
Stroke stroke = new BasicStroke(strokeWidth.getValue());
g.setStroke(stroke);
g.setColor(strokePaint.getRGBColor());
g.draw(shape);
}
}
public void setElement(SVGElement elem){
this.elem = elem;
setComponentSize();
}
private void setComponentSize(){
//this.setPreferredSize(new Dimension(
// (int)elem.getDocument().getWidth().getValue(),
// (int)elem.getDocument().getHeight().getValue()));
this.setSize(new Dimension(
(int)elem.getDocument().getWidth().getValue(),
(int)elem.getDocument().getHeight().getValue()));
}
}
I see that you're calling setOpaque(false). From the setOpaque javadoc, emphasis mine:
If true the component paints every pixel within its bounds. Otherwise, the component may not paint some or all of its pixels, allowing the underlying pixels to show through.
That "may" be the cause of paintComponent() not being called after the first time during a repaint() call. Swing can decide that the component has not "changed", and thus does not need repainting.
setting component size manually before calling repaint(), calling super.paintComponent(g) in the overridden paintComponent() and calling revalidate() on the frame after adding new components
Your code is wrong on these concepts.
a) never invoke the setSize() method. That is the job of the layout manager. You should be providing hints to the layout manager by overriding methods like getPreferredSize() to return the preferred size of your component
b) don't override the revalidate() method. The point of that tip is to use code like:
panel.add( .... );
panel.revalidate();
panel.repaint();
But I don't really know what all your code is supposed to do so I can't tell for sure if your code makes sense. I also find it strange that you are extending a JLayeredPane.
I can see extending JPanel to get the buffering and UI delegate, but the opacity is L&F dependent. Instead, you should probably start with JComponent and implement the EventListenerList plumbing for your (hypothetical) SVGEvent.
Related
Hello fellow programmers,
I've ran into a little issue in my code that I can't seem to crack. It has to do with the Jframe; Graphics area of Java. The code that I'll post below, is over a drawing method. Which purpose is to draw the "rooms" that are in a ArrayList roomList which is located in another class hence lvl. before. This off-course doesn't happen, hence the post on here.
public class LevelGUI implements Observer {
private Level lv;
private Display d;
public LevelGUI(Level level, String name) {
this.lv = level;
JFrame frame = new JFrame(name);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
d = new Display(lv, 500, 500);
frame.getContentPane().add(d);
frame.pack();
frame.setLocation(0, 0);
frame.setVisible(true);
}
private class Display extends JPanel {
public Display(Level fp, int x, int y) {
addKeyListener(new Listener());
setBackground(Color.GRAY);
setPreferredSize(new Dimension(x + 20, y + 20));
setFocusable(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
private void draw(Graphics g) {
Level lvl = new Level();
for(int i = 0; i < lvl.roomList.size(); i++) {
Room room = lvl.roomList.get(i);
g.setColor(room.floorColor);
g.drawRect(room.posX, room.posY, room.roomWidth, room.roomHeight);
}
}
}
}
To get some background info on the program. roomList is the ArrayList, and it is filled with various different sized and colored rooms. The rooms themselves are objects.
Here comes first Level class:
public class Level extends Observable {
private boolean Switch = true;
public ArrayList<Room> roomList = new ArrayList<Room>();
(...)
}
Here is the Class Room() that is used to create the rooms.
public class Room {
Color floorColor;
int roomWidth;
int roomHeight;
int posX;
int posY;
public Room(int dx, int dy, Color color) {
this.floorColor = color;
this.roomHeight = dy;
this.roomWidth = dx;
this.posY = 0;
this.posX = 0;
}
(...)
}
I've managed to locate where the problem is thought to occur, and it's the code in the for-loop. I tried switching the roomList.size() for an integer to test if it was the loop., But it wasn't. It is possible to draw a figure outside of the for-loop.
and again, the problem isn't an error message, the program simply doesn't draw the rooms that I've instructed it to draw in the method draw().
The display output looks like this:
Thanks beforehand!
Be aware that the paintComponent() method is invoked by Swing whenever the framework thinks the component needs to be rendered on screen. This usually is when the window is getting visible - initially or because some other window no longer hides the component. Such events are out of your control.
So your application should create a state and be ready to draw it anytime. Therefore you do not create state (like a level) inside the paint() or paintComponent() method. Put that elsewhere - if need be into the constructor.
Looking at you code:
As you are creating a new level inside paintComponent()/draw(), is it correct to assume that this level has no rooms associated? In that case the method is right to return without having painted anything.
If your application thinks the screen should be updated call repaint(), knowing that the paint() method will be called by the framework soon.
I'm looking to move a jpanel inside of a JFrame, and it won't seem to budge. I can set it's location in the paint() method, but it won't update in repaint. Please help! Here is my code:
public void paint(Graphics g) {
g.drawImage(playerImg, x, 50, null);
this.setLocation(x, 50);
}
public void update() {
this.repaint();
}
public void keyPressed(KeyEvent key) {
if(key.getKeyCode() == KeyEvent.VK_UP) {
x = x + 50;
System.out.println("e");
update();
}
}
"I can set it's location in the paint() method" - Don't, seriously, you should never modify the state of any component within any paint method, in fact, you've broken the paint chain by not calling super.paint, which is going to cause you no end of other problems.
Instead, set the parent containers layout manager to null, you will now find that the component disappears. This is because the layout manager is responsible for setting the size and position of the component, which you will have to take over control of.
Instead of overriding paint you should be overriding paintComponent and calling super.paintComponent. Take a look at Performing Custom Painting for more details
Problem:
Main.repaint() doesn't work for me. repaint() doesnt invoke my paint method in Main. I've tried calling validate before repainting but with no succes. Main paints perfectly initially or when resized but when i call repaint() in my code nothing is happening.
Here is how the program looks so far link
So im trying to create a level selection screen for a game in java. My game is a JApplet. I have a structure as follows:
my Main class which extends JApplet and contains an object of
LevelScreen class
LevelScreen has a paint method which Main invokes.
I tried to avoid using Swing since the layout managers gave me trouble with the design. So I've tried to make a structure which were simpler and more suited for my need.
paint() in Main.java
public class Main extends JApplet {
public static final int WIDTH = 700, HEIGHT = 500;
private static Main instance;
private LevelScreen levelScreen = new LevelScreen();
private View view = View.LEVELSCREEN;
public static Main getInstance() {
if (instance == null)
instance = new Main();
return instance;
}
#Override
public void init() {
setSize(WIDTH, HEIGHT);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if (view == View.LEVELSCREEN) {
levelScreen.mouseMoved(p);
}
}
});
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (view == View.LEVELSCREEN)
levelScreen.paint(g2);
}
public enum View {
GAME, LEVELSCREEN;
}
}
In the code of my Buttons i try to repaint Main because i want to make a fade out animation when mouse leaves the button. my problem is that i cant invoke the paint(Graphics g) in main with repaint()
Here i call repaint():
public void mouseExited() {
//start new thread to make fade out animation when mouse leave
mouseOver = false;
TimerTask task = new TimerTask() {
#Override
public void run() {
while (!mouseOver && opacity > 0.6) {
opacity -= 0.02;
//set level to 999 so i can see if the game repaints()
level = 999;
Main.getInstance().repaint(); //this doesnt work!!
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(task).start();
}
This is a problem with the way that you implement the singleton design pattern. The way you do it doesn't work for an applet, where the instance is created for you by the applet container. You can fix it by changing getInstance as follows:
public Main getInstance() {
return instance;
}
And add this line to the init method:
instance = this;
By the way, you should not override paint in a Swing component, which a JApplet is. You should override paintComponent instead, and call super.paintComponent(g) as the first line. This should fix the problem.
Main.getInstance().repaint(); //this doesnt work!!
I'm not surprised. You're not the one creating the instance of the JApplet, the browser is.
When you call this...
public static Main getInstance() {
if (instance == null)
instance = new Main();
return instance;
}
You are actually creating a second instance of the applet, which is NOT the one that is on the screen, so when you call repaint, Swing goes, "no point, you're not even displayable" and does nothing.
Without any more context of you code, you may not even need getInstance, instead reference the current instance using Main.this instead.
You should also consider taking a look at Performing Custom Painting.
Top level containers like JAppelt are not double buffered, which involves more work to paint directly to them. Instead, move your application to be based on something like a JPanel and override it's paintComponent method instead.
Painting is also a complex, multi-layered scheme. You MUST call super.paintXxx in order to preserve the paint chain and prevent any possible issues.
I have a Swing app with a glass pane over a map.
It paints dots at certain positions. When I click somewhere on the map, and the glass pane receives the message CONTROLLER_NEW_POLYGON_MARK I
want do display an additional dot at the position specified in the event data (see MyGlassPane.propertyChange).
The glass pane class is called MyGlassPane. Using the debugger I validated that addPointToMark is actually called in propertyChange.
But no additional dots appear on the screen.
How can I change the code so that PointSetMarkingGlassPane.paintComponent is called whenever an event (IEventBus.CONTROLLER_NEW_POLYGON_MARK) is fired?
public class PointSetMarkingGlassPane extends JComponent implements IGlassPane {
private final ILatLongToScreenCoordinatesConverter latLongToScreenCoordinatesConverter;
private final List<Point.Double> pointsToMark = new LinkedList<Point.Double>();
public PointSetMarkingGlassPane(final ILatLongToScreenCoordinatesConverter aConverter) {
this.latLongToScreenCoordinatesConverter = aConverter;
}
protected void addPointToMark(final Point.Double aPoint)
{
if (aPoint != null)
{
pointsToMark.add(aPoint);
}
}
#Override
protected void paintComponent(final Graphics aGraphics) {
for (final Point.Double pointToMark : pointsToMark)
{
final Point positionInScreenCoords = latLongToScreenCoordinatesConverter.getScreenCoordinates(pointToMark);
drawCircle(aGraphics, positionInScreenCoords, Color.red);
}
}
private void drawCircle(Graphics g, Point point, Color color) {
g.setColor(color);
g.fillOval(point.x, point.y, 10, 10);
}
}
public class MyGlassPane extends PointSetMarkingGlassPane implements PropertyChangeListener {
public MyGlassPane(ILatLongToScreenCoordinatesConverter aConverter) {
super(aConverter);
addPointToMark(DemoGlassPane.ARTYOM);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (IEventBus.CONTROLLER_NEW_POLYGON_MARK.equals(evt.getPropertyName()))
{
addPointToMark((Point.Double)evt.getNewValue());
invalidate();
}
}
}
As I think invalidate() only flags your component to check sizes and layout. You should call repaint() to repaint your pane.
Also I am wondering why you use propertyChangeListener for mouse clicks. I would prefer just simple mouse listener + MouseAdapter and MouseEvent x, y, buttons state.
invalidate() probably won't help you, as it flags a component for layout changes, not painting changes. Why not call repaint() instead?
For better performance, you could call the repaint method which takes a Rectangle (or four ints representing a rectangle), so that only the newly added point is repainted; I would suggest changing the return type of addPointToMark from void to java.awt.Point, and have it return the result of latLongToScreenCoordinatesConverter.getScreenCoordinates, so MyGlassPane can derive a rectangle from that Point which can then be passed to a repaint method.
I have a custom, abstract class 'Panel' which extends JPanel. There aren't many differences with the two when painting. I have a Panel and I'm simulating an animation by updating the x value of an image. I have two animations right now, one that properly repaints and another than does not. This is for the one that does not. The one that works will be labelled A, the one that doesn't will be B.
A and B follow the same format. Update some variable on the Panel, calls update (a method in Panel which calls PaintComponent) and then calls repaint. It calls repaint after because this issue was with A before and was solved that way.
A: Updates an image variable.
B: Updates the x variable of an image.
The Problem: The repaint doesn't clear the old image location and so it's a choppy mess across the screen.
What I've tried:
I've seen the super.PaintComponent(g) mentioned a lot, but this
hasn't solved the problem.
I've tried changing the order for when the repaint/update methods are
called.
Repaint does not update the Panel at all. (Probably because the
painting is done in PaintComponent)
Any help would be appreciated.
Code:
Panel:
public Panel (boolean visible){
super();
this.setLayout(new BorderLayout(640, 416));//sets the Layout type of the panel
this.setOpaque(false);//Makes it so that the panel underneath can be seen where images aren't drawn
this.setVisible(visible);
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getDefaultScreenDevice();
gc = gs.getDefaultConfiguration();
}
public void paintComponent (Graphics g){
setUp();
drawOff();
setDown(g);
}
private void setUp(){
off_screen = gc.createCompatibleImage(getSize().width, getSize().height, Transparency.TRANSLUCENT);
buffer = off_screen.createGraphics();
}
protected abstract void drawOff();
private void setDown(Graphics g){
g.drawImage(off_screen,0,0,this);
off_screen.flush();
}
public void update(){
paintComponent(this.getGraphics());
}
Animation Methods (mg is the panel in question):
private void battleStart(User user) {
for (int i = 0; i < user.battle.length; i++) {
mg.battleStart(user.battleStart(i));
mg.update();
try {
Thread.sleep(150);
} catch (Exception e) {
}
mg.repaint();
}
}
private void animateStart(User user){
for (int i = 0; i < 10; i++){
mg.x = mg.x + 10;
mg.update();
try {
Thread.sleep(100);
} catch (Exception e) {
}
mg.repaint();
}
}
I think your design is way off and that is why things are not working. I'm not quite sure how your non-abstract JPanels work, but consider making your parent JPanel something more along these lines:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MyPanel extends JPanel {
private GraphicsEnvironment ge;
private GraphicsDevice gs;
private GraphicsConfiguration gc;
private BufferedImage offScreen;
public MyPanel(boolean visible) {
super();
this.setLayout(new BorderLayout(640, 416)); // strange constants for this layout.
this.setOpaque(false);
this.setVisible(visible);
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getDefaultScreenDevice();
gc = gs.getDefaultConfiguration();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
setUp();
}
});
}
#Override
// don't make this public. Keep it protected like the super's
// just draw in this method. Don't call other methods that create buffers
// or draw to buffers.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (offScreen != null) {
g.drawImage(offScreen, 0, 0, null);
}
}
private void setUp() {
offScreen = gc.createCompatibleImage(getSize().width, getSize().height,
Transparency.TRANSLUCENT);
}
// draw to the buffer outside of the paintComponent
// and then call repaint() when done
public void upDateOffScreen() {
// ?? offScreen.flush(); // I've never used this before,
// so am not sure if you need this here
Graphics2D osGraphics = offScreen.createGraphics();
// TODO: do drawing with osGraphics object here
osGraphics.dispose();
repaint();
}
}
Also and again,
Do all long processing methods off of the EDT (Event Dispatch Thread).
Never call Thread.sleep(...) on the EDT.
Consider using Swing Timers instead of using Thread.sleep for the animations.
It's OK to call repaint on your JPanel off of the EDT, but for the most part that's about it.
All other Swing methods should be called on the EDT.
Read, re-read, and study the 2D and Swing graphics tutorials.