ObjectTableModel

29 05 2009

Uma versao em ingles desse artigo pode ser visto em:

http://code.google.com/p/markutils/wiki/ObjectTableModel

Índice.


1. Motivação
2. Let’s Code
2.1 Básico
2.1.1 Introdução
2.1.2.1 Formatadores customizados
2.1.2.2 CellRenderers customizados
2.1.3 Métodos da interface List.
2.1.4 Alterando os valores e pegando objetos do modelo.
2.2 Avançado
2.2.1 FieldResolver
2.2.2 FieldHandler e MethodHandler
3.Para saber mais

 


1. Motivação

“NÃO use DefaultTableModel”, é comum pra mim ver pessoas que tem dificuldades usando a DefaultTableModel, e a minha dica é: não use ela. Mas implementar uma que faça todo o trabalho ser fácil para nós, não é fácil também. Então eu decedi implementar uma e vim aqui compartilhar com todos.

Meu objetivo é escrever uma unica TableModel, que seja simpels, extensivel, legivel e poderosa. E isso se tornou possivel através de Reflection e Annotations.

Com esse modelo voce:

  • Adiciona e recupera o objeto para cada linha.
  • Não precisa trabalhar com arrays de Strings..
  • Mantém os objetos atualizados para cada atualização nas celulas da tabela.
  • Configurável com anotações que simplifica a leitura do código.
  • Métodos como os da interface List: add, addAll, remove e indexOf.
  • Se voce não gosta de Annotations voce ainda pode usar(Ver capitulo 2.2.1).

2. Let’s Code.

2.1 Básico
2.1.1 Introdução

Primeiro: Baixe o código fonte do projeto na pagina Mark Utils Project desse blog.

http://markyameba.wordpress.com/towelproject/

As classes interessantes desse projeto são.(Apenas para o momento, aos poucos posto sobre o resto das classes)

ObjectTableModel que é a implementação do table model.

FieldResolver o plano de fundo do modelo é feito aqui, acessando os campos dos objetos para as colunas das tabelas.

@Resolvable a anotação que marca os campos que irão para a tabela e algumas informações como formatadores (se necessário), nome da coluna e o FieldAccessHandler(Ver cap. 2.2.2).

O valor ‘default’ para o FieldAccessHandler é FieldHandler que acessa diretamente o campo na classe, e outra implementação é o MethodHandler que utiliza os métodos get(ou is)/set na classe.

A classe AnnotationResolver apenas possui métodos para facilitar a criação de FieldResolvers.

E esse é apenas o que precisamos para criar uma JTable de uma classe.

Primeiro: Uma classe, aqui como exemplo usarei Person.

import com.towel.el.annotation.Resolvable;
public class Person {
    @Resolvable(colName = "Name")
    private String name;
    @Resolvable(colName = "Age")
    private int age;
    @Resolvable(colName = "Lives") //No ideas for another boolean
    private boolean live;
    private Person parent;
    public Person(String name, int age, boolean live) {
        this(name, age, live, null);
    }
    public Person(String name, int age, boolean live, Person parent) {
        this.name = name;
        this.age = age;
        this.parent = parent;
        this.live = live;
    }
    //Getters and setters ommited
}

E o código para criarmos a tabela é apenas este.

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 com.towel.annotation.AnnotationResolver;
import com.towel.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,live");
	//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();
    }
}

O segundo parametro da ObjectTableModel pode ser bem mais poderoso que isso.

Voce não é limitado aos atributos da classe. Voce pode usar os atributos dos campos das classes.

        AnnotationResolver resolver = new AnnotationResolver(Person.class);
        ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(
                resolver, "name,age,parent.name,parent.age");

Se voce usar "parent.name" voce ve o nome do parent na tabela.

Voce pode especificar o nome da coluna também. Apenas coloque dois-pontos(:) depois do nome do campo e escreva o nome da coluna.

  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");

2.1.2.1 Formatadores customizados.
Para os atributos comuns (byte, char, int, long, float, double, boolean e String) não é necessario formatters para funcionar, mas se voce quiser faze um parse diferente de um double voce precisara de um.

Na maioria dos casos apenas isso é o suficiente para vermos a correta visualização dos campos.

Mas e se precisarmos colocar um campo Calendar na nossa tabela?

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]

Isto não é agradavel para ver.

Por esse motivo criamos uma nova instancia de com.towel.bean.Formatter.

E as assinaturas dos métodos das classes são:

package com.towel.bean;
/**
 *@author Marcos Vasconcelos
 */
public interface Formatter {
	/**
	 * Convert a object to be show at view.
	 */
	public abstract Object format(Object obj);
	/**
	 * Convert the view object to this Object.
	 */
	public abstract Object parse(Object s);
	/**
	 * Naming proposes only
	 */
	public abstract String getName();
}

Podemos setar o formatador na anotação @Resolvable, e aqui está minha implementação para a classe Calendar.

Importante, quando implementar um Formatter, ter certeza de assinar o método format com o retorno que voce quer exibir na tabela, no meu caso formatar para String.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import com.towel.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(Object s) {
        Calendar cal = new GregorianCalendar();
        try {
            cal.setTime(formatter.parse(s.toString()));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return cal;
    }
}

