Custom Swing Component Development Tip: Insets Matter

This is the first article in a series I will be posting detailing a number of tips and common pitfalls I have encountered while developing custom swing components for Java.

 

I have decided to start with a discussion around how a component's border and insets affect developing a custom swing component. To illustrate this concept I am going to ask you to put yourself into the shoes of a young developer called Toni.

 

Toni is a junior developer who has just joined a new start up company, whose strategy revolves around developing a large number of custom swing components. On his first day on the job, his technical lead Bob decides to test Toni's mettle and assigns him the task of developing the company's next big custom swing component named the Orb. The use case for the Orb states that the Orb must render a blue circle to the screen.

 

Toni is excited to start work on the Orb and immediately dives into the code. After a few hours Toni produces the following code for the Orb.


private class Orb extends JComponent {
     @Override
     protected void paintComponent(Graphics graphics) {
          super.paintComponent(graphics);                               

          //create a new graphics2D instance
          Graphics2D g2 = (Graphics2D) graphics.create();
                              

          //determine the x, y, width and height
          int x = 0;
          int y = 0;
          int w = getWidth();
          int h = getHeight();                               

          //draws the blue circle
          g2.setPaint(Color.BLUE);

          g2.fillOval(x, y, w, h);
     }
}

 

Bob is really impressed with Toni's work, the Orb meets all the requirements of the use case and flies through the testing phase without a single issue.

 

A few days later Bob informs Toni that he is preparing a demo for upper management and would really like to include the Orb in the demo, however as the demo would include a number of new components that the company has been working on, it would be necessary to add a titled border to the Orb to help identify it. Bob asks Toni if he minds quickly adding the Orb to the demo.

 

Toni gets to work including the Orb in the demo and gives an instance of the Orb a fancy border using the following code:


Orb customComponent = new Orb();
Border outterBorder = BorderFactory.createEmptyBorder(10,10,10,10);
Border innerBorder = BorderFactory.createTitledBorder("The Orb");
Border compoundBorder = BorderFactory.createCompoundBorder(outterBorder, innerBorder);
customComponent.setBorder(compoundBorder);

However when Toni runs the demo, he is horrified to discover that the Orb has not rendered correctly. The Orb seems to have totally ignored the borders when rendering. Toni begins to panic as the demo is in a matter of hours!

 

Stepping out of Toni's shoes it is time to discuss where Toni went wrong. The crux of Toni's problem is that he never knew he needed to cater for borders when rendering his custom Orb component.

 

When you apply a border to any swing component, the component's position (x and y coordinate) and dimensions (width and height) remain unchanged. This leads to the question, if applying a border to a component does not change its position or dimensions, then how are you expected to cater for the borders?

 

The answer to this question lies with the border's Insets. An Insets is a simple class that contains 4 fields: top, left, bottom and right. Insets are used to describe the padding surrounding a component. Every Border in swing is expected to return an Insets object describing how much padding to add to the component for the Border to render correctly. Below is a simple example of a Border (the code does not do much, but the key point is to notice the getBorderInsets method). The Border below returns an Insets whose top and bottom padding is 10 pixels, and whose left and right padding is 2 pixels. This is the amount of padding the border will require from the component.

 

(As a side note the constructor for Insets reads in the arguments anti-clockwise starting with top, then left, then bottom, then right).


private class ExampleBorder extends AbstractBorder {
     @Override
     public void paintBorder(Component c,Graphics g,int x,int y,int width,int height) {
          super.paintBorder(c, g, x, y, width, height);
          //code to paint a border goes here
     }

     @Override
     public Insets getBorderInsets(Component c) {
          //returns a new Inset with
          //top = 10
          //left = 2
          //bottom = 10
          //right = 2
          return new Insets(10,2,10,2);
     }
}

This sounds good, but how does a developer use this information to render his component correctly? The answer is simple, the component may call its own getInsets method which will give the developer an Inset objects describing how much padding exists around the component.

 

To illustrate the point further, lets assume we are rendering a component whose current width and height is 100 pixels and currently has no borders. The rectangular area where the component should be rendered would be:

x = 0
y = 0
w = 100
h = 100

However if we then applied a border whose Insets had a top and bottom padding of 10 and a left and right padding of 2, the rectangular area where the component should render would be different.

The x coordinate should be equal to the left padding.
The y coordinate should be equal to the top padding.
The renderable width would be the width of the component minus its left and right padding.
The renderable height would be the height of the component minus its top and bottom padding.

 

They key point to take away is that when a border is applied to a component, its location and dimensions remain the same, however its Insets are change based on the border.

 

Stepping back into Toni's now rather sweaty shoes... Toni is in a flat panic, Bob wants to demo his Orb with a border, and the component is simply not behaving! Toni decides to consults Google and discovers how borders and their insets affect the swing components. His new (correct code) looks as follows.


private class Orb extends JComponent {
     @Override
     protected void paintComponent(Graphics graphics) {
          super.paintComponent(graphics);
          //create a new graphics2D instance
          Graphics2D g2 = (Graphics2D) graphics.create();

          //determine the actual x, y, width and height
          int x = getInsets().left;
          int y = getInsets().top;
          int w = getWidth() - getInsets().left - getInsets().right;
          int h = getHeight() - getInsets().top - getInsets().bottom;

          g2.setPaint(Color.BLUE);
          g2.fillOval(x, y, w, h);
     }
}

 

Toni's new code fixes the issue a few minutes before the demo was due. Relieved Toni takes a well deserved coffee break, and vows never to forget about catering for borders ever again.

 

The moral of the story: always render within the Insets of your component and always include a test in your test pack whereby a border is applied to your custom component.

 

The extremely observant may have noticed more issues with Toni's code, however that will be discussed in the next blog.

Reply to comment | Custom Swing Components for the Java™

Awesome post.

The LATEST chatter

  • Halted all development on the twitter client. The application in its incomplete state is available at: http://tinyurl.com/6ktd8xw 3 years 36 weeks ago
  • Java Swing Components is currently undergoing a rebranding exercise to Custom Swing Components. The url however will remain the same. 4 years 6 weeks ago
  • Java Swing Components is proud to announce the release of our rater component. http://www.javaswingcomponents.com/product/rater 4 years 7 weeks ago
  • Java Swing Components is proud to announce the release of our first bundle including a fun demo. http://www.javaswingcomponents.com/products 4 years 12 weeks ago
  • New post: New Component Teaser http://tinyurl.com/35hxfnn 4 years 12 weeks ago

User login

Syndicate

Syndicate content