Skip to main content

Swing Internals: Paint Order

Posted by wzberger on January 21, 2009 at 2:40 AM PST

Generally in Swing you can specify the paint order by applying component Z-Order (see Container#setComponentZOrder). This method is useful as long as you're using a null layout or a layout manager which makes use of constraints.

The disadvantage of using #setComponentZOrder is that it can affect component position. The image below demonstrates the output of a simple test case with Line-Axis BoxLayout.

PaintOrder.png

Code:

    JPanel p = new ColorBoxContainer();
    p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
    add(p);

    p.add(new ColorBox(Color.RED));
    p.add(new ColorBox(Color.GREEN));
    p.add(new ColorBox(Color.BLUE));

If we try to set the blue box on top by modifying z-order some curious things happen. The blue box is painted on top but the components become rearranged.

PaintOrder2.png

Code:

    JPanel p = new ColorBoxContainer();
    p.setLayout(new BoxLayout(p, BoxLayout.LINE_AXIS));
    add(p);

    ColorBox red = new ColorBox(Color.RED);
    p.add(red);
    ColorBox green = new ColorBox(Color.GREEN);
    p.add(green);
    ColorBox blue = new ColorBox(Color.BLUE);
    p.add(blue);

    p.setComponentZOrder(red, 1);
    p.setComponentZOrder(green, 2);
    p.setComponentZOrder(blue, 0);

What's going wrong under the hood?

A container holds an array with all child components. For painting, Swing (more precise JComponent#paintChildren()) iterates over the array in reverse order - this means the first added component will be painted at last. Z-order modifies the child position within this array. In case that the layout manager makes use of Container#getComponents() (as many Swing core layout managers do) there's no guarantee that the array order represents the order in which the components have been added to the container.

The image below shows the result we would like to see
- blue box is painted on top and component position is correct.

PaintOrder3.png

How can we achieve this?

Generally it's recommended to use z-oder together with a proper layout manager (or null layout). In case that you want to use your preferred (constraint-less) layout manager like Box-Layout you have to modify painting order by overriding component containers paintChildren(Graphics g) method. The code below demonstrates how to achieve this without touching z-order.

Code:

    private LinkedList arrangeChildComponents()
    {
      LinkedList components = new LinkedList();
      for (int k = 0; k < getComponentCount(); k++)
      {
        if (k == onTopIndex)
          components.addFirst(getComponent(k));
        else
          components.add(getComponent(k));
      }
      return components;
    }
   
    @Override
    protected void paintChildren(Graphics g)
    {
      final int ANCESTOR_USING_BUFFER = 1;
      final int IS_PAINTING_TILE = 2;

      synchronized (getTreeLock())
      {
        int i = getComponentCount() - 1;
        if (i < 0)
          return;

        Rectangle clipBounds = g.getClipBounds();
        if (clipBounds == null)
          clipBounds = new Rectangle(0, 0, getWidth(), getHeight());

        //create list - the first item in the list will be painted on top
        LinkedList components = arrangeChildComponents();

        for (; i >= 0; i--)
        {
          Component comp = components.get(i);

          if (!(comp instanceof JComponent) || !comp.isVisible())
            continue;

          JComponent c = (JComponent) comp;
          Rectangle cr = c.getBounds(new Rectangle());
          if (!clipBounds.intersects(cr))
            continue;

          Graphics cg = g.create(cr.x, cr.y, cr.width, cr.height);
          cg.setColor(c.getForeground());
          cg.setFont(c.getFont());
          boolean resetFlag = false;

          if (getFlag(ANCESTOR_USING_BUFFER))
          {
            setFlag(c, ANCESTOR_USING_BUFFER, true);
            resetFlag = true;
          }

          if (getFlag(IS_PAINTING_TILE))
          {
            setFlag(c, IS_PAINTING_TILE, true);           
            resetFlag = true;
          }

          c.paint(cg);
          cg.dispose();
         
          if (resetFlag)
          {
            setFlag(c, ANCESTOR_USING_BUFFER, false);
            setFlag(c, IS_PAINTING_TILE, false);
          }
        }
      }
    }

Demo Application

WebStart Demo

Download Demo Source: Download file

There's some room for optimizations, feel free to add your preferred paint order strategy.

Note:

If you wonder how BoxLayout can be used to layout overlapped components - overriding #setBounds() does the job.

Code:

    @Override
    public void setBounds(int x, int y, int width, int height)
    {
      int index = 0;
      for (Component c : getParent().getComponents())
      {
        if (c == this)
          break;
        index++;
      }
      //overlap 25 pixels
      x += index * -25;
      super.setBounds(x, y, width, height);
    }
Related Topics >>

Comments

You did make some intriguing

You did make some intriguing points over there. but i would also want to say that you should improve your overall website so that more people will come and visit your site for useful information which you have shared in the site well thanks nice visit
virginia military academy

You're right I'm sorry. You did not change the layout manager.

Have you taken a look into the source? The layout manager isn't modified. Overlapping is achieved by overriding client components #setBounds() method only - see note at the end of the article. The only reason for overriding #paintChildren() is to modify paint order.

I see you implemented custom painting and an altered layout manager. Could you have skipped the custom painting and just used an altered layout manager to overlap the components? In my experience I've either implement custom layout OR implement custom painting, but not both on the same component.