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.
maio 29, 2009 at 10:00 pm
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.
maio 28, 2010 at 8:07 pm
Pode colocar a URL sem problemas.
Eu adoro annotations, te livra de muito parseXml.
julho 16, 2009 at 8:49 pm
Caro,
Poderia dar uma olhada num projeto do JDNC-Incubator, JXTablePanel?
Acredito que poderíamos trocar algumas idéias.
[],
AC
julho 16, 2009 at 9:10 pm
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?
outubro 16, 2009 at 1:09 pm
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.
outubro 16, 2009 at 1:14 pm
É 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.
julho 21, 2009 at 6:43 pm
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!
julho 21, 2009 at 6:50 pm
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.
julho 21, 2009 at 7:02 pm
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.
julho 21, 2009 at 7:42 pm
http://www.exampledepot.com/egs/javax.swing.table/CustRend.html
julho 21, 2009 at 8:10 pm
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!
outubro 3, 2009 at 6:56 pm
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!
outubro 4, 2009 at 12:05 am
É.. isso realmente está em uma TO-DO list minha.
Tem varias alterações o projeto inteiro em breve posto tudo atualizado.
Valeu o incentivo.
outubro 6, 2009 at 12:39 am
é 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
março 1, 2020 at 5:46 pm
Talvez já tenha algo mais atualizado, mas em todo caso, aqui está a minha solução…
(…)
public FieldResolver[] resolve(String… fieldNames) {
/* retirando os espaços, antes de depois dos nomes */
/* “field1, field2, field3 ,… => field1,field2,field3,… */
int i = 0;
for( String fld: fieldNames ) { fieldNames[i] = fld.trim(); i++; }
FieldResolver resolvers[] = new FieldResolver[fieldNames.length];
if (fieldNames.length == 0)
return resolvers;
for (i = 0; i < fieldNames.length; i++) {
try {
(…)
ps.: se houver alguma atualização mais recente, eu gostaria de saber, pfvr.
outubro 6, 2009 at 12:26 pm
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.
outubro 7, 2009 at 11:07 am
Legal! Você tem planos de colocar o projeto em um repositório público, tipo google code, ou sourceforge?
outubro 7, 2009 at 12:39 pm
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.
dezembro 3, 2009 at 3:49 pm
olá quem estiver interressado numa ferramenta de mapeamentro objeto – relacional de uma olhada:http://mapeator.blogspot.com/
dezembro 11, 2009 at 12:27 am
Olha marky.
Gostei muito do seu projeto.
Tenho uma duvida.
Como trato campos herdados utilizando o ObjectTableModel?
dezembro 11, 2009 at 10:24 am
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.
janeiro 9, 2010 at 10:53 pm
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!
janeiro 11, 2010 at 10:33 am
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é?^^
maio 6, 2010 at 6:59 pm
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
maio 6, 2010 at 7:11 pm
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.
maio 6, 2010 at 7:16 pm
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
maio 6, 2010 at 7:50 pm
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.
maio 20, 2010 at 1:09 pm
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.
maio 20, 2010 at 1:16 pm
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”;
}
}
maio 20, 2010 at 1:25 pm
Ok, sem problemas.
Muito obrigado pela ajuda !
Att,
Filipe Santana.
maio 28, 2010 at 6:13 pm
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.
junho 16, 2010 at 1:44 pm
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é + !
julho 19, 2010 at 10:25 am
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).
julho 19, 2010 at 2:35 pm
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!
julho 21, 2010 at 4:43 pm
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
julho 22, 2010 at 1:15 pm
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.
agosto 20, 2010 at 10:39 pm
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
agosto 21, 2010 at 11:20 am
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.
agosto 27, 2010 at 7:13 pm
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.
agosto 27, 2010 at 10:09 pm
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.
novembro 16, 2010 at 6:25 pm
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ê.
novembro 17, 2010 at 9:43 am
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.
novembro 21, 2010 at 10:42 pm
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
novembro 22, 2010 at 9:57 am
Hmm.. como está a implementação do seu formatter?
Por acaso não está branco onde deveria é null?
novembro 22, 2010 at 6:41 pm
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;
}
novembro 22, 2010 at 9:17 pm
Hmm.. voce tem certeza que os dados nos objetos tem valores? Por que ele apresenta em branco se for nulo.
novembro 23, 2010 at 9:38 am
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
novembro 23, 2010 at 9:46 am
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.
novembro 23, 2010 at 11:00 am
Valeu pela ajuda !
Posso tirar mais uma duvida ?…
Como faço para pegar as linhas que tiveram celulas alteradas
thanks !
novembro 23, 2010 at 12:07 pm
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.
novembro 24, 2010 at 8:25 pm
Mark,
como faço para entrar em contato com vc?
tenho umas implementações que gostaria que vc visse.
obrigado.
novembro 24, 2010 at 8:54 pm
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.
dezembro 12, 2010 at 6:32 am
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;
}
}
janeiro 25, 2011 at 11:41 am
Olá Marky, você implementou o FieldAccessHandler que veja a herança, para o ObjectTableModel? Estou enfrentando esse problema aqui.
Obrigado.
janeiro 26, 2011 at 9:57 am
Hmm.. ainda não, vou ver se faço isso para colocar na ultima versão que estou preparando para lançar em breve.
março 27, 2011 at 1:15 pm
Gostaria de saber se tem um exemplo de um Formatter para moeda ?
março 28, 2011 at 11:21 pm
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.
junho 22, 2011 at 2:35 pm
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…
junho 22, 2011 at 5:14 pm
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.
junho 24, 2011 at 3:09 pm
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
junho 24, 2011 at 3:50 pm
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.
outubro 25, 2011 at 5:30 pm
Cara muito bacana o trabalho.
Tem como colocar um jComboBox em uma cell ??
Valeu!
outubro 27, 2011 at 10:50 am
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.
novembro 1, 2011 at 8:45 pm
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.
novembro 2, 2011 at 2:18 pm
Essa coluna que é um checkbox, é só um campo boolean do seu model né?
Voce deixou o TableModel editavel? (setEditableDefault)
novembro 3, 2011 at 9:58 am
O checkbox realmente é só um campo boolean mesmo. E o TableModel está editavel mas mesmo assim nao consigo.
novembro 3, 2011 at 6:21 pm
Tem como postar algum codigo?
dezembro 21, 2011 at 9:47 am
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
dezembro 21, 2011 at 9:48 am
Vou tentar conseguir um exemplo para voce.
março 23, 2012 at 2:40 am
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!!
março 23, 2012 at 2:38 pm
Obrigado!
Se isso for um bug mesmo eu não conhecia, tem como mandar um codigo simples que reproduza o erro?
março 24, 2012 at 2:07 am
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!
março 24, 2012 at 4:33 pm
Heheheh
Que bom 🙂
abril 27, 2012 at 2:11 pm
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!
abril 28, 2012 at 2:46 pm
Legal, parabéns por conseguir.
Se possivel compartilhar o codigo depois fique a vontade.
junho 20, 2012 at 12:21 pm
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!
junho 20, 2012 at 12:25 pm
Primeiramente obrigado! ^^
Basta seu modelo ter um campo boolean que a JTable ira exibir um checkbox.
junho 20, 2012 at 12:35 pm
Perfeito… Mas não consigo realizar a edição deste campo.
junho 20, 2012 at 1:50 pm
Voce tem que deixar a tabela editavel através do model.
Ou com setEditableDefault ou entao apenas a coluna com setColumnEditable
junho 20, 2012 at 1:54 pm
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.
junho 20, 2012 at 2:14 pm
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.
junho 20, 2012 at 2:28 pm
Que erro meu! Agora funcionou perfeitamente..
Obrigado pelas repostas rápidas..
Parabéns
junho 20, 2012 at 2:41 pm
Por nada, obrigado!
setembro 29, 2012 at 11:14 pm
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.
outubro 1, 2012 at 11:10 am
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.
agosto 30, 2013 at 11:08 am
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.
agosto 30, 2013 at 3:24 pm
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());
outubro 12, 2013 at 4:20 pm
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.
dezembro 21, 2013 at 3:54 pm
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.
abril 2, 2014 at 9:16 am
E ai Marky, bom?
Parabéns pelo projeto, eu vi ele a algum tempo mais não havia usado, não sabia que era um projeto seu e continuava me matando com os defaultmodels da vida. hehe..
Agora estou usando e gostei…
Qualquer duvida eu uso meu direito de família e te ligo. rs.
Parabéns cara.
dezembro 23, 2014 at 10:31 am
Valeu aew Marcelo!
dezembro 15, 2015 at 10:48 am
Olá Marky, parabéns pelo projeto!
Gostaria de saber se é possível adicionar uma coluna contendo JSlider, controle deslizante, e também se a classe AnnotationResolver aceita nome de coluna como variável?
março 7, 2016 at 11:43 pm
Obrigado, você declara o nome da coluna no momento que estiver resolvendo a variavel. Ou não entendi sua duvida ^^