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.
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.
Pode colocar a URL sem problemas.
Eu adoro annotations, te livra de muito parseXml.
Caro,
Poderia dar uma olhada num projeto do JDNC-Incubator, JXTablePanel?
Acredito que poderíamos trocar algumas idéias.
[],
AC
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?
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.
É 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.
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!
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.
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.
http://www.exampledepot.com/egs/javax.swing.table/CustRend.html
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!
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!
É.. isso realmente está em uma TO-DO list minha.
Tem varias alterações o projeto inteiro em breve posto tudo atualizado.
Valeu o incentivo.
é 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
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.
Legal! Você tem planos de colocar o projeto em um repositório público, tipo google code, ou sourceforge?
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.
olá quem estiver interressado numa ferramenta de mapeamentro objeto – relacional de uma olhada:http://mapeator.blogspot.com/
Olha marky.
Gostei muito do seu projeto.
Tenho uma duvida.
Como trato campos herdados utilizando o ObjectTableModel?
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.
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!
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é?^^
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
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.
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
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.
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.
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”;
}
}
Ok, sem problemas.
Muito obrigado pela ajuda !
Att,
Filipe Santana.
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.
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é + !
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).
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!
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
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.
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
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.
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.
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.
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ê.
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.
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
Hmm.. como está a implementação do seu formatter?
Por acaso não está branco onde deveria é null?
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;
}
Hmm.. voce tem certeza que os dados nos objetos tem valores? Por que ele apresenta em branco se for nulo.
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
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.
Valeu pela ajuda !
Posso tirar mais uma duvida ?…
Como faço para pegar as linhas que tiveram celulas alteradas
thanks !
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.
Mark,
como faço para entrar em contato com vc?
tenho umas implementações que gostaria que vc visse.
obrigado.
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.
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;
}
}
[...] Acesse com este novo link. [...]
Olá Marky, você implementou o FieldAccessHandler que veja a herança, para o ObjectTableModel? Estou enfrentando esse problema aqui.
Obrigado.
Hmm.. ainda não, vou ver se faço isso para colocar na ultima versão que estou preparando para lançar em breve.
Gostaria de saber se tem um exemplo de um Formatter para moeda ?
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.
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…
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.
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
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.
Cara muito bacana o trabalho.
Tem como colocar um jComboBox em uma cell ??
Valeu!
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.
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.
Essa coluna que é um checkbox, é só um campo boolean do seu model né?
Voce deixou o TableModel editavel? (setEditableDefault)
O checkbox realmente é só um campo boolean mesmo. E o TableModel está editavel mas mesmo assim nao consigo.
Tem como postar algum codigo?
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
Vou tentar conseguir um exemplo para voce.
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!!
Obrigado!
Se isso for um bug mesmo eu não conhecia, tem como mandar um codigo simples que reproduza o erro?
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!
Heheheh
Que bom
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!
Legal, parabéns por conseguir.
Se possivel compartilhar o codigo depois fique a vontade.
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!
Primeiramente obrigado! ^^
Basta seu modelo ter um campo boolean que a JTable ira exibir um checkbox.
Perfeito… Mas não consigo realizar a edição deste campo.
Voce tem que deixar a tabela editavel através do model.
Ou com setEditableDefault ou entao apenas a coluna com setColumnEditable
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.
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.
Que erro meu! Agora funcionou perfeitamente..
Obrigado pelas repostas rápidas..
Parabéns
Por nada, obrigado!
[...] 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. [...]
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.
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.