我有一个小程序,它能够在运行时加载组件。我想为这些组件编写一个小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;
}
答案 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创建了一个可运行的示例,实际上看起来还不错......
代码有些混乱(特别是我使用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()
方法实现如下:
getValue()
方法以检索值propertyName
和检索到的值然后,您可以将创建的Panel添加到UI中,一旦需要表单的数据,您将获得MyBean类型的bean。
答案 3 :(得分:0)
你绝对可以这样做。加载一个对象。用反射查询其字段。一旦有了字段,就可以遍历其注释。如果找到注释@Property
,则获取其属性类型,并询问一个对象(您还必须创建),以创建swing组件。
我会以不同的方式构建你的类,制作纯数据类(B类),并将它们提供给一些可以创建子组件的面板。