简体   繁体   English

根据属性创建摆动组件

[英]Create swing component based on property

I have a small program, which is able to load components during the runtime. 我有一个小程序,可以在运行时加载组件。 I would like to write a small API for those components. 我想为这些组件编写一个小型API。 The main program should recognize properties from the component and create for each property a swing component. 主程序应识别组件的属性,并为每个属性创建一个swing组件。 My idea was to use annotations, something like: 我的想法是使用注释,例如:

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

What do you think about it? 你怎么看待这件事? Any problems with that? 有什么问题吗? Are there similar implementations or other options? 是否有类似的实现或其他选择? Thanks for all advices and hints! 感谢您的所有建议和提示!

Update Please, notice that I'm not able to use third party libraries. 更新请注意,我无法使用第三方库。

Update I would like to create an abstract class, which is able to create swing components based on the attributes from the concert class. 更新我想创建一个抽象类,该抽象类能够根据Concert类的属性创建摇摆组件。 The abstract class controls the presentation. 抽象类控制表示。 For example (pseudo code): 例如(伪代码):

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;
}

In terms of actually getting the annotations into the layout, you could do something like this: 就将注释实际放入布局而言,您可以执行以下操作:

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 ...
        ...
    }
}
}

Of course, that won't look fancy because I'm using FlowLayout to avoid clutter in the example. 当然,这看起来并不理想,因为我使用FlowLayout来避免示例中的混乱。 For the value of the JTextField , I'm not sure what direction you want to take. 对于JTextField的值,我不确定您要朝哪个方向前进。 You could add DocumentListener s that will directly update the related Field object or something, but I'm not sure what the requirements are. 您可以添加DocumentListener ,它们将直接更新相关的Field对象或其他对象,但是我不确定要求是什么。 Here's one idea: 这是一个主意:

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) {
    }
}

......
});

And you could also do some PropertyChangeSupport in there when you set the value. 设置该值时,还可以在其中进行一些PropertyChangeSupport。

I made a runnable example using GroupLayout and PropertyChangeSupport that actually looks okay... 我使用GroupLayout和PropertyChangeSupport制作了一个可运行的示例,实际上看起来还不错... 示例运行

The code is somewhat messy (particularly my use of GroupLayout, sorry), but here it is in case you need something to look at.. I hope I'm not making this too cluttered... 代码有些混乱(尤其是我对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);
        }
    }
}

Hope this helps! 希望这可以帮助!

In my opinion (I know I shouldn't write opinions but only facts on stackoverflow answers, but here the question is "what do you think about it?"... :-) ) in this solution you are mixing Model and View information in class B (particularly having that PropertyType.Textfield in your annotation). 以我的观点(我知道我不该写观点,而应该只写关于stackoverflow答案的事实,但是这里的问题是“您对此有何看法?” ... :-))在类B中(特别是在注释中具有那个PropertyType.Textfield)。 I know MVC pattern is not a law (even Android Adapter breaks MVC contract and it's ok anyway :-) ), but it is generally considered good practice. 我知道MVC模式不是法律(即使Android Adapter违反了MVC合同,也可以:-)),但是通常认为这是一种好习惯。

If I'm getting you right you have class A that provides the view (swing components) for class B. So A is a view and B is a model. 如果我说对了,那么您有A类,它提供B类的视图(摆动组件)。所以A是视图,B是模型。 For this reason I wouldn't have B to extend A because it breaks the "IS A" relationship (B extends A means "B is a A" and in your case this would mean B is a view while it is not). 因此,我不会让B扩展A,因为它破坏了“ IS A”关系(B扩展A意味着“ B是A”,而在您的情况下,这意味着B是一个视图而没有)。

As a different option, did you consider investigating an approach based on JavaBeans and PropertyEditors? 作为另一个选择,您是否考虑研究基于JavaBeans和PropertyEditor的方法? it should be more in line with the regular management of "runtime-loaded" components in swing. 它应该更符合“运行时加载”组件的常规管理。 See http://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html 参见http://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html

I wrote a simple example just to show you what I mean: 我写了一个简单的例子只是为了向您展示我的意思:

Here is my class B: 这是我的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;
   }
}

And this is my test GUI: 这是我的测试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 provides a default editor for int and for Color. PropertyEditorManager为int和Color提供了默认编辑器。 What about my class Address? 那我的班级地址呢?

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;
   }
}

I provide a custom editor for class Address (PropertyEditorManager uses different strategies to discover class editors... I used naming convention) This is my AddressEditor class: 我为地址类提供了一个自定义编辑器(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;
   }
}

This way B (and also Address) are just models and editors are provided to customize the views. 这样,B(以及Address)仅是模型,并提供了编辑器以自定义视图。

Well... this was just to respond to your question about other options... I hope I didn't go too much out of track... :-) 好吧...这只是为了回答您有关其他选项的问题...我希望我不会走得太远... :-)

The cleanest way in my opinion would be to create something like the following. 我认为最干净的方法是创建类似以下内容的东西。

Create a plain data bean with annotated fields: 创建带有注释字段的纯数据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;

}

A class called FormFactory which provides the method: 一个名为FormFactory的类,它提供以下方法:

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

This method creates a Component of the type Form by reflecting the fields of the given object and creating a PropertyComponent for each field. 此方法通过反映给定对象的字段并为每个字段创建一个PropertyComponent来创建类型为Form的Component。 Simply fill the components into the form by using a GridBagLayout (if you have further questions on this, ask ;). 只需使用GridBagLayout将组件填充到表单中(如果对此有其他疑问,请询问;)。

The generic Form class extends the type JPanel and provides the method: 通用Form类扩展了JPanel类型并提供了以下方法:

public T getBean();

A generic PropertyComponent is structured like this: 通用PropertyComponent的结构如下:

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

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

The T getBean() method of the Form class is implemented as follows: Form类的T getBean()方法实现如下:

  • Create a new empty bean of the type T 创建一个新的T类型的空bean
  • Iterate over each PropertyComponent 遍历每个PropertyComponent
  • Call the getValue() method of the field component to retrieve the value 调用字段组件的getValue()方法以检索值
  • Fill the value into the bean with the propertyName and the retrieved value propertyName和检索到的值将值填充到Bean中

Then you can add the created Panel to your UI, and once the data of the form is needed, you get a bean of the type MyBean. 然后,您可以将创建的面板添加到您的UI,一旦需要表单的数据,您将获得MyBean类型的bean。

you can absolutely do this. 您绝对可以做到这一点。 load an object. 加载对象。 query its fields with reflection. 用反射查询其字段。 once you have a field, you can iterate through its annotations. 一旦有了一个字段,就可以遍历其注释。 if you find the annotation @Property , then get its property type, and ask an object (which you will also have to create), to create the swing component. 如果找到注释@Property ,则获取其属性类型,并请求一个对象(也将要创建)来创建swing组件。

i would structure your classes differently though, make pure data classes (class B's), and feed those into some panel that can create the sub components. 不过,我会以不同的方式构造您的类,制作纯数据类(B类),并将其输入可以创建子组件的面板中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM