Blogs / Tech Blog

Swing hack: making transparent components disappear

We encounter overlapping components a lot in our application, in situations ranging from a huge drag-and-drop panel across the main frame to small, unavoidable collisions in some of our smaller components. These overlaps almost always involve a transparent component that is used to paint something cool to the screen. Since the final layout looks visually good, it’s easy to think Swing is feeling good. However, this usually isn’t true.

Even though a component is transparent, Swing may still think it occupies some area. You can test this by creating a large transparent panel over a text box and calling SwingUtilities.getDeepestComponentAt(frame, textbox_midx, textbox_midy). You’d expect it to return the textbox; however, it actually returns the transparent panel! Whoa, that’s kind of silly, but what does it matter? The most noticeable loss is the cursor of the textbox. The cursor of the transparent panel will show up instead, even though it’s visually wrong. Other losses would be anything that relies on getDeepestComponentAt, findComponentAt, etc. to return an accurate result.

So how do we make a transparent component disappear to Swing? We were using the hack described by Alex Potochkin in “A well-behaved GlassPane,” for a while, but it turns out things like Container.findComponentAt() actually use Component.contains(). Setting it to a constant false has the effect of hiding all child components as well as the transparent component itself. A better Swing hack might be one that reports a point (x, y) contained in a parent component if it is contained in any child component. That’s what I have below. Just override JComponent.contains() and you’re set!

Here’s a code snippet illustrating the point:

  @Override
  public boolean contains(final int x, final int y) {
   return childrenContains( this, x, y );
  }

  /**
   * Tests whether any of the children in basec contain the given point
   * (in basec’s coordinate system). This does not call
   * basec.findComponentAt or basec.contains.
   */
  public static boolean childrenContains(final Container basec, final int x, final int y) {
    synchronized ( basec.getTreeLock() ) {
      final int size = basec.getComponentCount();
      for ( int i = 0; size > i; i++ ) {
        final Component c = basec.getComponent( i );
        if ( null != c ) {
          if ( c.contains( x - c.getX(), y - c.getY() ) ) {
            return true;
          }
        }
      }
    }
    return false;
  }
Other Blogs