Voltando a nossa classe Person vamos criar um campo Calendar chamado birth para nossa tabela.

@Resolvable(formatter = CalendarFormatter.class)
private Calendar birth;

No segundo parametro da ObjectTableModel podemos passar a seguinte String: "name,age,birth".

E a terceira coluna vai ter um valor como: "26/06/1991".

Muito melhor para se ver no lugar do Calendar.toString() por padrão.

2.1.2.2 CellRenderers customizados.
Outra opção é criar um CellRenderer a atribuir a JTable.
Por padrão para campos boolean a JTable mostra JCheckBox no valor da coluna.
Mas é possivel criar outros, que podem substituir o uso de formatters (O Default simplesmente passa o objeto para a JTable do modo que está) deixando assim ao Renderer o trabalho de renderiza-lo.

Quando forem atribuir renderers a tabela, não usem os .class de tipos primitivos (int.class, double.class, char.class, etc..) mas sim, seus respectivos wrapers (Integer.class, Double.class, Character.class, etc..)

2.1.3 Métodos da interface List.
Eu coloquei métodos da interface List no modelo por que isso torna simples trabalhar com os objetos assim como nas listas.

Ps: O método getValue pode ser usado como o get da List.(Descrito no próximo topico)

E aqui um exemplo com esses métodos.

	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);//Delete with the index
	model.remove(person1);//Delete with the object
	model.clean();//Clean the model

2.1.4 Alterando e recuperando objetos do modelo.
É claro, uma tabela não é exclusivamente para mostrar dados. No modelo tem um método chamado setEditDefault que recebe um valor boolean e o método isEditable(int x, int y) retorna esse valor. (Isso significa que se está setado true, toda tabela é editabel. Caso falso, toda tabela não será editavel.

Se setado como true voce pode editar as células. Depois do focus lost a tabela invoka o método setValueAt no modelo e ele seta o valor apropriado para o campo do objeto da linha da tabela.

O valor é passado como String e o FieldResolver usa seu Formatter para converter o valor para setar no objeto. Isso significa que voce não está limitado a trabalhar com Strings, mas a qualquer objeto. Implementando o Formatter corretamente isso se torna possivel.

E aqui um exemplo como isto funciona.

Primeiro. Nosso modelo e um Formatter.

import com.towel.el.annotation.Resolvable;
import com.towel.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);
	}
}
}

O exemplo:

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 com.towel.el.annotation.AnnotationResolver;
import com.towel.swing.table.ObjectTableModel;
import test.Person;
public class AnnotationResolverTest {
	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();
	}
}

Qualquer alteração nas células ira fazer o update no objeto.
Recuperando o objeto do modelo.
A pior parte trabalhando com JTables é para recuperar os valores. Quase todo tempo temos que usar o getValueAt e setar no atributo correspondente do objeto. Mas o objetivo desse projeto é fazer isso virar passado.

O método getValue(int row) do ObjectTableModel retorna o objeto da linha passada por argumento.

A classe ObjectTableModel é tipada e o método getValue retorna um objeto do tipo, evitando class-casting.

O seguinte código retorna o Person da segunda linha.

               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 Avançado
Tudo visto até aqui já é o suficiente para utilizar o modelo. Mas ainda assim é possivel extender mais ainda as funcionalidades da tabela para servir a qualquer proposito.
2.2.1 FieldResolver
Todo plano de fundo desse projeto está nessa classe.E a anotação @Resolvable e a classe AnnotationResolver são apenas para criar FieldResolvers.

Mas voce ainda pode utilizar sem anotações.

O seguinte código.

		FieldResolver nameResolver = new FieldResolver(Person.class, "name");
		FieldResolver ageResolver = new FieldResolver(Person.class, "age");
		ageResolver.setFormatter(new IntFormatter());
		FieldResolver parentNameResolver = new FieldResolver(Person.class,"parent.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 });

É equivalente ao seguinte.

		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());

Mas no primeiro caso não precisamos das anotações @Resolvable nos campos da classe.
FieldResolverFactory
A classe FieldResolverFactory foi feita apenas para facilitar a criação de FieldResolvers. Seu construtor recebe um Class que representa a classe que criaremos os FieldResolvers.(O mesmo que passamos como argumento para o FieldResolver)

Seus métodos são:
createResolver(String fieldName).
createResolver(String fieldName, String colName).
createResolver(String fieldName, Formatter formatter).
createResolver(String fieldName, String colName, Formatter formatter).

O primeiro exemplo que mostra como usar FieldResolvers pode ser reescrito com a FieldResolverFactory como asseguir.

		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 and MethodHandler.
Até aqui nós estamos usando o FieldAccessHandler padrão, o FieldHandler.

Usando ele, não precisamos de getters/setters para os atributos. Eles são acessados diretamente via Reflection.
Usando o MethodHandler ele procura na classe os métodos getter(ou is)/setters para utilizar para setar e pegar os valores.

Um exemplo simples:

import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import com.towel.el.annotation.Resolvable;
import com.towel.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;
	}
}

E o ObjectTableModel.

AnnotationResolver resolver = new AnnotationResolver(Person.class);
		ObjectTableModel<Person> tableModel = new ObjectTableModel<Person>(resolver,	"name,age");
		tableModel.setData(getData());

