Drawing on a JPanel, repaint request ignored.

Hi, I’ve split logic from drawing ;D with controller view model.

so in the class of my JPanel ControlPanel
I have

Game_Interface :
LEFT_MARGIN = 10;
CONTROL_PANEL_WEIGHT = 250;
PANEL_HEIGHT = 150;

public class ControlPanel extends JPanel implements Game_Interface {

  protected void paintComponent(Graphics g) {
    // paint frame contents first...
    super.paintComponent(g);
    // then, make sure lightweight children paint
    g.drawString("Next Piece:", LEFT_MARGIN,18);
    g.drawString("Move with the ArrowKeys", LEFT_MARGIN, 300);
    g.drawString("Drop: Down ArrowKey", LEFT_MARGIN, 320);
    g.drawString("Glide: Spacebar", LEFT_MARGIN, 340);
    g.drawString("Pauze: P", LEFT_MARGIN, 360);
  }
  public Dimension getPreferredSize() {
    return new Dimension(CONTROL_PANEL_WEIGHT, PANEL_HEIGHT);
  }

and the problem occurs here:

  public void draw_Score(int score, Graphics2D g) {
    g.setColor(Color.BLACK);
    repaint(LEFT_MARGIN, 150,150,80);
    g.drawString("Score: " + score, LEFT_MARGIN, 150);
  }
  1. I’m trying to repaint (erase to default background) a pixel rectangle
  2. draw a text (the score)

at the moment the second score overlaps the first score. It’s Ignoring my repaint request!
I alsoo would like to add the draw_Score into the paintComponent BUT then i need to store the score and the JPanel shouldn’t know what the score is?

I’m thinking/ Doing something wrong?

A call to the repaint method makes sure that a repaint request is appended to the swing event queue. Swing constantly dispatches events from the eventqueue, eventually reaching the repaint request. However, you draw the string right after the repaint call, meaning that the repainting will be performed AFTER you have drawn the string. This is your problem.

The solution involves making sure that no drawing operation (by drawing operations I mean method calls on Graphics objects, as opposed to, say, repaint() requests) takes place outside of the paintComponent method, which is the normal way to do things (unless you want to draw on an image buffer or something). Throwing Graphics2D objects around OUTSIDE the paintComponent method is sure to cause trouble. Also, for the sake of things being easy, just call repaint() without any argument. If the game gets sluggish, you can worry about limiting the repaint rectangle at some other time.

As for your last question, the JPanel might not need to know what the score is, but it could call a method which returns the score every time it is drawn, i.e. something like game.getScore(). That way the panel will just have to know one object which takes care of the game-related stuff such as scoring.

Thx for the excellent reply Ask_Hjorth_Larsen :slight_smile:
I understand what I done wrong. ;D

  /**
   * Tells the controller what to draw.
   */
  private void paintScreen() {

    if( board != null)
      controller.draw_GameBoard(board);

    if( showFPS )
      controller.draw_FPS(usedTime);

    if( quit )
      controller.draw_Message(Color.RED, "Game Over");

    if( showStartScreen())
      controller.draw_Message(Color.GREEN, "Welcome, fancy a quick game?");
}

But to folow the model - view - controller thing
I realy have to give arguments to the controller and then the controller passes them through to the JPanel. At least I think ???
So I have 2 options:

Wrap all my custom functions into the paintComponent function as in ??? but it’s not allowed to call it directly (have to use repaint)

  protected void paintComponent(Graphics g, Color col1, String msg,Color scoreCol, int score ) {
    super.paintComponent(g);
    g.setColor(col1);
    g.drawString("Next Piece:", LEFT_MARGIN,18);
    g.setColor(scoreCol);
    g.drawString("Score: " + score, LEFT_MARGIN, 150);
  }

or I make a bufferImage and paint on that
and when I’m finished I would call repaint() and draw the bufferImage?

First of all, about MVC: it wouldn’t be necessary to use the controller for any communication between view and model. You will want the view to have direct access to the model, since the view is likely a graphical representation of (some of the) model. See the graph here: http://en.wikipedia.org/wiki/Model-view-controller .

The controller should basically handle the way in which user input affects the model. E.g. when the user presses ‘space’, the controller adds a ‘missle’ object somewhere in the model, or something similar. Thus, the paintScreen() method would have something to do with the view, not the controller - something like view.drawGameBoard, view.drawFPS, etc. (The underscore, by the way, is against java naming conventions).

I notice that there are some extra arguments in the paintComponent method you listed. When a repaint event is resolved by swing, it will call paintComponent(Graphics), and any method with the name paintComponent which accepts a different set of parameters will NOT be invoked.

You could do something like


protected void paintComponent(Graphics g){
  super.paintComponent(g);
  if(showScore){
    int score = model.getScore();
    g.drawString(.... appropriate arguments ... )
  }
}

You could also use an image as a buffer, true, but at present I think you should just get used to the way swing works and not care about the other stuff.

I understand :slight_smile:

I changed everything, and now the view only get’s painted when Swing thinks it’s needed (minimize, maximize, other screen before part of the view)
I understand why, and how to solve it (I need to call repaint) but
How would I call the repaint() from the model game.run loop

Should I set a boolean when there should be a repaint
then some sort of loop that checks when to repaint inside the view
and when we repainted set the boolean back to false?

or just put repaint() in the paintComponent method?
or some other way ???

I’m nearly there ;D
thanks for the help already given realy appriciated :wink:

simply call repaint() as required by swing and wait for the component to refresh its content. DON’T put the repaint() call in the paintComponent() block, otherwise you’ll get an undesirable infinite loop. That is, repaint() sends an update-is-required-event to Swing which will satisfy the request as soon as possible by calling the update() component method.
Usually, there’s a steady RepaintManager instance that is intended to check which are the part of the component to be actually repainted. These fonctions check for any a.k.a “dirty regions” to repaint. :smiley:

simply call repaint() as required by swing ??? => MVC model doesn’t know the view…
all my paint functions are within the paintComponents method.

  /**
   * Custom PaintComponent Method
   * @param g Graphical Context
   */
  protected void paintComponent(Graphics g) {
    // paint Frame contents first...
    super.paintComponent(g);

    Graphics2D g2D = (Graphics2D) g;

    if( canShowStartScreen())
      drawMessage(Color.GREEN, "Welcome", g2D);
  }
  private void drawMessage(Color col, String msg, Graphics2D g) {
    Dimension size = getSize();
    if (col == null)
      g.setColor(Color.WHITE);
    else
      g.setColor(col);

    g.drawString(msg, 55, (float) size.getHeight()/2);
  }

It goes to the method once, when building the gui.
Then It only draws when Swing dirty regions come in.

When I add repaint to paintComponent :smiley: I get 99% cpu usage doh :-X
I need to tell swing to repaint whilst not knowing about the view… sounds…like trouble ::slight_smile:

try not to call super.paintComponent when you paint a frame (you’ll certainly loose the graphics access) ! Plus, verify that the messages you want to display are on their correct positions when you make calculations, like width divided, that means to log the args to std console output, too !
That be my recommandations. :smiley:

[quote]try not to call super.paintComponent when you paint a frame (you’ll certainly loose the graphics access)
[/quote]
I paint inside a JPanel

The calculations aren’t finished yet :wink:

but my question still stands!

  1. Maybe you want to use active drawing and not passive drawing. There is sample code in the net for JFrame using BufferStrategy and such stuff to get a graphics from the frame and actively draw on it.

  2. You need to call all drawing in the paint method, so showScore must be called from paint or be inlined.

-JAW

Thx.

The solution was to use an EventHandler
http://java.sun.com/j2se/1.4.2/docs/api/java/beans/EventHandler.html

from http://www.ibm.com/developerworks/java/library/j-tetris/

btw I highly suggest looking and learning from the given source above for novice game devs who are just starting
as it is extremly clean and nicely done everything conform the java guidelines.

Nice Job Scott!