This discussion is archived
14 Replies Latest reply: Aug 24, 2010 3:34 PM by 843807 RSS

Generate UI based on a bean?

843807 Newbie
Currently Being Moderated
[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?
    EJP Guru
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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?
    EJP Guru
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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.

    Edit:
    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?
    EJP Guru
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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?
    EJP Guru
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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?
    EJP Guru
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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?
    843807 Newbie
    Currently Being Moderated
    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
                     */
                    @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()
                    }
    
                    @Override
                    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?
    843807 Newbie
    Currently Being Moderated
    And a few things in MyTableModel:
        void setData( final PropertyEditorSupport[] athletic )  {
            supporters = athletic;
            fireTableDataChanged();
        }
    
        @Override
        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;
        }
    
        @Override
        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];
        }
    
        @Override
        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();
            else
                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.

    Enjoy!