📜 ⬆️ ⬇️

Library of reflective generation of Swing forms

Some time ago I had the idea to create a library for fast generation of forms in Java Swing. I'll tell you how I came to this.

Formulation of the problem


Surely, many people have to periodically implement java-bean editing using a Swing component. For example, various reference books. JDK is conservative, and does not provide something “out of the box” that would greatly facilitate the life of a developer. For each case, you need to write something like:
GridBagPanel panel = new GridBagPanel(){{ add( new JLabel( "Name" ), 0, 0 ); add( new JTextField( person.getName() ) {{ getDocument().addDocumentListener( new DocumentListener() { public void insertUpdate( final DocumentEvent e ) { person.setName( getText() ); } public void removeUpdate( final DocumentEvent e ) { person.setName( getText() ); } public void changedUpdate( final DocumentEvent e ) { person.setName( getText() ); } } ); }}, 0, 1 ); //      ... }}; 

There is already a lot of code here. But you may still need to add validation to the handler.

There are different approaches how to make your life easier.

Driven by the thirst for the invention of bicycles, I decided to write a minimalist form generation library that would allow me to do:

I will describe how the code using this library should look


Default form


Everything is simple: we give a class, we get a form.
 Form<Person> form = FormBuilder.map( Person.class ).buildForm(); myFrame.add( form.asComponent() ); Person person = new Person(); person.setName( "john smith" ); // ... further initialization form.setValue( person ); 

Setting the location of components


Probably, in 80% of cases, each field requires only 2 components: a label with the name and the editor itself. The classic approach is: ask the name of the field, we get the component.
 Form<Person> form = FormBuilder.map( Person.class ).with( new PropertyNameBeanMapper<Person>() {      @Override      public JComponent mapBean( PropertyNameContext<Person> ctx ) {        JPanel panel = new JPanel( new BorderLayout() );        panel.add( ctx.label( "name" ), BorderLayout.NORTH );        panel.add( ctx.editor( "name" ), BorderLayout.CENTER );        return panel;      } } ).buildForm(); 

Is it possible to replace strings with something more reliable? For example:
 Form<Person> form = FormBuilder.map( Person.class ).with( new SampleBeanMapper<Person>() {      @Override      protected JComponent mapBean( Person sample, SampleContext<Person> ctx ) {        Box box = Box.createHorizontalBox();        box.add( ctx.label( sample.getName() ) );        box.add( ctx.editor( sample.getName() ) );        return box;      } } ).buildForm(); 

If you submit a dynamic proxy as a sample ( an example of using the CGLIB library ), which would tell the context which methods are called, then this code will become fully functional. The approach is not thread safe, but this is a GUI.
')

Mapping


With ctx.label () everything is clear - it should return a JLabel. And what should ctx.editor () return? Within the context, there must be a certain mapping, which the editor will select for him for each field according to its type. But pick up a little. And if this is a custom component? There are several things that need to be configured.

For example, for a third-party calendar component, this configuration would look like this:
 class DateToDateChooserMapper implements TypeMapper<JDateChooser, Date> { public Class<Date> getValueClass() { return Date.class; } public JDateChooser createEditorComponent() { return new JDateChooser(); } public Date getValue( final JDateChooser editorComponent ) { return editorComponent.getDate(); } public void setValue(final JDateChooser editorComponent, final Date value) { editorComponent.setDate( value ); } public void handleChanges(final JDateChooser editorComponent, final ChangeHandler changeHandler) { editorComponent.getDateEditor() .addPropertyChangeListener( "date", new PropertyChangeListener() { public void propertyChange( final PropertyChangeEvent evt ) { changeHandler.onChange( BackgroundMarker.INSTANCE ); } } ); } } 


Here, changeHandler serves to let the system know about changes in the component. By default, the bean is validated using the Hibernate Validator. Its results are notified by the ValidationMarker, in this case, the BackgroundMarker, which decides how to change the appearance of the validated components.

If you need to set the mapping for all fields of this type, the code looks simple:
 Form<Person> form = FormBuilder.map( Person.class ).use( new StringToTextAreaMapper() ).buildForm(); 

For specific fields, it is somewhat more complicated:
 Form<Person> form = FormBuilder.map( Person.class ) .useForProperty( "description", new StringToTextAreaMapper() ) .buildForm(); 

Again, is it possible to use dynamic proxies, and bind to methods instead of strings, providing type checking? It is possible, although somewhat cumbersome:
 Form<Person> form = FormBuilder.map( Person.class ).useForGetters( new GetterMapper<Person>() {      @Override      public void mapGetters( Person beanSample, GetterConfig config ) {        config.use( beanSample.getDescription(), new StringToTextAreaMapper() );      } } ).buildForm(); 


Conclusion


I wrote such a library some time ago, and now I decided to submit it to the community court. Now I am making an adapter of this library for Scala, but for now it turns out to be rather trivial. Perhaps I will describe this in the next post.

A wiki with a more detailed description of the library and the mavenovsky repository is located on Google Code: code.google.com/p/swing-formbuilder
I transferred source codes to GitHub: github.com/aeremenok/swing-formbuilder

Source: https://habr.com/ru/post/126579/


All Articles