How can I repaint local field on JPanel? I'm writing a snake game. When the snake moves to the next cell, we need to repaint only changed cell (not whole JPanel). Cell can throw PropertyChangeEvent object and then I can call repaint(x, y, h, w) method. Is this the right way?
First of all, I'll assume that you really do want to use a panel instead of a table, though I think you may want to look into the JTable component because that may be better suited to your application.
The answer to your question is that you shouldn't explicitly request a repaint for a cell. Instead each cell should be represented as an instance of a JComponent subclass that contains code like the following:
public class SnakeGameCell extends JComponent {
private boolean snakePresent;
public boolean isSnakePresent() {
return snakePresent;
}
public void setSnakePresent(boolean present) {
if (snakePresent != present) {
snakePresent = present;
repaint();
}
}
}
In other words, each component should be responsible for knowing when it should be repainted and for making that happen when appropriate. It's rare to find a situation where it's appropriate for repaint() to be invoked "externally". To put it another way, don't have the controller class responsible for initiating the repaint -- make the thing (component) that needs to be repainted decide for itself.
Related
I am trying to create a JFrame with numerous JPanels and sections of text, all of which have default colors (panels have background of white, and text is black). I would like to allow the user to change these dynamically at run time, so I have implemented a JColorChooser dialog, where I can click on a button, have the Dialog display, and from there I am able to save the Color.
My default colors are set up in instance variables, as so:
private Color panelColor;
private Color textColor;
To set the background color of my panels, I use:
JPanel samplePanel = new JPanel();
samplePanel.setBackground(panelColor); //panelColor has already been instantiated
This works fine, making the panel white. However when I try to use the JColorChooser to load a new Color for use, the panels do not change. The code I use after a Color is selected is:
if(newColor != null)
{
panelColor = newColor;
myFrame.repaint();
}
All my Components are inside a single JFrame, and I thought that calling repaint() on the JFrame would in turn call paintChildren() and cascade all the way down to the lowest level.
I believe that my issue lies in my understanding of how objects are handled in Java. I know that objects are passed-by-reference, but I am also assuming that when I set the background color of the Components, this is achieved through a pass-by-reference scenario, which I'm beginning to doubt. When I call setBackground(), does the Color parameter that I pass in end up as a copy through pass-by-value?
If my hunch is correct, does this mean I should just write up a method that has all the components that require their backgrounds to be changed, and call setBackground() of each, passing in the Color variable with the new value?
Cheers
Due to the fact that the Color object is passed-by-value to the Component when using setBackground(), as markspace suggested, each Component will have its individual method called.
Using a modified version of the code provided by Dan O in this thread: Iterate through all objects in Jframe,
I have come up with a solution that will allow modification of each JPanel within a JFrame (or any Container for that matter). It is a recursive solution, so any nested Container instances will also have their children iterated through.
public void setPanelColor(Container parent)
{
for(Component c : parent.getComponents())
{
if(c instanceof Container)
{
if(c instanceof JPanel)
{
c.setBackground(panelColor);
}
setPanelColor((Container)c);
}
}
}
As I am using an instance variable (panelColor) to hold the Color, I can reference it from within the method, but you could easily pass it in as a parameter and substitute panelColor for the name of the passed in Color variable.
I'm currently able to draw rectangles, elipses, and lines in Java by means of adding a component that extends JComponent in which I modify a paintComponent method:
public class myComponent extends JComponent
{
public void paintComponent(Graphics g)
{
/* do simple draw stuff */
}
}
I also know how to have my class extend either JApplet or JPanel and then draw in a paint method:
public class myClass extends JPanel
{
public void paint(Graphics g)
{
/* do simple draw stuff */
}
}
But, both of these methods suffer from not allowing me to pass them parameters. In the case of a multiframe animated sprite, I could conceivably have some external variable that it reads to determine the frame number and then internally draws only the appropriate "sprite" contents based on that frame number, but I'd prefer to be able to pass the frame number to it directly. Unfortunately, not only do I not know where this is called from, I don't know where to the the Graphics g that it requires as input.
There may be a better way to accomplish what I want, to directly communicate with the draw routine to tell it to draw only what I want, whenever I desire, but I don't know how to accomplish this.
If such a method is possible, how would that be done?
If it is better to use the existing paint or paintComponent methods, how can I best pass additional information to them?
Apparently I wasn't clear in what I asked. I wish to have a component or other entity that has its own paintComponent or paint method, inside of which, based on either a frameNumber parameter that is passed to it, or apparently-more-likely, a class property such as frameNumber that it can access, the method determines which frame of a sprite to draw.
Importantly, though, I wish to be able to re-call paint or paintComponent to redraw the sprite when the frame number changes. My big confusion comes in not knowing how to re-call the method, which, to the best of my understanding, only is called when the frame is resized or otherwise redrawn.
So, how can I redraw my component/object/entity, frame-by-frame?
Firstly, don't override paint, use paintComponent.
Secondly, you need to define some kind of model which records the state of all the graphical objects. When paintComponent is called, you then need to render that state. Instead of trying to pass parameters to the paint methods, you should have a method which allows the paint methods to access the model (ie getModel) which is passed to the component at some earlier time.
Then the update engine would update the model and the component would paint that model
For an example ... Use a timer when a key is pressed
I could conceivably have some external variable that it reads to determine the frame number and then internally draws only the appropriate "sprite" contents based on that frame number
Your class would need to have some internal state, i.e. instance variables.
You can then inspect those within the paint method.
Think "member variables":
public class MyClass extends JPanel {
private final int frameNumber;
public MyClass() {
this(0);
}
public MyClass(int f) {
this.frameNumber = f;
}
public void paint(Graphics g)
{
if (this.frameNumber == x) {
/* do simple draw stuff */
}
}
}
I have looked all round, and no one seems to have an answer for me. I have a JPanel where I add/remove labels from it while it;s running. The problem is that every time I add a new label to it I have to revalidate() it so the new label shows up. Every time I revalidate(), it I get a very annoying flicker, and all my labels ghost up to the top of my window for a millisecond then return to what they were doing. The only time I call revalidate() is after I add a label; I do not change the paint method or anything only did
public void paintComponent(Graphics page)
{
super.paintComponent(page);
}
so that my page would not look bad. I could really use some help on this. I've made a asteroids game using JFrame, Jlabel and Jpanel game; it work great, but this flickering and component "ghosting quickly ghosting to top of panel" is just unbearable, and I know for a fact this is the revalidate() method.
Using labels (or generally GUI components) to represent quickly animating, dynamically created/moved objects is not practical. Its like using a car to drive from the living room to the kitchen.
The Swing components come with a lot of nice features, e.g. they can determine the space they require and a layout manager can use that to automatically place them nicely. Unfortunately for a game, you neither need all the nice stuff, nor do you want to pay the processing overhead involved with it. Matters are further complicated by swings multi-platform capabilities which mandate that each component has a peer component (the peer deals with rendering the component to the active look).
To solve this, you do not represent individual game objects as swing components. Instead you use a single swing component (usually JPanel) as a canvas to paint your game objects on to. You simply call repaint() for that JPanel periodically (e.g. 30 or 60 times a second). Of course you will override paintComponent() on the JPanel to do paint all the game objects using your own code.
Edit: To outline how you do your own rendering
You need to decide how to organize your game related objects. A very simple skeleton could look like this (getters/setters omitted):
public class GameObject {
public int x;
public int y;
public Image image;
}
In practice this class will have additional members (like a behavior that controls how the object moves, and maybe some states/counters for animation). It may also be abstract with a concrete subclass for each possible behavior (like ship, asteroid and bullet). All instances of GameObject are managed in some kind of collection, I assume you use a List.
When implementing the paintComponent it comes down to just loop through the list and render each objects's image:
public void paintComponent(Graphics g) {
List<GameObject> list = ... // however you obtain it in your game
for (GameObject gobject : list) {
g.drawImage(gobject.image, gobject.x, gobject.y, (ImageObserver) null);
}
}
More elaborate games may order the gameobjects to get control of how game objects overlap each other. But in principle its incredibly simple.
When I was having problems with filckering Swing components it turned out to be concurrency issues. Since you are doing a game, which means you are doing animation it may be the case here.
Take care to modify Swing components only from AWT-EVENT thread. Either use SwingUtilities.invokeLater or do your animation using javax.swing.Timer.
As a concrete example, Asteroids doesn't flicker at all. Here are a few reasons:
It runs on the event dispatch thread.
It uses JPanel, which is double buffered by default.
It paces the animation using javax.swing.Timer, updating the game's model in actionPerformed().
It overrides paintComponent(), but can safely omit super.paintComponent() because the action listener repaints the entire panel.
The listener calls paintImmediately() to avoid any potential delay; substitute repaint() to see if there's any difference on your platform.
Addendum: As shown in this counter-example, revalidate() does not cause flickering. Instead of replacing labels, update the label's icon, as shown here.
Have you considered reusing them - i.e. using setVisible(false) when they're not needed?
Not sure if adding and removing labels is the best way of achieving what you want for a game.
I'd personally be tempted to manage the drawing myself.
I am trying to understand how painting with Swing works. For that purpose I've been reading the Oracle tutorial: http://docs.oracle.com/javase/tutorial/uiswing/painting/step3.html
My question is a simple one:
Why do the two calls to the same function (repaint) have different behaviour? How does it come that the UI Delegate paints the background on the previously painted rectangle, but paints a new rectangle on the new area? I don't see any particular reason on paintComponent() for that.
I have also read http://docs.oracle.com/javase/tutorial/uiswing/painting/closer.html trying to understand the situation. It seems there is some connection to the opaque property of a component. Does this property change after we create a new rectangle, so that it is true (thus, as mentioned, the ui.update() will set it to the background color). How come it paintComponent() doesn't paint a new rectangle there?
The function is called with different parameters:
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
In the first call to repaint() squareX and squareY indicates location of the previously painted object. In second the second call, squareX and squareY is changed to the current mouse location.
From JComponent API documentation on repaint():
Adds the specified region to the dirty region list if the component is
showing. The component will be repainted after all of the currently
pending events have been dispatched.
That is, the first call to repaint() marks the previous location as dirty, the second, marks the current location as dirty. When the event (moveSquare) is completed, paintComponent is re-executed, and these two areas are updated. The red rectangle is only placed in the new location, the old location is updated to be "empty".
You are right about the opaque property, which is true for typical PanelUI implementations. In particular, paintComponent() says,
if you do not invoke super's implementation, you must honor the opaque property...If you do not honor the opaque property you will likely see visual artifacts.
Your code does honor the opaque property by invoking super.paintComponent(g). Both calls to repaint() clear the drawing rectangle to the background color. Try setting a different background color in the MyPanel constructor and omitting the super call to see the effect:
public MyPanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
setBackground(Color.cyan);
...
}
#Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.drawString("This is my custom Panel!", 10, 20);
...
}
I am using a JPanel with an OverlayLayout to draw two unrelated components on top of each other. Specifically, I have a transparent component which contains my own line drawings, and beneath that, I am using a JMapViewer component, which displays OpenStreetMap tiles. This works.
Except when the JMapViewer loads tiles asynchronously. In that case, it calls repaint() on itself when the loading is finished, and it draws itself over my line layer. My line layer cannot know this, since it cannot know when the JMapViewer chooses to repaint itself.
Now I would have expected the JPanel to repaint my line layer when the map layer repaints, since it knows the correct drawing order and knows that my line layer has to be repainted. But it doesn't do this.
How would you solve this?
JMapViewer implements TileLoaderListener and simply calls repaint() when loading is finished. You should be able to update the map and then forward the event to your layer, as suggested below.
MyViewer map = new MyViewer();
...
private class MyViewer extends JMapViewer {
#Override
public void tileLoadingFinished(Tile tile, boolean success) {
super.tileLoadingFinished(tile, success);
lineLayer.repaint();
}
}
it calls repaint() on itself when the loading is finished,
Call repaint() on the parent panel instead.
This depends on how the JMapViewer repaints itself. If it just updates its content, the parent(s) might not even notice that something has changed.
I don't know if this is the best approach, but it's the only one I can think of. You could replace the RepaintManager for the JMapComponent with a custom one that also manages repaints of your overlayed component. That way, you can guarantee that your overlayed component always gets repainted last.
...do this from the AWT event thread via SwingUtilities
RepaintManager myManager = new RepaintManager() {
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
....implemnt code here to guarentee your top level component gets repainted whenever 'c' is the JMapComponent
}
}
RepaintManager.setCurrentManager(myManager);