Rodando esse exemplo, notamos que todas as células da coluna name começa com "The name is:" por que isto está como retorno para o método getName e usamos o MethodHandler nesse campo. Mas para o getAge que sempre retorna 150 notamos os valores atuais por que ele ainda usa o FieldHandler.

MethodHandler e FieldHandler implementam a interface FieldAccessHandler. E é possivel criar novos handlers e passa-los como argumento, é necessario apenas implementar esta interface e utilizar, mas vejo poucos casos que realmente precise de um novo Handler então já deixei os dois implementados.

3. Pontos de Interesse

Reflection é incrível. Vejam o pacote mark.utils.el e seus subpacotes para ver toda Reflection implementada para esse projeto.

About these ads

Ações

Information

90 responses

29 05 2009
job

Grande Mark!

Gostei bastante de seu trabalho, tambem desenvolvi um Modelo que utiliza Reflection, mas no meu caso o mapeamento é realizado via XML.

Vendo e analisando sua ideia, pensei porque não seguir o caminho das anotações também?

É Algo que pensarei depois quando tiver tempo de implementar algo realmente funcional, desculpe por isso mas é que sou meio que ‘alucinado’ com o uso de Reflection.

Parabens novamente!

ps: caso deseje ver meu Modelo manda um email que passo a url pra todos verem.

28 05 2010
markyhitchhiker

Pode colocar a URL sem problemas.

Eu adoro annotations, te livra de muito parseXml.

16 07 2009
AC de Souza

Caro,

Poderia dar uma olhada num projeto do JDNC-Incubator, JXTablePanel?

Acredito que poderíamos trocar algumas idéias.

[],
AC

16 07 2009
markyhitchhiker

Achei bem interessante esse JXTablePanel.

Eu não consegui baixar os fontes por que estou sem um cliente CVS aqui. Estou curioso para ver.

Qual é sua idéia? Usar meu TableModel para a tabela?

16 10 2009
ACdeSouza

Minha idéia é um merge entre o seu código e o meu :)

[],
AC

P.S.: Desculpa não ter respondido antes, achei q receberia um email informando da resposta.

16 10 2009
markyhitchhiker

É o sistema de email do wordpress nao ta funcionando legal.

Mas parece uma boa idéia sim.

Em breve vou colocar todo projeto no SourceForge e voce pode comitar o seu projeto junto.

Vou avisar quando isso acontecer.

21 07 2009
Rodrigo Vieira

Cara, legal essa biblioteca. Parabéns!

Só uma dúvida: Estou tentando exibir uma coluna editável e com valores booleanos na tabela que estou criando. Utilizando a sua biblioteca aparece as strings true ou false na coluna, mas gostaria que aparecessem checkboxes. Dessa forma, o usuário seleciona true ou false apenas “checando” os checkboxes. Tentei criar um BooleanFormatter que implementa Formatter (do seu pacote) mas não adiantou nada, pois pelo que entendi o método format retorna apenas strings :(

Agradeço desde já! Até mais!

21 07 2009
markyhitchhiker

O TableModel é responsavel apenas para obter os dados.
Quem devia deixar a celula desse modo não é o TableModel.

Por isso que coloquei a opção de deixar uma coluna “blank”. Precisavam colocar botoes nas celulas e precisavam de uma coluna em branco para isso.

E vai ser uma lógica a parte.

Mas estou pensando em um modo de facilitar isso também.

21 07 2009
Rodrigo Vieira

hmmm….cara, não sei não, mas quando se cria uma implementação de AbstractTableModel informando que determinada coluna deve receber valores booleanos, todas as linhas dessa coluna exibem checkboxes

Dá uma olhada nesse link:

http://www.guj.com.br/posts/list/76941.java

Vou tentar contornar aqui. Assim que possível passo o que fiz.

21 07 2009
21 07 2009
Rodrigo Vieira

Entendi o recado :)

De qualquer forma retirei o booleano dos objetos que são exibidos no table model, pois não pertencem ao objeto. Utilizarei a coluna “blank”.

Valew!! Até mais!

3 10 2009
muggler

Olá Mark! Antes de mais nada, parabéns pelo belo exemplo de como se usar a API de reflection de forma construtiva!

Usando a sua API, reparei num detalhezinho que pode atrapalhar os desenvolvedores mais distraídos: A notação da string utilizada para instanciar o ObjectTableModel (string com os nomes dos campos) não suporta espaços em branco! Por exemplo, se eu usar a string “nome, idade, endereco” para construir o objeto, na hora de acessar o campo reflexivamente vou ganhar um NoSuchFieldException de presente, depois de resolver o primeiro campo! ;-D

Fica então como sugestão, fazer uma limpezinha rápida na string pra remover estes espaços antes de fazer a reflexão, ou então lançar uma exceção pro usuário explicando o que ele fez de errado com a string. No mais, achei fantástica a ferramenta! Muito bom mesmo cara! Meus parabéns!

4 10 2009
markyhitchhiker

É.. isso realmente está em uma TO-DO list minha.

Tem varias alterações o projeto inteiro em breve posto tudo atualizado.

