简单的强大 TableModel(带反射)





3.00/5 (3投票s)
TableModel 复杂性不再是问题。
源代码更新日期: 2009年5月28日
本文的葡萄牙语版本,请访问 此链接。
章节目录
1. 动机
“不要使用 DefaultTableModel”,我经常遇到一些人在使用 DefaultTableModel 实现时遇到问题,我的建议是,不要使用它。但是实现一个能让所有工作都变得简单的类,也并非易事。所以我实现了一个,现在在这里与大家分享。
我的目标是编写一个单一的 TableModel,简单、可扩展、易读且功能强大。这一切都通过反射和注解来实现。
有了这个模型,您可以
- 添加并获取每一行的当前对象
- 无需处理 String数组
- 在单元格更新时保持对象同步更新
- 通过注解进行配置,简化代码的可读性
- List接口的方法,如:- add、- addAll、- remove和- indexOf
- 如果您不喜欢注解,仍然可以使用它(参见第 2.2.1 章)
2. 开始编码
2.1 基础
2.1.1 简介
首先:下载包含项目源代码的 objecttablemodel.zip 压缩包。
这个项目中值得关注的类有
- ObjectTableModel,这是实现的表格模型
- FieldResolver,后台工作在这里进行,访问表格列的字段
- @Resolvable,标记字段的注解,用于表格的默认值。如格式化器(如果需要)、- Column名称以及实际访问字段的- FieldAccessHandler。
- 实现的 FieldAccessHandlers有FieldHandler(默认),它直接使用类中的Field;以及MethodHandler,它使用类中的get(或 is)/set方法。
- AnnotationResolver类只包含创建- FieldResolvers的通用方法。
这是创建类 JTable 所需的唯一代码。
首先:类,这里以 Person 为例。
import mark.utils.el.annotation.Resolvable;
public class Person {
    @Resolvable(colName = "Name")
    private String name;
    @Resolvable(colName = "Age", formatter = IntFormatter.class)
    private int age;
    private Person parent;
    public Person(String name, int age) {
        this(name, age, null);
    }
    public Person(String name, int age, Person parent) {
        this.name = name;
        this.age = age;
        this.parent = parent;
    }
    //Getters and setters omitted 
} 
创建表格所需的代码就是这些
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import mark.utils.el.annotation.AnnotationResolver;
import mark.utils.swing.table.ObjectTableModel;
import test.Person;
public class ObjectTableModelDemo {
    public void show() {
	//Here we create the resolver for annotated classes.
        AnnotationResolver resolver = new AnnotationResolver(Person.class);
	//We use the resolver as parameter to the ObjectTableModel
	//and the String represent the cols.
        ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
                resolver, "name,age");
	//Here we use the list to be the data of the table.
        tableModel.setData(getData());
        JTable table = new JTable(tableModel);
        JFrame frame = new JFrame("ObjectTableModel");
        JScrollPane pane = new JScrollPane();
        pane.setViewportView(table);
        pane.setPreferredSize(new Dimension(400,200));
        frame.add(pane);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    //Just for create a default List to show.
    private List<Person> getData() {
        List<Person> list = new ArrayList<Person>();
        list.add(new Person("Marky", 17, new Person("Marcos", 40)));
        list.add(new Person("Jhonny", 21));
        list.add(new Person("Douglas", 50, new Person("Adams", 20)));
        return list;
    }
    public static void main(String[] args) {
        new ObjectTableModelDemo().show();
    }
} 
ObjectTableModel 类的第二个参数可以更强大。
您不限于该类的属性。您可以使用其中字段的属性。
        AnnotationResolver resolver = new AnnotationResolver(Person.class);
        ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
                resolver, "name,age,parent.name,parent.age"); 
如果您使用“parent.name”,您将看到此 Person 的父级的名称。
您也可以指定列名。只需在字段名后加上冒号(:),然后写上列名。
  AnnotationResolver resolver = new AnnotationResolver(Person.class);
  ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
   resolver, "name:Person Name,age:Person Age,parent.name:Parent Name,parent.age:Parent Age"); 
以下所有文本,请勿写入 objecttablemodel_demo.zip 压缩包中。
2.1.2 自定义格式化器
在大多数情况下,仅 String 就足以提供正确的可视化。
但是如果我们想在表格中放置一个 Calendar 字段呢?
java.util.GregorianCalendar[time=-367016400000,areFieldsSet=true,areAllFieldsSet=
true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Sao_Paulo",offset=
-10800000,dstSavings=3600000,useDaylight=true,transitions=129,lastRule=java.util.
SimpleTimeZone[id=America/Sao_Paulo,offset=-10800000,dstSavings=3600000,useDaylight=
true,startYear=0,startMode=3,startMonth=9,startDay=15,startDayOfWeek=1,startTime=0,
startTimeMode=0,endMode=3,endMonth=1,endDay=15,endDayOfWeek=1,endTime=0,endTimeMode=
0]],firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=1,YEAR=1958,MONTH=4,WEEK_OF_YEAR=
20,WEEK_OF_MONTH=3,DAY_OF_MONTH=16,DAY_OF_YEAR=136,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH
=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=
-10800000,DST_OFFSET=0] 
这不适合显示。
为此,我们创建一个 mark.utils.bean.Formatter 的新实例。
它的方法签名是
package mark.utils.bean;
/**
 *@author Marcos Vasconcelos
 */
public interface Formatter {
	/**
	 * Convert a object to String.
	 */
	public abstract String format(Object obj);
	/**
	 * Convert the String to the Object.
	 */
	public abstract Object parse(String s);
	/**
	 * Naming proposes only
	 */
	public abstract String getName();
}
我们可以在 @Resolvable 注解中设置格式化器,这是我的日历实现。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import mark.utils.bean.Formatter;
public class CalendarFormatter implements Formatter {
    private final static SimpleDateFormat formatter = new SimpleDateFormat(
            "dd/MM/yyyy");
    @Override
    public String format(Object obj) {
        Calendar cal = (Calendar) obj;
        return formatter.format(cal.getTime());
    }
    @Override
    public String getName() {
        return "calendar";
    }
    @Override
    public Object parse(String s) {
        Calendar cal = new GregorianCalendar();
        try {
            cal.setTime(formatter.parse(s));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return cal;
    }
} 
回到 Person 类,我们现在可以创建另一个字段并将其放入我们的 Table 中。
@Resolvable(formatter = CalendarFormatter.class)
private Calendar birth;
对于我们的表格,我们可以使用 String: "name,age,birth"。
第三列将显示类似“26/06/1991”的值。
比标准的 Calendar.toString() 更适合显示。
2.1.3 List 接口的方法
我在模型中添加了 List 接口的方法,这样就可以像处理列表一样轻松地添加和删除对象。
这里有一个使用这些方法的示例。
	ObjectTableModel<Person> model = new ObjectTableModel<Person>(
		new AnnotationResolver(Person.class).resolve("name,age"));
	Person person1 = new Person("Marky", 17);
	Person person2 = new Person("MarkyAmeba", 18);
	model.add(person1);
	model.add(person2);
	List<Person> list = new ArrayList<Person>();
	list.add(new Person("Marcos", 40));
	list.add(new Person("Rita", 40));
	model.addAll(list);
	int index = model.indexOf(person2);// Should return 2
	model.remove(index);
	model.remove(person1);
	model.clean(); 
2.1.4 更新和从模型中获取对象
当然。表格不仅仅用于显示数据。在模型中,有一个名为 setEditDefault 的方法,而 isEditable(int x, int y) 方法返回此值。(这意味着如果设置为 true,则整个表格都可以编辑。如果设置为 false,则整个表格都不可编辑)。
如果设置为 true,您可以编辑单元格。焦点丢失后,表格会在 Model 中调用 setValueAt,并将其设置到相应的对象中。
值以 String 的形式传递,FieldResolver 使用其 Formatter 实例将值转换为对象以进行设置。这意味着您不限于处理 String,而是可以处理任何 Object。实现正确的 Formatter 即可实现这一点。
这是一个关于它是如何工作的示例。
首先。我们的模型和格式化器。
import mark.utils.el.annotation.Resolvable;
import mark.utils.el.handler.MethodHandler;
public class Person {
	@Resolvable(colName = "Name")
	private String name;
	@Resolvable(colName = "Age", formatter = IntFormatter.class)
	private int age;
	private Person parent;
	public Person(String name, int age, Person parent) {
		this.name = name;
		this.age = age;
		this.parent = parent;
	}
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
public static class IntFormatter implements Formatter {
	@Override
	public String format(Object obj) {
		return Integer.toString((Integer) obj);
	}
	@Override
	public String getName() {
		return "int";
	}
	@Override
	public Object parse(String s) {
		return Integer.parseInt(s);
	}
}
}
这是一个例子。
package test.el.annotation;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import mark.utils.el.annotation.AnnotationResolver;
import mark.utils.swing.table.ObjectTableModel;
import org.junit.Test;
import test.Person;
public class AnnotationResolverTest {
	@Test
	public void testAnnotationResolverInit() {
		AnnotationResolver resolver = new AnnotationResolver(Person.class);
		ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
				resolver,
				"name,age,parent.name:Parent,parent.age:Parent age");
		tableModel.setData(getData());
		tableModel.setEditableDefault(true);
		JTable table = new JTable(tableModel);
		JFrame frame = new JFrame("ObjectTableModel");
		JScrollPane pane = new JScrollPane();
		pane.setViewportView(table);
		pane.setPreferredSize(new Dimension(400, 200));
		frame.add(pane);
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	private List<Person> getData() {
		List<Person> list = new ArrayList<Person>();
		list.add(new Person("Marky", 17, new Person("Marcos", 40)));
		list.add(new Person("Jhonny", 21, new Person("",0)));
		list.add(new Person("Douglas", 50, new Person("Adams",20)));
		return list;
	}
	public static void main(String[] args) {
		new AnnotationResolverTest().testAnnotationResolverInit();
	}
}
单元格中的任何更改都会更新对象。
获取行的对象
处理 JTables 最糟糕的部分是获取其值。几乎所有时候,我们需要使用 getValutAt 来获取值,并将其设置为对象中的相应值。但本项目旨在实现这一点。
ObjectTableModel 的 getValue(int row) 方法返回指定行的 Object。
ObjectTableModel 是类型化的,并且 getValue 方法返回该类型的一个对象,避免了类型转换。
以下代码返回第二行的 Person。
        AnnotationResolver resolver = new AnnotationResolver(Person.class);
		ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
				resolver,
				"name,age,parent.name:Parent,parent.age:Parent age");
		tableModel.setData(getData());
		tableModel.setEditableDefault(true);
		Person person = tableModel.getValue(2);//The row
		System.out.println(person.getName()); 
