Monday, October 18, 2010

GridBagLayout honoring PreferredSize

I was recently asked to provide an example of a Swing Metawidget layout where some JComponents had a preferredSize. By default org.metawidget.swing.layout.GridBagLayout just stretches everything as wide as possible, but sometimes this is not what you want.

Complete example below. Some points of note:
  • The example includes a CustomWidgetBuilder that chooses JTextField instead of JSpinner for int fields. Note you shouldn't do this by extending SwingWidgetBuilder! You should just write a lightweight WidgetBuilder for the widget you're interested in, and return null for everything else. Leave CompositeWidgetBuilder to chain all the WidgetBuilders together

  • There's a CustomWidgetProcessor that sets all JTextFields to the same preferredSize. It leave other components (such as JCheckBox and JTextArea) alone

  • There's a CustomLayout that tweaks org.metawidget.swing.layout.GridBagLayout to honor preferredSize
Hope that helps!

package com.myapp;

import static org.metawidget.inspector.InspectionResultConstants.*;

import java.awt.*;
import java.util.Map;

import javax.swing.*;

import org.metawidget.inspector.annotation.*;
import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.layout.GridBagLayout;
import org.metawidget.swing.widgetbuilder.SwingWidgetBuilder;
import org.metawidget.widgetbuilder.composite.*;
import org.metawidget.widgetbuilder.iface.WidgetBuilder;
import org.metawidget.widgetprocessor.iface.WidgetProcessor;

public class Main {

   @SuppressWarnings( "unchecked" )
   public static void main( String[] args ) {

      // Model

      Person person = new Person();

      // Metawidget

      SwingMetawidget metawidget = new SwingMetawidget();
      metawidget.setWidgetBuilder( new CompositeWidgetBuilder<JComponent, SwingMetawidget>(
            new CompositeWidgetBuilderConfig<JComponent, SwingMetawidget>().setWidgetBuilders(
                  new CustomWidgetBuilder(),
                  new SwingWidgetBuilder() ) ) );
      metawidget.addWidgetProcessor( new CustomWidgetProcessor() );
      metawidget.setMetawidgetLayout( new CustomLayout() );
      metawidget.setToInspect( person );

      // Frame

      JFrame frame = new JFrame( "Example" );
      frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      frame.getContentPane().add( metawidget );
      frame.setSize( 400, 250 );
      frame.setVisible( true );
   }

   /**
    * Model
    */

   public static class Person {

      private String   mName;
      private int      mAge;
      private boolean   mRetired;
      private Address   mAddress   = new Address();
      private String   mNotes;

      public String getName() {

         return mName;
      }

      public void setName( String name ) {

         mName = name;
      }

      @UiComesAfter( "name" )
      public int getAge() {

         return mAge;
      }

      public void setAge( int age ) {

         mAge = age;
      }

      @UiComesAfter( "age" )
      public boolean isRetired() {

         return mRetired;
      }

      public void setRetired( boolean retired ) {

         mRetired = retired;
      }

      @UiComesAfter( "retired" )
      public Address getAddress() {

         return mAddress;
      }

      public void setAddress( Address address ) {

         mAddress = address;
      }

      @UiLarge
      @UiComesAfter( "address" )
      public String getNotes() {

         return mNotes;
      }

      public void setNotes( String notes ) {

         mNotes = notes;
      }
   }

   static class Address {

      private String   mStreet;
      private String   mCity;
      private String   mState;

      public String getStreet() {

         return mStreet;
      }

      public void setStreet( String street ) {

         mStreet = street;
      }

      @UiComesAfter( "street" )
      public String getCity() {

         return mCity;
      }

      public void setCity( String city ) {

         mCity = city;
      }

      @UiComesAfter( "city" )
      public String getState() {

         return mState;
      }

      public void setState( String state ) {

         mState = state;
      }
   }

   /**
    * Custom WidgetBuilder.
    * <p>
    * You don't have to extend <code>SwingWidgetBuilder</code>. You just build the one widget
    * you're interested in, and return null for all the rest. Rely on
    * <code>CompositeWidgetBuilder</code> to chain WidgetBuilders together.
    */

   static class CustomWidgetBuilder
         implements WidgetBuilder<JComponent, SwingMetawidget> {

      public JComponent buildWidget( String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {

         if ( "int".equals( attributes.get( TYPE ) ) ) {
            return new JTextField();
         }

         return null;
      }
   }

   /**
    * Custom WidgetProcessor.
    * <p>
    * Tweak similar widgets based on their type. For example, set all <code>JTextFields</code> to
    * the same preferred size.
    */

   static class CustomWidgetProcessor
      implements WidgetProcessor<JComponent, SwingMetawidget> {

      public JComponent processWidget( JComponent component, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {

         if ( component instanceof JTextField ) {
            component.setPreferredSize( new Dimension( 100, 20 ) );
         }

         return component;
      }
   }

   /**
    * Custom Layout.
    * <p>
    * Use the new <code>setFillConstraints</code> hook to avoid filling width if
    * <code>preferredSize</code> has been set.
    */

   static class CustomLayout
      extends GridBagLayout {

      @Override
      protected void setFillConstraints( JComponent component, GridBagConstraints componentConstraints ) {

         if ( component.getPreferredSize().getWidth() == 100 ) {
            return;
         }

         super.setFillConstraints( component, componentConstraints );
      }
   }
}

0 comments: