14 Replies Latest reply on Aug 24, 2010 10:34 PM by 843807

    Generate UI based on a bean?

      [I'm not sure this topic belongs in this forum, so mods, if there's a better place please move it.]

      There is a requirement for a GUI (probably Swing, though web-based would be acceptable) to allow field by field editing of a database record.

      On the client side of an RMI connection to the database, the record is a Javabean with nearly 100 fields. The database is not relational and the app does not use SQL or JDBC. We could build the UI by hand - a large and tedious task. We could generate it using reflection (probably not too hard).

      But it occurs to me that there should already exist a tool to auto-generate such a thing. IBM's VisualAge had one, but I believe it was driven off the SQL table definitions.

      Anyone know of such a thing that is driven off a Javabean, or perhaps a BeanInfo class?
        • 1. Re: Generate UI based on a bean?
          I did build one of these things via bean introspection about ten years ago. Could probably dig it up given enough incentive.
          • 2. Re: Generate UI based on a bean?
            Virtual money? :D

            You can tell me a little about it. Swing? Did you use a JTable? Or just drop label/field components on a panel? (Gridbag layout in a scroll pane would seem perfect for this.)

            The component types for the primitives are obvious enough, but there's a couple of embedded beans, some enums, and quite a few collections that I'm not entirely sure how to represent. There are some interdependent fields that are easy enough with events and actions, but generated?

            And was ... <err> is it pretty or ugly?
            • 3. Re: Generate UI based on a bean?
              I used the bean introspection suite to the max, to provide property editors and all that, and an appropriate Swing control for each property type. When the property is an Object it gets recursive of course, so whatever container I had around the original object was instantiated again inside and off we go again. I can't remember what the outer container was, sorry. This is the area where the work has to go, in, to make in pretty, the recursive construction of property editor panels was actually pretty straightforward. As you don't know anything about required space etc, unless the property editor does, you have to let the layout manager do the work. There must also have been a set of default property editors for the basic types.
              • 4. Re: Generate UI based on a bean?
                One could do a notebook, with a tab for each object property. Some could be canned. A couple are pretty elaborate so probably deserve their own page. A JTable is perfect for one.

                Drat. None of the default editors support customEditor or Paintable. Maybe just use a JTextField. But then, what's the point of using a PropertyEditor? I already have access to the accessor methods for the property. Well I guess it does handle string conversion... I wonder how difficult it would be to subclass them and set JTextField as the customEditor? I'll have to play with it. I'm already reading and writing properties using BeanInfo, so right now the challenge is fitting into the structure of a Swing program.

                If you run across your old program, I'd gladly steal code from it.
                • 5. Re: Generate UI based on a bean?
                  One thing I don't get. A PropertyEditor has a getValue() and a setValue() method. But how is an editor associated with a bean instance? There's no API for it on PropertyEditor or PropertyEditorSupport (from the Javadoc, source isn't it).

                  This isn't mentioned in the Tutorial, and the system won't let me download the spec for some reason.

                  Looking at the PropertyEditorSupport code instead of the Javadoc, it appears that the bean must register as a PropertyChangeListener of the editor. A value can certainly be passed that way, but my beans aren't set up for it. I'm back to using the read and write methods.

                  So what's a PropertyEditor used for then, just an intermediary for translating text into the internal form?

                  Edited by: DMF. on Aug 15, 2010 3:26 PM
                  • 6. Re: Generate UI based on a bean?
                    The PropertyEditor is used via getCustomEditor() to supply a Component that will render and edit instances.

                    I'm pretty sure the BeanContext came into it somewhere too but I'm sorry I cannot remember the details.
                    • 7. Re: Generate UI based on a bean?
                      This is turning out to be a royal pain in the rear. I've always had a hard time getting GridBagLayout to behave, even with visual editors. I've finally gone to GridLayout, which isn't nearly as pretty as GridBagLayout can be, but a lot easier to deal with.

                      As you might be able to tell from the Hierarchy thread, filtering the properties of interest was a bit of an adventure. The Introspector behaves very differently when on it's own vs. in the presence of a BeanInfo class.

                      In short, I'm populating a panel with labels and edit fields for the properties of JavaBeans that I have Components for (primitives). I can see it's just a matter of fleshing out type coverage, though I haven't done recursive discovery yet. But the project is doable.

                      I do want to try populating a JTable before deciding which one to use, though.

                      Say, shouldn't there be a source of component-loaded PropertyEditors out there somewhere?
                      • 8. Re: Generate UI based on a bean?
                        You'd think so. I had to do a few. Pretty easy, boolean is a checkbox, all the other primitives and String are text areas, enum is a set of radiobuttons. Any other Object is a panel, choose your layout manager again and away you go.
                        • 9. Re: Generate UI based on a bean?
                          I'm going to spec most of the enums as ComboBoxen. Some of them are large.

                          Handling some of the embedded classes is going to be fun too. There are a few that are larger than the enclosing class. Maybe pop up a modal dialog box? We'll see.
                          • 10. Re: Generate UI based on a bean?
                            I hope you can help because I'm just about out of ideas.

                            I'm using JTable to display and edit the bean properties. The datum in the TableModel is a PropertyEditor with an embedded PropertyDescriptor. Column 1 is a no-brainer - just displayNames. Column 2 is what's driving me nutso. The columnClass is PropertyEditor, but columnClass is meaningless since the value in each cell is a different type - the large majority can use default editors, but I have to handle the others. There is a default renderer for the column that is working well (everything currently rendered as JLabel). But I'm stuck on the TableCellEditor, specifically this method:
                                           //Implement the one method defined by TableCellEditor.
                                public Component getTableCellEditorComponent ( final JTable table,
                                                                             final Object value, final boolean isSelected,
                                                                             final int row, final int column) {
                                    PropertyEditorSupport editor = (PropertyEditorSupport)value;
                                    if( editor.supportsCustomEditor() )
                                        return editor.getCustomEditor(); // none of the Object properties do this yet, but all will
                                    else  {  /* urgh - try to use a default editor */
                                        Class<?> valueType = Autobox.box( ((PropertyDescriptor)editor.getSource())
                                                                            .getPropertyType() );
                                        TableCellEditor tce = table.getDefaultEditor( valueType );
                                        if( tce == null )  {  // it's an Object - need a custom Component
                                            return new JTextField();  // stub - I'll put in a pantomime editing Component real soon now
                                        else  { // 80% of the cells should come here
                                            Component comp = tce.getTableCellEditorComponent( table, editor.getValue(), isSelected, row, column );
                                            return comp;
                            The problem is when I call getTableCellEditorComponent() on a default editor - instead of using the Component implied by its own type (i.e. JTextField for NumberEditor) the damn thing consults getColumnClass and decides that the world has gone mad!

                            So what should I do? Ignore getTableCellEditorComponent() and just toss a JTextField at it? Do all new custom editing components (oh joy!)? Or should I take some other approach entirely?

                            Btw, can one subclass an inner class like JTable.GenericEditor? Delegate? Hmm...
                            • 11. Re: Generate UI based on a bean?
                              I don't know what you mean by 'decides that the world has gone mad'.

                              You've run this code - we haven't.
                              • 12. Re: Generate UI based on a bean?
                                Let's just say it doesn't work.

                                I think the problem is trying to have the same cell editor handle the default editors - that it just can't be done. I'm going to try overriding getCellEditor() and see if I can keep them separate. For the default editors the trick will be in getting the changed value into the PropertyEditor behind the cell.

                                Edit: Much better. The ugly part is trying to tell from within getCellEditor() or getCellRenderer() which cells need special treatment. There's gotta be a better way than this:
                                                public TableCellEditor getCellEditor ( final int row, final int column ) {
                                                    Class<?> type = gModel.getTypeAt( row, column );
                                                    TableCellEditor defawlt = getDefaultEditor( type );
                                                    if( type == String.class || type == Boolean.class
                                                     || defawlt instanceof DefaultCellEditor )
                                                        return defawlt;
                                                    else return myCellEditor;
                                Edited by: DMF. on Aug 24, 2010 12:48 PM
                                • 13. Re: Generate UI based on a bean?
                                  Well, it can be done, and while the JTable implementation is harder, it looks a lot better and once you get it working is probably more easily customized. Here are the key lessons and code snippets.

                                  First, unless you want to expose everything, you must have good BeanInfo classes for all your beans. Mask properties and write methods you don't want manually editted (a surprising number).

                                  Second, extract PropertyDescriptors and PropertyEditors to serve as data for the GUI. Note that if you want to use custom editing components you will have to subclass PropertyEditorSupport.
                                      public static PropertyEditorSupport getPropertyEditor ( final Object bean, final PropertyDescriptor prop )  
                                          final PropertyEditorSupport editor = null;
                                               * See if there's one specified in the descriptor 
                                          Class<?> editorClz = prop.getPropertyEditorClass();
                                          if( editorClz == null )  {
                                                   * Nope. Try to get a default editor (common types have them)
                                              editorClz = prop.getPropertyType();
                                              editor = (PropertyEditorSupport)PropertyEditorManager.findEditor( editorClz );
                                          else  { /* A custom editor was specified in BeanInfo */
                                              try {
                                                  final Object obj = editorClz.newInstance();
                                                  editor = PropertyEditorSupport.class.cast( obj );
                                              } catch( Exception ex ) {  ex.printStackTrace();  }
                                          if( editor == null )  {  // got to have some sort of editor
                                              editor = new UsablePropertyEditor();
                                          editor.setSource( prop ); // save a ref to the property - this is a bit of a kluge
                                          read( bean, editor );  // set the property value into the editor
                                          if( prop.getWriteMethod() != null )  { // it's editable
                                              try {
                                                  final Class<?>[] args = { java.awt.Component.class };
                                                  final Method meth = editor.getClass().getMethod("setCustomEditor", args );
                                                  final Class<?> type = prop.getPropertyType();
                                                  if( type.isEnum() )  {
                                                      final JComboBox box = new JComboBox( type.getEnumConstants() );
                                                      meth.invoke( editor, box );
                                              } catch( Exception ex ) { }  // oh well...
                                          return editor;
                                  In the GUI, there is a JTable with two columns - property names, and values. The values column has cell values of many different types, and JTable really doesn't like that. You also need a special cell renderer and cell editor to handle the types without defaults for them (any Object other than Number and String). Plus you have to keep JTable from calling TableModel.getColumnClass() on column 2 or he gets confused. Override two methods:
                                      JTable getGTable ()  {
                                          if( gTable == null )  {
                                              gModel = new MyTableModel();
                                              gTable = new JTable( gModel )
                                                  private static final long serialVersionUID = 1L;
                                                   * While setting my renderer as the default for non-String objects 
                                                   * works for most fields, it does not work for interfaces.  (why?)
                                                   * Plus we still need to keep <code>super</code> from
                                                   * calling model.getColumnClass() so must keep this override
                                                  public TableCellRenderer getCellRenderer( final int row
                                                                                          , final int column )  {
                                                      final Class<?> type = gModel.getTypeAt( convertRowIndexToModel(row),
                                                                                  convertColumnIndexToModel(column) );
                                                      final TableCellRenderer defawlt = getDefaultRenderer( type );
                                                      if( defawlt != null )  return defawlt;
                                                      else return myCellRenderer;  // type.isInterface()
                                                  public TableCellEditor getCellEditor ( final int row, final int column ) {
                                                      final Class<?> type = gModel.getTypeAt( convertRowIndexToModel(row),
                                                                                  convertColumnIndexToModel(column) );
                                                      final TableCellEditor defawlt = getDefaultEditor( type );
                                                      if( defawlt != null )  return defawlt;
                                                      else return new PropertyTableCellEditor();
                                               * The String default renderer is the same as the Object default renderer 
                                               * so we need to replace it if we replace Object. 
                                              final TableCellRenderer rend = gTable.getDefaultRenderer( String.class );
                                              gTable.setDefaultRenderer( Object.class, myCellRenderer );
                                              gTable.setDefaultRenderer( String.class, rend );
                                               * Can't register Editor like we do with Renderer since the class is stateful.
                                              final TableCellEditor strEd = gTable.getDefaultEditor( String.class );
                                              gTable.setDefaultEditor( Object.class, null );
                                              gTable.setDefaultEditor( String.class, strEd );
                                          return gTable;
                                  • 14. Re: Generate UI based on a bean?
                                    And a few things in MyTableModel:
                                        void setData( final PropertyEditorSupport[] athletic )  {
                                            supporters = athletic;
                                        public Class<?> getColumnClass ( final int column ) {
                                             * KLUGE ALERT!  There are three places this is called from JTable: 
                                             * getCellRenderer(), getCellEditor(), and $GenericClass.getTableCellEditorComponent()
                                             * The first two are overridden in Frame and don't use it, but the 
                                             * last needs to believe that the column is full of Strings.  (IMO a bug!)
                                            return String.class;
                                        public Object getValueAt ( final int rowIndex, final int columnIndex ) {
                                            switch( columnIndex )  {
                                            case 0 :
                                                return getPropertyDes(rowIndex).getDisplayName();
                                            case 1 :
                                                return supporters[rowIndex].getValue();
                                            return null;  // or barf
                                        Class<?> getTypeAt ( final int rowIndex, final int columnIndex ) {
                                            Class<?> clazz = String.class;
                                            if( columnIndex == 1 )  {  
                                                clazz = getPropertyDes( rowIndex ).getPropertyType();
                                                clazz = Autobox.box( clazz );  // no primitives allowed
                                            return clazz;
                                        PropertyEditorSupport getDatumAt ( final int rowIndex, final int columnIndex ) {
                                            return supporters[rowIndex];
                                        public boolean isCellEditable ( final int rowIndex, final int columnIndex ) {
                                            if( ! columns[columnIndex].isEditable() )  return false;
                                            else  {
                                                return getPropertyDes( rowIndex ).getWriteMethod() != null;
                                        PropertyDescriptor getPropertyDes ( final int row )  {
                                            final PropertyEditorSupport supp = this.supporters[row];
                                            if( supp.getSource() instanceof PropertyDescriptor )
                                                return (PropertyDescriptor)supp.getSource();
                                                throw new BadError("<whine>");
                                    Also note that the default JTable editors for Number call setValueAt() with a String, and the PropertyEditors don't do type conversion.