2.2 高级
2.2.1 FieldResolver
这个项目的所有后台都在这个类中。而 @Resolvable 和 AnnotationResolver 只是为 ObjectTableModel 创建 FieldResolver 实例。
但您仍然可以代替注解使用它。
以下代码
		FieldResolver nameResolver = new FieldResolver(Person.class, "name");
		FieldResolver ageResolver = new FieldResolver(Person.class, "age");
		ageResolver.setFormatter(new IntFormatter());
		FieldResolver parentNameResolver = new FieldResolver(Person.class,
				"paren.name", "Parent");
		FieldResolver parentAgeResolver = new FieldResolver(Person.class,
				"parent.age", "Parent age");
		FieldResolver birthResolver = new FieldResolver(Person.class, "birth",
				"Birth day");
		birthResolver.setFormatter(new CalendarFormatter());
		ObjectTableModel<Person> model = new ObjectTableModel<Person>(
				new FieldResolver[] { nameResolver, ageResolver,
				parentNameResolver, parentAgeResolver, birthResolver });
这等同于以下代码
		AnnotationResolver resolver = new AnnotationResolver(Person.class);
		ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
				resolver,
		"name,age,parent.name:Parent,parent.age:Parent age,birth: Birth day");
		tableModel.setData(getData());
但是在第一种情况下,我们不需要 Person 类字段中的 @Resolvable 注解。
Field Resolver 工厂
FieldResolverFactory 仅为提高代码可读性而存在,使其易于创建新的 FieldResolvers。它的构造函数需要一个 Class<?> 对象,该对象代表我们正在为其创建字段解析器的类(与在 FieldResolver 构造函数中传递的相同)。
主要方法是
- createResolver(String fieldName)
- createResolver(String fieldName, String colName)
- createResolver(String fieldName, Formatter formatter)
- createResolver(String fieldName, String colName, Formatter formatter)
使用工厂的第一个 FieldResolver 示例应该是
		FieldResolverFactory fac = new FieldResolverFactory(Person.class);
		FieldResolver nameRslvr = fac.createResolver("name");
		FieldResolver ageRslvr = fac.createResolver("age", new IntFormatter());
		FieldResolver parentNameRslvr = fac.createResolver("paren.name",
				"Parent");
		FieldResolver parentAgeRslvr = fac.createResolver("parent.age",
				"Parent age", new IntFormatter());
		FieldResolver birthRslvr = fac.createResolver("birth", "Birth day",
				new CalendarFormatter());
		ObjectTableModel<Person> model = new ObjectTableModel<Person>(
				new FieldResolver[] { nameRslvr, ageRslvr, parentNameRslvr,
						parentAgeRslvr, birthRslvr });
2.2.2 FieldHandler 和 MethodHandler
到目前为止,我们使用的是默认的 FieldAccessHandler,即 FieldHandler。
使用它,我们不需要类中的任何 getter/setter 方法。所有内容都直接通过反射访问。
使用 MethodHandler 时,类会在给定的类中搜索 getter 或 is/setter 方法。
一个简单的例子。
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import mark.utils.el.annotation.Resolvable;
import mark.utils.el.handler.MethodHandler;
public class Person {
	@Resolvable(colName = "Name", accessMethod = MethodHandler.class)
	private String name;
	@Resolvable(colName = "Age", formatter = IntFormatter.class)
	private int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return "The name is: " + name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return 150;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
以及 TableModel 的 init。
AnnotationResolver resolver = new AnnotationResolver(Person.class);
		ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
				resolver,
				"name,age");
		tableModel.setData(getData());
运行此代码,我们会注意到所有姓名列都以“The name is: ”开头,因为它在 getName 方法中,我们为此字段使用了 MethodHandler。但对于始终返回 150 的 getAge,我们可以注意到实际设置的年龄属性,因为它仍然使用 FieldHandler。
3. 兴趣点
反射非常强大。请查看 mark.utils.el 包及其子包,了解此项目中所有基于反射的后台。
历史
- 2009年6月5日:初始版本