Valeu o incentivo.

6 10 2009
muggler

é então… Se quiser compartilhar essa TO-DO list, terei prazer em ajudar! Pensei em colocar suporte a mais algumas funcionalidades, que com certeza você já deve ter pensado também, como, habilitar/desabilitar colunas dinamicamente, esse tipo de coisa… Bom, se interessar, disponha!
[]s

6 10 2009
markyhitchhiker

Então.. essa TO-DO List ta mais na minha cabeça, e fica meio dificil te mandar, mas vou enviar a ultima versão do projeto e voce ve o que consegue fazer ^^

Eu não postei nada mas a ultima versão do TableModel fora o setEditableDefault tem o setColumnEditable(int idx). Assim caso o default seja false voce pode setar alguma coluna como true.
Bem interessante as vezes.

Só não implementei um setCellEditable que não achei que fosse necessario mas fica ai uma dica pra quem quiser fazer algo.

7 10 2009
muggler

Legal! Você tem planos de colocar o projeto em um repositório público, tipo google code, ou sourceforge?

7 10 2009
markyhitchhiker

Eu não tinha planos de hospedar por que não achei que realmente era necessario, agora vi que se fizer isto as pessoas podem ajudar.

Em breve eu coloco no SourceForge e aviso.

3 12 2009
Patrick

olá quem estiver interressado numa ferramenta de mapeamentro objeto – relacional de uma olhada:http://mapeator.blogspot.com/

11 12 2009
Fabio

Olha marky.
Gostei muito do seu projeto.

Tenho uma duvida.

Como trato campos herdados utilizando o ObjectTableModel?

11 12 2009
markyhitchhiker

Hmm.. esse é um problema na arquitetura que montei.

Para pegar os campos ele usa o método getDeclared(Field/Method) e de acordo com a API este método retorna apenas os métodos e campos locais.
Para buscar em uma super-classe é necessario usar o get(Field/Method) mas nao consegueria mais enxergar os campos privados por isso escolhi a primeira opção.
Mas de qualquer modo.
Se voce criar um novo FieldAccessHandler para ler em hierarquia voce pode passar ele par ao Resolver que voce consegue ler.

Se me sobrar um tempo eu mesmo faço isso nos AccessHandlers que já estão prontos.

9 01 2010
Andre Silva

Boas!

Queria implementar este tablemodel, mas ele não permite trabalhar com classes com herança, certo? Dará muito trabalho eu implementar um tablemodel que funcione dessa forma?

Obrigado e desculpa se disse algo errado!

11 01 2010
markyhitchhiker

Pra falar a verdade não é tão dificil, voce só precisa criar um novo FieldAccessHandler que veja a herança.

Eu mesmo to devendo de implementar isso para uma proxima versão mas estou sem tempo.

Se quiser voce pode ver o FieldHandler e adapto-lo para ver os campos por herança caso não esteja na classe.

E caso implemente poderia me enviar e compartilhar né?^^

6 05 2010
Bira

Boa tarde, Mark.
Primeiramente gostaria de lhe parabenizar pelo trabalho. Muito bom mesmo, e me ajudou bastante na criação de uma tabela.
Porém agora estou com um pequeno problema… preciso criar uma tabela com 4 colunas, sendo 3 delas combobox. É possível fazer isso utilizando esse modelo ou terei que recorrer ao DefaultTableModel? Também, se não for pedir muito, você poderia me direcionar para algum exemplo?
Obrigado!
Bira

6 05 2010
markyhitchhiker

Bem.. infelizmente não há como.

Eu preciso fazer algumas alterações no TableModel para fazer isso e também uma coluna com checkbox quando for boolean.

Mas voltar para o DefaultTableModel é a pior idéia que voce pode ter.

6 05 2010
Bira

Bem, vou tentar criar um modelo então… já que o uso do DefaultTableModel é completamente recomendável! Vou procurar na net algum material sobre o assunto.
Obrigado pela pronta-resposta!
Abraços

6 05 2010
markyhitchhiker

Bem.. voce pode baixar o projeto do google code e criar e modificar meu model para atender essas necessidades. E depois ainda me enviasse para que eu pudesse compartilhar com todos sua solução.

20 05 2010
Filipe

Bom dia !

Estou tentando usar seu projeto aqui…

Estou querendo formatar a classe Date ao invés do Calendar como descrito no artigo, mas não sei como. Também vejo que na classe Formatter quando vou implementar as interfaces, existe um método diferente do descrito: o parse, que tem como parametro um Object ao invés de uma String.

Poderia me ajudar ?

Att
Filipe Santana.

20 05 2010
markyhitchhiker

Desculpe, mudei a assinatura da classe Formatter pois agora uso em outros lugares do sistema.

Mas os formatters do ObjectTableModel só recebem String mesmo com essa assinatura.

Um Formatter para date seria como:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import mark.utils.bean.Formatter;

public class DateFormatter implements Formatter {
private DateFormat format = new SimpleDateFormat(“dd/MM/yyyy”);

public Object format(Object obj) {
Date cal = (Date) obj;
if (cal == null)
return “”;
return format.format(cal);
}

public Object parse(Object obj) {
try {
return format.parseObject(obj.toString());
} catch (ParseException e) {
return “ERROR”;
}
}

public String getName() {
return “date”;
}
}

20 05 2010
Filipe

Ok, sem problemas.

Muito obrigado pela ajuda !

Att,
Filipe Santana.

28 05 2010
Daniel Lage

Boa tarde,

Muinto bacana este projeto. Também sou adepto a utilização de reflexão. Ajuda bastante.
Recentemente escrevi uma biblioteca para gerar relatórios em planilhas xls, utilizando anotações e reflexão. O projeto utiliza o jxl para criar as planilhas. Por ter desenvolvido esta biblioteca e utilizado nos projetos da empresa onde trabalho, acredito não poder postar seu fonte.

Um abraço a todos e, realmente, ótimo trabalho e iniciativa.

16 06 2010
Isabel

Olá marky! Primeiro quero te parabenizar pelo projeto! Muito legal a sua iniciativa!

Segundo queria que vc desse uma verificada no link com o código fonte: http://markyameba.wordpress.com/markutilsproject/

Eu não consigo abrir esta página…

Até + !

19 07 2010
Diego Queres

Bom dia, tudo bem?

Eu estava pensando… não seria possível fazer um Table Model desse tipo, mas utilizando generics? Desse modo, ele automaticamente se ajustaria para a classe que eu fosse mostrar na tabela.
Os formatters mais personalizados (como datas, horas, etc) provavelmente teriam que ser fornecidos pelas classes que serão demonstradas na tabela (provavelmente forçando a implementação de uma interface, pelo menos).

19 07 2010
markyhitchhiker

Pra falar a verdade faz um tempo que penso em algo parecido, voce simplesmente passa o class e o nome das colunas e ele gera sozinho sem precisar da anotação.
Mas é que ando sem tempo e sem computador para continuar atualizando o projeto.
Mas espero conseguir fazer isso em breve.
Valeu pela dica!

21 07 2010
Wagner M R

Boa tarde Marky !
Primeiro quero te parabenizar pelo projeto! Muito boa a sua iniciativa!!
Gostaria de saber se teria como me passar alguma dica ou exemplo de como adicionar um botão ou checkbox em cada linha no tablemodel.
É possível ?
Desde já agradeço pela atenção dispensada e fico aguardando.
Atenciosamente,
Wagner

22 07 2010
markyhitchhiker

Então, como colocar um JButton vai dar bem mais trabalho de explicar.
Mas para CheckBoxs voce pode colocar um Formatter no seu atributo boolean que no método parse recebera um objeto boolean e no objeto format devera retornar um boolean.
Assim a JTable automaticamente ira colocar os JCheckBoxes pra voce. Se sua versao do MarkUtils só aceita esses métodos do Formatter com a assinatura para Strings, baixe a ultima versao do projeto que agora a assinatura foi modificada para receber e retornar Object para tornar isso possivel.

20 08 2010
Izac

Mark, em 1º lugar parabens pela iniciativa. e eu estava olhando a sua implementação do TabelModel aqui na pagina, mas ainda não baixei o src e queria saber algumas coisas:
- vc implementou alguma validação no caso de entrar dados no table, por ex: se eu colocar um formatter do tipo int(como vc fez) e digitar uma letra “A” o que acontece?

- seu table suporta outros componentes, como por ex. um ComboBox, ou Button(com uma ação para chamar uma tela de edição-por exemplo), ou um RadioButton e etc…? E no caso do RadioButton fazer o bind com o atributo(true ou false) do objeto Person(por exemplo classificado sim ou não)?

- como o seu TableModel trabalhar com internacionalização?

Abraços

Izac

21 08 2010
markyhitchhiker

Em 1° lugar obrigado.

-Então, a validação seria dentro do formatter mesmo, e caso voce faça apenas o parse e seja lançado uma Exception o model não altera o objeto.PS: Caso ele retorne null o model tenta colocar null no valor.

-Então, na ultima versão na classe Formatter ele aceita um Object para o parse e tem que retornar um Object no format. Se voce retorna um boolean por exemplo sem transformar em String a JTable já renderiza um CheckBox, caso para outros componentes voce pode criar um Formatter que retorna por exemplo um botão por que o model retornaria JButton.class para a JTable, e depois voce cria um TableCellRenderer e coloca na JTable para desenhar botoes quando for retornado JButton.class, já para radios nunca fiz nada.

-Não fiz nada para internacionalização.

27 08 2010
Fabio

Ola Mark,

Novamente estou aqui te fazendo perguntas.

Mark, estou tentando fazer o ObjectTableModel,
suportar JComponets nas celulas de algumas colunas, como o JButton, JCheckBox, JSpinner, JComboBox, JLabel.

O problema é que não estou conseguindo.
Ja Tentei fazer o Formatter, tentei implementar um TableCellRenderer,
mas tudo sem sucesso.

Sera que voce não poderia incluir no MarkUtils um exemplo de como utilizar JComponents com o ObjectTableModel, seria bem util.

Des de ja.
Obrigado por ter criado esses otimos projetos.

27 08 2010
markyhitchhiker

Então, eu preciso mudar uma coisa na estrutura do model.

Vou fazer isso esse final de semana, e também melhorar os docs.
Aviso em breve.

16 11 2010
MarcosFPo

Mark,

Uma sugestão: mudar a anotação @Resolvable para aceitar também os getters, não só os fileds. Quando se herda uma classe com propriedades privadas, fica mais trabalhoso para fazer as anotações. Eu estou querendo usar sua “API”, mas as classes que preciso nas “listar” nas tabelas são proprietárias. Ou seja, não tenho o fonte. Pensei em criar decorators herdados das classes e anotá-los. Se as anotações pudessem ser usadas nos getters, bastava eu fazer um override e já estaria tudo bem. Estou tentando mudar seu código para aceitar getters. Comecei agora. Não sei se terei tempo de terminar. Se conseguir eu mando para você.

17 11 2010
markyhitchhiker

Tem um modo para funcionar.

Criar o FieldResolver diretamente, voce passa o getClass() da super classe mas o FieldResolver será usado na classe filha.

Mas eu preciso criar um modo para herança mesmo.

21 11 2010
Daniel

Olá Mark,

Muito bom seu trabalho, parabéns !
Eu estou fazendo uma implementação em um projeto meu e estou com uma situação estranha.
Tenho uma classe que é uma entity do meu banco de dados e por isso não usei as notações e então eu mapeio as colunas conforme seu exemplo :
FieldResolverFactory fac = new FieldResolverFactory(Ativos.class);

FieldResolver data = fac.createResolver(“data” ,”Data” );
data.setFormatter(new CalendarForm());
FieldResolver codfun = fac.createResolver(“codfun” ,”Fundo” );
FieldResolver atvo = fac.createResolver(“ativo” ,”Ativo” );
FieldResolver qtd = fac.createResolver(“quantidade” ,”qtd” );
FieldResolver pu = fac.createResolver(“pu” ,”Pu” );
FieldResolver m = fac.createResolver(“mercado” ,”M” );
FieldResolver op = fac.createResolver(“operacao” ,”Op” );
FieldResolver ls = fac.createResolver(“ls” ,”Ls” );

mas o setFormatter parece não funciona porque minha tabela fica com o conteúdo da coluna data em branco.

Onde será que estou errando ?

valeu !!
Daniel

22 11 2010
markyhitchhiker

Hmm.. como está a implementação do seu formatter?

Por acaso não está branco onde deveria é null?

22 11 2010
Daniel

Olá Mark obrigado pelo retorno !

O formatter está implementado igual ao seu exemplo, apenas mudei o nome.

Veja :

public class CalendarForm implements Formatter {
private final static SimpleDateFormat formatter =
new SimpleDateFormat(“dd/MM/yyyy”);

@Override
public String format(Object o) {
Calendar cal = (Calendar) o;
return formatter.format(cal.getTime());
}

@Override
public String getName() {
return “calendar”;
}

@Override
public Object parse(Object s) {
Calendar cal = new GregorianCalendar();
try {
cal.setTime(formatter.parse(s.toString()));
} catch (ParseException e) {
e.printStackTrace();
}
return cal;
}

22 11 2010
markyhitchhiker

Hmm.. voce tem certeza que os dados nos objetos tem valores? Por que ele apresenta em branco se for nulo.

23 11 2010
Daniel

Então tenho certeza !
Porque quando eu tiro o formatter, os valores de data volta a aparecer.

Fiz um outro teste e coloquei alguns System.out para saber quando o formatter é chamado e para minha supresa o nem um dos 3 metodos da classe foi executado.

estranho não acha ?

para referencia estou usando esses objetos dentro de um modulo do netbeans.

abcs
Daniel

23 11 2010
markyhitchhiker

Nossa, muito estranho.

Eu vou investigar isso pra voce, mas só posso fazer isso no final de semana.

Só para ter certeza, voce está usando a versão 2.8 né?

Por que isso era um erro que deixei que já corrigi.

23 11 2010
Daniel

Valeu pela ajuda !

Posso tirar mais uma duvida ?…

Como faço para pegar as linhas que tiveram celulas alteradas

thanks !

23 11 2010
markyhitchhiker

Hmm..não existe uma maneira pronta para isso, voce realmente precisa pegar a lista de objetos e verificar.

Nunca tinha pensado nessa possibilidade, mas não parece dificil implementar isso, se eu precisar arrumar algo por causa do outro problema que voce descobriu eu já coloco algo para te ajudar com isso.

24 11 2010
MarcosFPo

Mark,

como faço para entrar em contato com vc?
tenho umas implementações que gostaria que vc visse.

obrigado.

24 11 2010
markyhitchhiker

Tenho o msn marcosavjr(at)hotmail.com

Mas eu não entro durante semana, e só de vez em quando no final de semana.

Mas email eu sempre vejo e respondo.

12 12 2010
Fabio

Ola markyhitchhiker, blz.

Eu aqui novamente…

Minha ultima pergunta foi a de como colocar um JComponent ( JButton, etc) na Tabela com o ObjectTableModel….
Ainda estou na esperança de vc postar um exemplo….

Mas nao vim aqui pra isso.

Da uma olhada na minha implementacao de um FieldResolver Automatico, para facilitar a vida quando se quer todos os campos que estiverem com a anotacao @Resolvable

Ah.. sera que ha uma forma um pouco mais elegante(Menos tosca) de fazer o mesmo??

Novamente…
Obrigado por ter criado o MarckUtils

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import mark.utils.el.FieldResolver;
import mark.utils.el.factory.FieldResolverFactory;

/**
*
* @author Fabio C. Barrionuevo da Luz
*/
public class AutoAllFieldResolver {

private Class classType;

public AutoAllFieldResolver(Class classType) {
if (classType == null) {
throw new IllegalArgumentException(“Argumento nao pode ser nulo!”);
}
this.classType = classType;
}

public FieldResolver[] autoResolveFields() {
FieldResolverFactory fac = new FieldResolverFactory(classType);
List fieldTempList = new ArrayList();
Field fieldlist[] = classType.getDeclaredFields();
for (Field fld : fieldlist) {
Annotation[] bb = fld.getDeclaredAnnotations();
for (Annotation c : bb) {
String str = c.toString();
if (str.contains(“colName=”)) {
String[] str2 = str.split(“colName=”);
str2[1] = str2[1].replace(“)”, “”);
if (str2[1].equals(“”)) {
fieldTempList.add(fac.createResolver(fld.getName()));
} else {
fieldTempList.add(fac.createResolver(fld.getName(), str2[1]));
}
}
}
}
FieldResolver[] fieldResolver = fieldTempList.toArray(new FieldResolver[fieldTempList.size()]);
return fieldResolver;
}
}

28 12 2010
ObjectTableModel « MarkyHitchhiker's Blog

[...] Acesse com este novo link. [...]

25 01 2011
mrrbigu

Olá Marky, você implementou o FieldAccessHandler que veja a herança, para o ObjectTableModel? Estou enfrentando esse problema aqui.

Obrigado.

26 01 2011
markyhitchhiker

Hmm.. ainda não, vou ver se faço isso para colocar na ultima versão que estou preparando para lançar em breve.

27 03 2011
Vinicius

Gostaria de saber se tem um exemplo de um Formatter para moeda ?

28 03 2011
markyhitchhiker

Pior que não tenho pronto. Mas se voce quer criar um campo com uma mascara para centavos então voce devia fazer isso através do Renderer e não do Formatter.

22 06 2011
Brruno

Cara não entendi nada ….to tentando entender ainda.
preciso passar meu resultset para o tablemodel mais nao sei criar =[

mas mesmo assim otimo topico…

22 06 2011
MarkyHitchhiker

Voce nao deve passar um ResultSet para o TableModel, no seu DAO, ele deve usar esse ResultSet para criar uma List dos seus objetos, e estes sim vão para o TableModel.

24 06 2011
Bruno

criei minha tablemodel
no DAO passei os results sets para objetos e criei uma list na tablemodel
como passar agora para o evento do botao chamar a JTable

24 06 2011
MarkyHitchhiker

Crie seu TableModel e uma JTable com ele (ainda estara vazio) e guarde uma variavel na sua classe.

No evento do botão, use essa variavel do TableModel para adicionar os valores que vem do seu DAO.

Simplesmente assim.

25 10 2011
Roberto Barbosa de Sousa

Cara muito bacana o trabalho.
Tem como colocar um jComboBox em uma cell ??

Valeu!

27 10 2011
Roberto Barbosa de Sousa

Consegui aqui.

JComboBox comboBox = new JComboBox(new DefaultComboBoxModel(UsuarioStatusDomain.getValues(false)));
tabelaUsuario.getColumnModel().getColumn(2).setCellEditor(new DefaultCellEditor(comboBox));

onde UsuarioStatusDomain é um enum e o atributo mapeado para a coluna guarda uma instancia desse mesmo enum.

1 11 2011
UlissesSouza

Criei mina table model e coloquei um checkbox em uma coluna mas nao consigo clicar e “desclicar” no checkbox. O que eu faço pra capturar este estado.

2 11 2011
MarkyHitchhiker

Essa coluna que é um checkbox, é só um campo boolean do seu model né?

Voce deixou o TableModel editavel? (setEditableDefault)

3 11 2011
UlissesSouza

O checkbox realmente é só um campo boolean mesmo. E o TableModel está editavel mas mesmo assim nao consigo.

3 11 2011
MarkyHitchhiker

Tem como postar algum codigo?

21 12 2011
Cássio

Olá Mark,

Eu utilizo o seu projeto towel ha algum tempo, principalmente o ObjectTableModel, juntamente com formatadores
estou com dúvida em como criar uma classe que seja utilizada para formatar outros tipos de valores, como
quantidade (valores inteiros e casas decimais) e valore percentuais com o simbolo %

Obrigado

21 12 2011
MarkyHitchhiker

Vou tentar conseguir um exemplo para voce.

23 03 2012
wasolucoes

Fala Mark!!
Primeiramente eu gostaria de te parabenizar pela sua iniciativa, que é ótima e me ajuda muito em meus projetos!
Pode ser que eu esteja errado, mas creio que tanto o método remove que recebe o índice da seleção quanto o que recebe um objeto lançam IndexOutOfBoundsException quando removemos o último objeto do modelo…
Todos os objetos ele remove do modelo e atualiza a JTable normalmente, mas ao chegar no último registro ele lança a exception.
Saberia me dizer se estou fazendo algo errado?
Mais uma vez, parabéns pelo seu projeto!!

23 03 2012
MarkyHitchhiker

Obrigado!

Se isso for um bug mesmo eu não conhecia, tem como mandar um codigo simples que reproduza o erro?

24 03 2012
wasolucoes

Eu estava fazendo um teste com uma situação mais simples pra te enviar e percebi que o bug não existe. Foi um erro em uma implementação que eu fiz…. rs.

Vlw!

24 03 2012
MarkyHitchhiker

Heheheh

Que bom :)

