根据属性创建swing组件

时间:2013-12-08 16:16:36

标签: java swing annotations

我有一个小程序,它能够在运行时加载组件。我想为这些组件编写一个小API。主程序应该识别组件中的属性,并为每个属性创建一个swing组件。我的想法是使用注释,例如:

@Property(name = "X",  PropertyType.Textfield, description = "Value for X")
private int x;

你怎么看?有什么问题吗?是否有类似的实施或其他选择?感谢您的所有建议和提示!

更新 请注意,我无法使用第三方库。

更新 我想创建一个抽象类,它能够根据Conceet类的属性创建swing组件。抽象类控制演示文稿。例如(伪代码):

public class A {
    /**
     * Create swing component based on the concret class
     * In this example class A should create a jTextField with a jLable "Cities". So I have not to create each component manuel, 
     * If the text field changed the attribute for cities should set (My idea was with propertyChangesSupport).
     */
}

public class B extends A {
    @Property(name = "Cities",  PropertyType.Textfield, description = "Number of cities")
    private int cities;
}

4 个答案:

答案 0 :(得分:3)

就实际将注释添加到布局中而言,您可以执行以下操作:

public class A extends JPanel {

A() {
    this.setLayout(new FlowLayout());
    addProperties();
}

private void addProperties() {
    for (Field field : this.getClass().getDeclaredFields()) {
        Property prop = field.getAnnotation(Property.class);
        if (prop != null) {
            createAndAddComponents(prop);
        }
    }
}

private void createAndAddComponents(Property prop) {
    JLabel jLabel = new JLabel(prop.name());
    this.add(jLabel);
    switch (prop.type()) {
    case Textfield:
        JTextField jTextField = new JTextField();
        this.add(jTextField);
    case ...
        ...
    }
}
}

当然,这看起来不会很花哨,因为我使用FlowLayout来避免示例中的混乱。对于JTextField的值,我不确定你想采取什么方向。您可以添加将直接更新相关DocumentListener对象的Field,但我不确定要求是什么。这是一个想法:

jTextField.getDocument().addDocumentListener(new DocumentListener() {

@Override
public void insertUpdate(DocumentEvent e) {
    try {
        int value = Integer.parseInt(jTextField.getText());
        field.setInt(A.this, value);
    } catch (NumberFormatException e1) {
    } catch (IllegalArgumentException e1) {
    } catch (IllegalAccessException e1) {
    }
}

......
});

当你设置值时,你也可以在那里做一些PropertyChangeSupport。

我使用GroupLayout和PropertyChangeSupport创建了一个可运行的示例,实际上看起来还不错...... Example Run

代码有些混乱(特别是我使用GroupLayout,对不起),但是这里是为了你需要看一些东西......我希望我不会让它太混乱......

public class B extends A {
    @Property(name = "Cities",  type = PropertyType.Textfield, description = "Number of cities")
    private int cities;
    @Property(name = "Cool Cities",  type = PropertyType.Textfield, description = "Number of cool cities")
    private int coolCities;

    public static void main(String[] args) {
        final B b = new B();
        b.addPropertyChangeListener("cities", new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("Cities: " + b.cities);
            }

        });
        b.addPropertyChangeListener("coolCities", new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("Cool Cities: " + b.coolCities);
            }

        });

        JFrame frame = new JFrame();
        frame.add(b);
        frame.setVisible(true);
    }
}


public class A extends JPanel {

    //Need this retention policy, otherwise the annotations aren't available at runtime:
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Property {
        String name();
        PropertyType type();
        String description();
    }

    public enum PropertyType {
        Textfield
    }


    A() {
        addProperties();
    }

    private void addProperties() {
        GroupLayout layout = new GroupLayout(this);
        this.setLayout(layout);
        layout.setAutoCreateContainerGaps(true);
        layout.setAutoCreateGaps(true);
        Group column1 = layout.createParallelGroup();
        Group column2 = layout.createParallelGroup();
        Group vertical = layout.createSequentialGroup();

        for (Field field : this.getClass().getDeclaredFields()) {
            field.setAccessible(true); // only needed for setting the value.
            Property prop = field.getAnnotation(Property.class);
            if (prop != null) {
                Group row = layout.createParallelGroup();

                createAndAddComponents( prop, column1, column2, row, field );
                vertical.addGroup(row);
            }
        }
        Group horizontal = layout.createSequentialGroup();
        horizontal.addGroup(column1);
        horizontal.addGroup(column2);
        layout.setHorizontalGroup(horizontal);
        layout.setVerticalGroup(vertical);
    }

    private void createAndAddComponents(Property prop, Group column1, Group column2, Group vertical, final Field field) {
        JLabel jLabel = new JLabel(prop.name() + " (" + prop.description() + ")");
        column1.addComponent(jLabel);
        vertical.addComponent(jLabel);
        switch (prop.type()) {
        case Textfield:
            final JTextField jTextField = new JTextField();
            jTextField.getDocument().addDocumentListener(new DocumentListener() {

                @Override
                public void insertUpdate(DocumentEvent e) {
                    updateValue();
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    updateValue();
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    updateValue();
                }

                private void updateValue() {
                    try {
                        int value = Integer.parseInt(jTextField.getText());
                        field.setInt(A.this, value);
                        firePropertyChange(field.getName(), "figure out old", value);
                    } catch (NumberFormatException e1) {
                    } catch (IllegalArgumentException e1) {
                    } catch (IllegalAccessException e1) {
                    }
                }

            });
            column2.addComponent(jTextField);
            vertical.addComponent(jTextField, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE);
        }
    }
}

希望这有帮助!

答案 1 :(得分:1)

在我看来(我知道我不应该写出意见但只有关于stackoverflow答案的事实,但这里的问题是“你怎么看待它?”...... :-))在这个解决方案中你混合模型并在B类中查看信息(特别是在注释中具有PropertyType.Textfield)。我知道MVC模式不是一个法则(甚至Android适配器打破MVC合同,无论如何都是好的:-)),但它通常被认为是良好的做法。

如果我找到了你,那么你有A级为B级提供视图(摆动组件)。所以A是视图而B是模型。出于这个原因,我不会让B扩展A,因为它打破了“IS A”关系(B扩展A表示“B是A”,在你的情况下,这意味着B是视图,而不是)。 / p>

作为一种不同的选择,您是否考虑调查基于JavaBeans和PropertyEditors的方法?它应该更符合摇摆中“运行时加载”组件的常规管理。 见http://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html

我写了一个简单的例子,只是为了告诉你我的意思:

这是我的B班:

import java.awt.Color;

public class B {
   private int x = 0;
   private Color c = Color.BLUE;
   private Address address;

   public B() {
      address = new Address();
      address.setCity("Liverpool");
      address.setState("England");
      address.setStreet("Penny Lane");
      address.setNumber("1");
      address.setZip("12345");
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x;
   }

   public Color getC() {
      return c;
   }

   public void setC(Color c) {
      this.c = c;
   }

   public Address getAddress() {
      return address;
   }

   public void setAddress(Address address) {
      this.address = address;
   }
}

这是我的测试GUI:

import java.awt.GridLayout;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;

public class Test extends JFrame {

   /**
    * 
    */
   private static final long serialVersionUID = 1L;

   public Test(Object b) {
      super("Test");
      initGUI(b);
   }

   private void initGUI(Object b) {
      Class<?> c = b.getClass();
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLayout(new GridLayout(0, 2));

      try {
         BeanInfo info = Introspector.getBeanInfo(c);
         for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
            try {
               Method readMethod = pd.getReadMethod();
               Object val = readMethod.invoke(b, new Object[0]);
               // Or you may want to access the field directly... but it has to be not-private
               // Field f = c.getDeclaredField(pd.getName());
               // Object val = f.get(b);
               //
               // You can use annotations to filter... for instance just consider annotated
               // fields/methods and discard the others...
               // if (f.isAnnotationPresent(Property.class)) {
               // ...

               java.beans.PropertyEditor editor = java.beans.PropertyEditorManager.findEditor(val.getClass());
               if (editor != null) {
                  editor.setValue(val);
                  add(new JLabel(pd.getDisplayName()));
                  if (editor.supportsCustomEditor()) {
                     add(editor.getCustomEditor());
                  } else {
                     String[] tags = editor.getTags();
                     if (tags != null) {
                        add(new JComboBox<String>(tags));
                     } else {
                        if (editor.getAsText() != null) {
                           add(new JTextField(editor.getAsText()));
                        }else{
                           add(new JPanel());
                        }
                     }
                  }
               }

            } catch (SecurityException e1) {
               // TODO Auto-generated catch block
               e1.printStackTrace();
            } catch (IllegalArgumentException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
            } catch (IllegalAccessException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
            } catch (InvocationTargetException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
            }
         }
      } catch (IntrospectionException ex) {
         ex.printStackTrace();
      }

   }

   public static void main(String[] args) {
      B b = new B();

      Test t = new Test(b);
      t.pack();
      t.setVisible(true);

   }

}

PropertyEditorManager为int和for Color提供默认编辑器。我的班级地址怎么样?

public class Address {
   private String city;
   private String state;
   private String zip;
   private String street;
   private String number;

   public String getCity() {
      return city;
   }

   public void setCity(String city) {
      this.city = city;
   }

   public String getState() {
      return state;
   }

   public void setState(String state) {
      this.state = state;
   }

   public String getZip() {
      return zip;
   }

   public void setZip(String zip) {
      this.zip = zip;
   }

   public String getStreet() {
      return street;
   }

   public void setStreet(String street) {
      this.street = street;
   }

   public String getNumber() {
      return number;
   }

   public void setNumber(String number) {
      this.number = number;
   }
}

我为类Address提供了一个自定义编辑器(PropertyEditorManager使用不同的策略来发现类编辑器......我使用了命名约定) 这是我的AddressEditor类:

import java.awt.Component;
import java.awt.FlowLayout;
import java.beans.PropertyEditorSupport;

import javax.swing.JPanel;
import javax.swing.JTextField;

public class AddressEditor extends PropertyEditorSupport {
   @Override
   public boolean supportsCustomEditor() {
      return true;
   }

   @Override
   public Component getCustomEditor() {
      Address value = (Address) getValue();
      JPanel panel = new JPanel();
      panel.setLayout(new FlowLayout());

      panel.add(new JTextField(value.getStreet()));
      panel.add(new JTextField(value.getNumber()));
      panel.add(new JTextField(value.getCity()));
      panel.add(new JTextField(value.getState()));
      panel.add(new JTextField(value.getZip()));

      return panel;
   }
}

这样B(以及地址)只是模型和编辑器,用于自定义视图。

嗯......这只是为了回答你关于其他选项的问题...我希望我没有走得太远......: - )

答案 2 :(得分:1)

在我看来,最简洁的方法是创建如下内容。

使用带注释的字段创建一个简单的数据bean

public class MyBean {

    @Property(name="Cities", PropertyType.Textfield, description = "Number of cities")
    private int cities;

    @Property(name="Countries", PropertyType.Textfield, description = "Number of countries")
    private int countries;

}

一个名为 FormFactory 的类,它提供了方法:

public <T> Form<T> createForm(T o);

此方法通过反映给定对象的字段并为每个字段创建 PropertyComponent 来创建 Form 类型的Component。只需使用GridBagLayout将组件填充到表单中(如果您对此有其他疑问,请询问;)。

泛型 Form 类扩展了JPanel类型并提供了方法:

public T getBean();

通用 PropertyComponent 的结构如下:

public abstract PropertyComponent<T> extends JComponent {
    String propertyName;
    abstract T getValue();
}

public TextfieldStringComponent extends PropertyComponent<String> {
    ...
}

Form 类的T getBean()方法实现如下:

  • 创建一个类型为T
  • 的新空bean
  • 遍历每个 PropertyComponent
  • 调用字段组件的getValue()方法以检索值
  • 使用propertyName和检索到的值
  • 将值填入bean中

然后,您可以将创建的Panel添加到UI中,一旦需要表单的数据,您将获得MyBean类型的bean。

答案 3 :(得分:0)

你绝对可以这样做。加载一个对象。用反射查询其字段。一旦有了字段,就可以遍历其注释。如果找到注释@Property,则获取其属性类型,并询问一个对象(您还必须创建),以创建swing组件。

我会以不同的方式构建你的类,制作纯数据类(B类),并将它们提供给一些可以创建子组件的面板。