Create swing component based on property
annotations, java, swing
Solution
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. For the value of the `JTextField`, I'm not sure what direction you want to take. You could add `DocumentListener`s that will directly update the related `Field` object or something, but I'm not sure what the requirements are. 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.
I made a runnable example using GroupLayout and PropertyChangeSupport that actually looks okay...
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...
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!
Problem
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. The main program should recognize properties from the component and create for each property a swing component. 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. 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; } ```