tags:

views:

922

answers:

4

Item is a simple model class.

ItemComponent is a view for an Item which just draws simple rectangles in a given spot. A bunch of ItemComponent instances are put into a parent component that is added to the JFrame of the application (just a simple shell right now).

The view has two different display styles. I want to adjust some properties of the model, and possibly change the state (which controls the style), and then call update() to repaint.

The problem is, as far as I can tell... paint() is only EVER called once. repaint() seems to have no effect.

What's wrong?

I'm not a Swing programmer and cobbled this together from examples, so I expect it may be something trivial here I don't understand.

public class ItemComponent extends JComponent implements ItemView {

    private static final Color COLOR_FILL_NORMAL = new Color(0x008080ff);
    private static final Color COLOR_FILL_TARGET = Color.LIGHT_GRAY;
    private static final Color COLOR_OUTLINE = new Color(0x00333333);

    Item item;
    RoundRectangle2D rect;
    State state = State.NORMAL;
    float alpha = 1.0f;

    public ItemComponent(Item item) {
        this.item = item;
        this.rect = new RoundRectangle2D.Double(0, 0, 0, 0, 5, 5);
        item.setView(this);
    }

    public void setState(State state) {
        this.state = state;
    }

    public void update() {
        System.out.println("ItemComponent.update");
        setLocation(item.getLeft(), 1);
        setSize(item.getWidth(), getParent().getHeight()-1);
        rect.setRoundRect(0, 0, getWidth()-1, getHeight()-1, 5, 5);

        repaint();
        //paintImmediately(getBounds());
    }

    @Override
    public void addNotify() {
        update();
    }

    @Override
    public void paint(Graphics g) {
        System.out.println("paint");

        Graphics2D g2 = (Graphics2D)g;

        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));

        if (state == State.NORMAL) {
            System.out.println("draw normal");
            g2.setPaint(COLOR_FILL_NORMAL); // light blue
            g2.fill(rect);

            g2.setPaint(COLOR_OUTLINE);
            g2.draw(rect);
        }
        else if (state == State.TARGET) {
            System.out.println("draw target");
            g2.setPaint(COLOR_FILL_TARGET);
            g2.fill(rect);

            float[] dashPattern = { 8, 5 };
            g2.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, dashPattern, 0));
            g.setColor(COLOR_OUTLINE);
            g2.draw(rect);
        }
    }
}


Hint: I traced into repaint() and found a point where isDisplayable() was being checked, and it's returning false. It makes sure that getPeer() != null.

So my component has no peer? What's up with that? It's been added to a container which itself is added to the rest of the app. And it gets painted once, so I know it's visible.

A: 

The answer is somewhere here. Particularly the section on Painting in Swing. I'm puzzled that moving the rendering code into paintComponent() didn't work as suggested by Johannes Rössel. As the documentation there says..

The Paint Methods

The rules that apply to AWT's lightweight components also apply to Swing components -- for instance, paint() gets called when it's time to render -- except that Swing further factors the paint() call into three separate methods, which are invoked in the following order:

protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)

Swing programs should override paintComponent() instead of overriding paint().

Arnold Spence
+2  A: 

My apologies to people who looked at this already. I trimmed down the code a bit for posting and inadvertently left out a key part:

@Override
public void addNotify() {
    update();
}

I was using this to do some setup as soon as it's been added. Turns out it's pretty critical that you don't override this, or at least you need to call super.addNotify(), or a whole bunch of important initialization doesn't happen.

Changing it to this fixed the problem:

@Override
public void addNotify() {
    super.addNotify();
    update();
}
Mark Renouf
Glad you figured it out. Thanks for following up. I'm still learning Java myself. I'll add this info to 'for-future-reference' part of my brain.
Arnold Spence
A: 

A Peer is only around for AWT components (the peer is the native window system's real component that goes with the component).

Why are you doing work on addNotify anyway?

John Gardner
Please move your answer to a comment on my answer, and I can reply there...
Mark Renouf
A: 

You would want to call repaint() to cause your JComponent to repaint itself instead of update.

Rather than overriding paint( Graphics g ), instead override paintComponent( Graphics g ) and place your custom rendering code within.

Make sure to call super.paintComponent( g ) as the first line of the overridden method because some key initialization happens.

Also important to note is that depending on the LayoutManager of the container your ItemComponent is being added to, it may be necessary to set the size explicitly. I see you are attempting to do that in the update() method. You should only need to set the size once, perhaps best placed in the code that constructs your component and adds it to a container.

For what you are trying to do, it should only be necessary to override paintComponent(). You should remove all other overridden methods.

Call repaint() from setState(). If the size of the component depends on the state passed in, you may want to call setSize() just before repaint().

Hope that helps.