27 04 2012
eliangela

Olá, Marky!
Muito boa essa implementação do ObjectTableModel!
Eu estou fazendo um sistema com suporte a internacionalização, e precisei fazer uma adaptação na classe AnnotationResolver pra aceitar nome de coluna variável. Ficou ótimo!

28 04 2012
MarkyHitchhiker

Legal, parabéns por conseguir.

Se possivel compartilhar o codigo depois fique a vontade.

20 06 2012
Jader Caresia

Primeiramente Parabéns pelo seu projeto!
Utilizo ele e acho muito bom.
Mas estou com uma dúvida… Como posso fazer para adicionar uma coluna “Selecionado” do tipo Boolean para cada linha da Jtable? Andei lendo acima os comentários sobre CheckBoxes e não achei alguma solução ainda..

Obrigado

Parabéns!

20 06 2012
MarkyHitchhiker

Primeiramente obrigado! ^^

Basta seu modelo ter um campo boolean que a JTable ira exibir um checkbox.

20 06 2012
Jader Caresia

Perfeito… Mas não consigo realizar a edição deste campo.

20 06 2012
MarkyHitchhiker

Voce tem que deixar a tabela editavel através do model.

Ou com setEditableDefault ou entao apenas a coluna com setColumnEditable

20 06 2012
Jader Caresia

Fiz isso e não funcionou. Ele não muda o status do CheckBox.
Posso estar falando besteira mas ele só muda o Status do CheckBox caso o campo do modelo estiver na base de dados..
Pois neste mesmo modelo tenho um campo chamado “Padrao” do tipo Boolean e este funciona..
Como o meu campo “Selecionado” não está na base de dados ele não funcionou.

20 06 2012
MarkyHitchhiker

Ele funciona para qualquer lista de objetos. Voce está pesquisando o valor atualizado no mesmo objeto que voce colocou no model? A mesma instancia que será modificada.

20 06 2012
Jader Caresia

Que erro meu! Agora funcionou perfeitamente..
Obrigado pelas repostas rápidas..
Parabéns

20 06 2012
MarkyHitchhiker

Por nada, obrigado!

8 07 2012
Como implementar um TableModel. | DevSV

[...] pra finalizar, quem já entendeu o conceito e quer ir um pouco além, pode pesquisar sobre o ObjectTableModel do projeto Towel, criado pelo Marky Vasconcelos, que é um table model genérico e bem flexível. [...]

29 09 2012
System

Mark, tenho uma dúvida e talvez você possa me ajudar.
É possível gerar um model com o AnnotationResover de duas classes diferentes?
Você usou ali o Person e gerou 3 colunas, tem por ex como gerar mais uma coluna no mesmo model de uma classe Família por ex.

1 10 2012
MarkyHitchhiker

Bem, não é possivel pois sempre que voce for fazer um TableModel voce criará ele baseado em um objeto modelo (um unico .class).

Mas se voce por exemplo quer mostrar dados de duas pessoas diferentes, voce pode criar esse objeto ‘Familia’ que tenha dois Person e criar o TableModel em cima dele. Nesse caso funcionaria.

30 08 2013
Léo

Parabéns pelo projeto! Com ele fica muito fácil e rápido.

Estou com problemas para verificar se alguma linha (e qual) foi selecionada.
Tentei usar o
linhaSelecionada = tabela.getSelectedRow();

Mas não está dando certo.
Se puder me ajude.

Desde já agradeço.

30 08 2013
MarkyHitchhiker

O getSelectedRow() retorna um inteiro, que é o indice do objeto que representa a linha. Para ter o objeto basta:

ObjectTableModel model = tabela.getModel();
Pessoa pessoa = model.get(tabela.getSelectedRow());

12 10 2013
Roberto Silva

Olá blz Mark,primeiramente parabéns excelente framework!
Estou usando ObjectTableModel em meu projeto desktop, adicionei o jar towel-v1.2.2.jar em minha lib, estou ofuscando o código com proguard 4.8 no netbeans, quando vou abrir uma classe que faz uso do ObjectTableModel tenho uma exceção de NullPointerException na classe ObjectTableModel linha 167 , você pode me dar uma força de como contornar isso, obrigado.

21 12 2013
MarkyHitchhiker

Não lembro se comentei, mas ao ofuscar o código final é necessario manter os nomes dos atributos, normalmente é um -keepnames no seu proguard.cfg

Isso deve resolver seu problema.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s




Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

Junte-se a 153 outros seguidores

%d blogueiros gostam disto: