CalendarView

1 06 2011

Esse artigo é sobre uma classe do projeto Towel, para utiliza-la, baixe a biblioteca na pagina Towel Project desse blog.

Esse componente foi feito em 1999 pelo meu professor de Java enquanto ele fazia estagio na area, por esse motivo eu desculpo ele por ter usado null-layout, mas refatorei ele para ser mais generico e independente de LayoutManagers.

O CalendarView é um componente que exibe um JTextField e um JButton juntos.
O JTextField serve para digitar dadas no formato ‘dd/MM/yyyy’ e o JButton abre uma JWindow que permite escolher a data em um calendario.

Para utiliza-lo é bem simples:

import java.awt.event.*;
import javax.swing.*;

import com.towel.swing.calendar.CalendarView;

public class CalendarViewTest {
	public CalendarViewTest() {
		JFrame frame = new JFrame("CalendarView");
		JPanel content = new JPanel();
		final CalendarView view = new CalendarView();
		JButton button = new JButton("X");
		content.add(view);
		content.add(button);
		button.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.out.println(view.getSelectedDate());
			}
		});

		frame.setContentPane(content);
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	public static void main(String[] args) {
		new CalendarViewTest();
	}
}

Esse codigo exibe a seguinte janela:

E quando pressionado o botao “..” a JWindow aparece com o DatePicker para escolher uma data.

Escolhendo alguma data o valor é exibido no campo de texto.

Para obter o valor que o usuario digitou (em uma ação por exemplo), temos dois métodos:

  • getText() que retorna o texto do TextField
  • getSelectedDate() que retorna um objeto Calendar com a data selecionada pelo usuario.
  • E no caso do código de exemplo, ao pressionar ‘X’ é exibido o toString do objeto Calendar que for escolhido.

    Como sempre, ainda é possivel melhorar esse componente, e essa é a lista do que já pode ser feito:

  • Um Layout melhor para o JWindow.
  • Internacionalizar os textos (nome do mês, inicial do dia, botão “hoje”)
  • Poder digitar datas no formato MM/dd/yyyy




  • JTableView

    14 02 2011

    Algo que eu planejava a algum tempo é oferecer um modo onde seja possivel ter colunas em uma JTable com valores agregados como, soma, média, menor, maior e etc.., foi com essa intenção que criei as AggregateFunctions. O model que faz isso para as colunas está pronto, e é o AggregateModel.
    Mas encontrei alguns desafios não triviais para fazer isso funcionar, não existe nem na JTable nem no JScrollPane um modo de adicionar um footer, a unica maneira que pensei para resolver isso é criando uma classe que estende JPanel que tenha um JScrollPane com uma “Main Table” e outro com uma linha fixa com o model de agregação dos valores da tabela principal. Então para encapsular essa “gambiarra” criei a classe JTableView, com ela é possivel utilizar esse novo recurso. Mas ainda tem alguns poucos defeitos que ainda não consegui resolver (uma contribuição se alguem souber é bem vinda ^^).

    Para utiliza-la, é necessario instanciar o JTableView com um modelo qualquer e depois as funções que serão utilizadas nas colunas do footer.

    Como exemplo, usaremos o modelo “Person”  para exibir alguns valores e aplicaremos algumas funções nas colunas.

    Person.java

    public class Person {
    	@Resolvable(colName = "Name")
    	private String name;
    	@Resolvable(colName = "Age")
    	private int age;
    	@Resolvable(colName = "Live")
    	private boolean live;
    
    	public Person(String name, int age, boolean live) {
    		this.name = name;
    		this.age = age;
    		this.live = live;
    	}
    }
    

    Com isso criamos o modelo e adicionamos alguns dados.
    PS:Para quem não sabe como funciona o ObjectTableModel pode ver aqui.

    		ObjectTableModel model = new ObjectTableModel(
    				new AnnotationResolver(Person.class), "name,age,live");
    
    		model.setEditableDefault(true);
    
    		model.add(new Person("A", 10, true));
    		model.add(new Person("B", 20, true));
    		model.add(new Person("C", 30, false));
    		model.add(new Person("D", 40, true));
    		model.add(new Person("E", 50, true));
    

    PS: JTableView não funciona apenas com o ObjectTableModel, mas ele é o mais simples de ser utilizado em qualquer ocasião.

    Agora com o modelo pronto, só precisamos criar o JTableView e colocar as funções de agregações que queremos na tabela.

    		JTableView view = new JTableView(model);
                    //Os indices são 0 based.
    		view.getFooterModel().setFunction(0, new FuncConcat("-"));
    		view.getFooterModel().setFunction(1, new FuncSum());
    

    PS: Para saber como funciona a API de AggregateFunctions, ver em CollectionsUtils.

    Não é necessario ter uma função para todas as colunas, estas ficaram em branco.

    Agora só basta colocar esse componente em algum lugar, como em um JFrame e exibi-lo.

    		JFrame frame = new JFrame();
    		frame.getContentPane().add(view);
    
    		frame.pack();
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    

    Com isso temos o seguinte resultado.

    Ao alteramos os valores na tabela principal o novo resultado é calculado no footer.

    Como eu disse, este componente não está perfeito, ele tem suporte a re-alocação de colunas, caso voce esteja clicando no header da main table e arrastando a coluna para outro lugar.

    Mas ela não tem suporte para resize de colunas, o resultado se esticar algumas colunas vai ser bem estranho, se alguém souber como resolver será muito interessante para o projeto.
    Então ainda não indico utilizar este componente a não ser em caso que deixem a tabela fixada e sem suporte para mudar o tamanho das colunas, uma versão melhorada desse componente vai ser liberado assim que for arrumado estes erros pendentes.





    JImagePanel

    11 02 2011

    Essa classe está no projeto Towel, para utiliza-la, entre na pagina “Towel Project” e baixe a versão mais atual para adicionar ao classpath.

    JImagePanel é uma classe criada pelo ViniGodoy, é um JPanel que tem uma imagem de background no fundo e modificada por mim para ter suporte a exibir uma sequencia de imagens através do tempo.

    Com ela, é possivel criar telas em Swing com imagens de fundo e ainda ter componentes por cima, diferente do que seria possivel com um JLabel, e ela é extremamente simples de usar.

    Para utilizar:

    • Criar uma instancia com uma ou varias imagens;
    • Escolher o FillType, opcional, este é o modo de como a imagem vai se comportar quando a area disponivel for maior que ela, as opções são: RESIZE (crescer), CENTER (centralizado), SIDE_BY_SIDE (Lado a lado, assim como no Desktop do windows). Por default o FillType é RESIZE;
    • Adicionar ela a um Container, como um JFrame por exemplo;
    • Adicionar componentes sobre ela, isso é feito assim como em um JPanel;
    • Exibi-la.

    O seguinte código representa isso.

    import java.awt.Dimension;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    import javax.imageio.ImageIO;
    import javax.swing.JFrame;
    
    import com.towel.swing.img.JImagePanel;
    
    public class JImagePanelSingleTest {
    	public static void main(String[] args) throws Throwable {
    		JImagePanel panel = new JImagePanel(
    				loadImage("/home/marcos/imgs/1.png"));
    
    		JFrame frame = new JFrame();
    		frame.setPreferredSize(new Dimension(100, 100));
    		frame.add(panel);
    		frame.pack();
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    	}
    
    	private static BufferedImage loadImage(String file) throws IOException {
    		return ImageIO.read(new File(file));
    	}
    }
    

    Com isso, temos um resultado como esse:

    Se não quisermos que aumente nossa imagem, podemos usar o FillType como no seguinte código.

    public class JImagePanelSingleTest {
    	public static void main(String[] args) throws Throwable {
    		JImagePanel panel = new JImagePanel(
    				loadImage("/home/marcos/imgs/1.png"));
    
    		panel.setFillType(JImagePanel.FillType.CENTER);
    
    		JFrame frame = new JFrame();
    		frame.setPreferredSize(new Dimension(100, 100));
    		frame.add(panel);
    		frame.pack();
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    	}
    
    	private static BufferedImage loadImage(String file) throws IOException {
    		return ImageIO.read(new File(file));
    	}
    }
    

    Com isso temos o seguinte resultado:

    Para utilizar o modo em loop, basta usar o construtor que recebe um long e um array de imagens, o primeiro parametro é o tempo que levará para mudar entre uma imagem e outra em millisegundos.
    Assim que criado uma Thread é disparada para atualizar as imagens, essa Thread é daemon e não vai interferir caso a Thread principal termine.
    Também é possivel utilizar o FillType em modo de loop.

    O código é praticamente o mesmo, ficando assim:

    public class JImagePanelLoopTest {
    	public static void main(String[] args) throws Throwable {
    		JImagePanel panel = new JImagePanel(10, new BufferedImage[] {
    				loadImage("/home/marcos/imgs/1.png"),
    				loadImage("/home/marcos/imgs/2.png"),
    				loadImage("/home/marcos/imgs/3.png"),
    				loadImage("/home/marcos/imgs/4.png"),
    				loadImage("/home/marcos/imgs/5.png"),
    				loadImage("/home/marcos/imgs/6.png"),
    				loadImage("/home/marcos/imgs/7.png"),
    				loadImage("/home/marcos/imgs/8.png"),
    				loadImage("/home/marcos/imgs/9.png"),
    				loadImage("/home/marcos/imgs/10.png"),
    				loadImage("/home/marcos/imgs/11.png"),
    				loadImage("/home/marcos/imgs/12.png") });
    
    		JFrame frame = new JFrame();
    		frame.setPreferredSize(new Dimension(100, 100));
    		frame.add(panel);
    		frame.setLocationRelativeTo(null);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setVisible(true);
    	}
    
    	private static BufferedImage loadImage(String file) throws IOException {
    		return ImageIO.read(new File(file));
    	}
    }
    

    O resultado da execução é uma animação, então não é possivel colocar uma imagem aqui.

    Ainda a fazer nessa classe é o seguinte:

    • Começar a animação com um método start() por exemplo
    • Oferecer um modo para executar a animação apenas uma vez e parar em alguma imagem.




    ObjectComboBoxModel

    4 11 2010

    Essa classe está no projeto faz tempo mas nunca falei sobre ela pois ainda existe um problema que não vejo solução, ComboBox podem ser editaveis, e com esse model, não.

    Mas pra quem só deseja exibir pode ser uma grande mão na roda.

    Ele funciona da seguinte maneira, igual ao ObjectTableModel ele é paremetrizado e é necessario especificar qual o tipo do Objeto que vamos exibir na lista.

    Nesse exemplo meu model será Person.

    package model;
    
    @SuppressWarnings("unused")
    public class Person {
    	private String name;
    	private int age;
    
    	public Person(String str, double d) {
    		this.name = str;
    		this.age = (int) d;
    	}
    
    	public String toString() {
    		return "Name: " + name + " age: " + age;
    	}
    }
    

    Até ai, nada demais, só o modelo.

    Para utilizar precisamos criar um Formatter para essa classe, e o meu é o seguinte.

    package combobox;
    
    	public class PersonFormatter implements Formatter{
    		@Override
    		public Object format(Object arg0) {
    			Person p = (Person) arg0;
    			if(p == null)//No combo box o primeiro item sempre é null, para poder ficar em branco
    				return "";
    			return p.toString();
    		}
    
    		@Override
    		public String getName() {
    			return "person";
    		}
    
    		@Override
    		public Object parse(Object arg0) {
    			return null;//Nunca sera invocado
    		}
    	}
    

    Nesse caso, format recebe um objeto Person, e deve retornar uma String que seja o conteudo que deve ser exibido no JComboBox.

    E aqui como utilizar:

    package combobox;
    
    import java.util.*;
    import javax.swing.*l;
    
    import com.towel.awt.ann.*;
    import com.towel.bean.Formatter;
    import com.towel.swing.ObjectComboBoxModel;
    import model.Person;
    
    public class ComboBoxExample extends JFrame {
    	private ObjectComboBoxModel<Person> model;
    	@Action(method="showPerson")
    	private JButton button;
    
    	public ComboBoxExample(){
    		super("ComboBoxModel");
    
    		model = new ObjectComboBoxModel<Person>();
    		model.setFormatter(new PersonFormatter());
    		model.add(new Person("A",10.0));//  Igual ao ObjectTableModel
    		model.add(new Person("B",20.0));//os métodos addAll adicionam todos os elementos de uma coleção no model
    		model.add(new Person("C",30.0));//e o método setData coloca a coleção para ser os dados do model
    		model.add(new Person("D",40.0));
    		model.add(new Person("E",50.0));
    
    		JComboBox combo = new JComboBox(model);
    
    		JPanel cont = new JPanel();
    		cont.add(combo);
    		cont.add(button = new JButton("Show"));
    
    		setContentPane(cont);
    		pack();
    		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		setLocationRelativeTo(null);
    		setVisible(true);
    
    		new ActionManager(this);//Necessario para mapear @Action com o método
    	}
    
    	private void showPerson(){
    		Person p = model.getSelectedObject();
    		System.out.println(p.toString());
    	}
    
    	public static void main(String[] args) {
    		new ComboBoxExample();
    	}
    }
    

    Executando essa classe será exibida uma janela com um combo e um botao que quando pressionado mostrara na saida padrao o valor de Person.toString.

    No exemplo também está com a anotação @Action, também do projeto, para entende-la ver este artigo.





    Renderers Customizados. ObjectTableModel

    29 08 2010

    Fiz algumas atualizações no ObjectTableModel e está disponivel no site do projeto. 

    A principal modificação é que não é mais necessario ficar criando formatters pra tudo, antes era necessario criar um pra int, pra float, pra double, pra boolean e etc.. 

    Os tipos primitivos e Strings são suportados sem precisar nada mais do que “@Resolvable”. 

    Outra modificação é que agora é possivel usar TableCellRenderers com os componentes, só é necessario atribuir o Renderer a JTable(Que outra hora mostro como fazer isso). 

    Mas por enquanto, aqui vai um exemplo de como mostrar uma tabela que tenha um JCheckBox. 

    O Modelo (Sem formatters customizados para tipos que não sejam Strings) 

    public class Person {
     @Resolvable
     private String name;
     @Resolvable
     private int age;
     @Resolvable
     private boolean live;
     public Person(String name, int age, boolean live) {
      this.name = name;
      this.age = age;
      this.live = live;
     }
     public void printAttrs() {
      System.out.print("Name: " + name);
      System.out.print(" Age: " + age);
      System.out.println(" Live? " + live);
     }
    } 
    
    

     E como colocar na tabela.

    public class TableTeste {
     public static void main(String[] args) {
      AnnotationResolver resolver = new AnnotationResolver(Person.class);
      FieldResolver cols[] = resolver.resolve("name:Name,age:Age,live:Live");//Pega as colunas
      final ObjectTableModel<Person> model = new ObjectTableModel<Person>(cols);
      model.setEditableDefault(true);//Para poder modificar o valor
      //Coloca itens na tabela
      model.add(new Person("Marky", 19, true));
      model.add(new Person("Douglas", 20, true));
      model.add(new Person("Adams", 17, true));
      model.add(new Person("Vscs", 37, true));
      model.add(new Person("Pedro", 58, false));
      //Cria e mostra na tela os componentes
      JFrame frame = new JFrame("ObjectTableModel v2.7");
      frame.setContentPane(new JPanel(new BorderLayout()));
      JScrollPane pane = new JScrollPane();
      pane.setViewportView(new JTable(model));
      frame.add(pane, BorderLayout.CENTER);
      JButton show = new JButton("Show");
      show.addActionListener(new ActionListener() {
       public void actionPerformed(ActionEvent e) {
        for(Person p : model.getData())
         p.printAttrs();
       }
      });
      frame.add(show, BorderLayout.SOUTH);
      frame.setSize(400,400);
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
     }
    }

    Rodando a main do segundo vemos uma tabela onde o terceiro campo é um JCheckBox, e o botão (“show”) mostra os atributos dos objetos das linhas.

    Try it!





    ActionManager, uma nova maneira de mapear Actions para seus formularios.

    29 04 2010

    Para usar as classes do projeto acesse a pagina Towel Project desse blog para baixar o projeto.

    Bem.. essa idéia surgiu em uma conversa com o Felix (Felagund do GUJ) que também aderiu ao projeto.

    Como adoro annotations, criei mais uma para isso, a annotation Action que tem dois atributos.

    String method() default “” – Caso queira mapear um botão para simplesmente invocar um método quando pressionada só é necessario passar o nome dele nesse atributo

    Class<? extends ActionListener> listener() default ActionListener.class – Caso voce queira que um ActionListener seja chamado voce precisa passar o .class da classe que implementa ele para esse atributo.

    Aproveitei o código do artigo que fiz para o binder apenas modificando o modo que crio as ações para os botões.

    
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JCheckBox;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JTextField;
    import com.towel.awt.ann.Action;
    import com.towel.awt.ann.ActionManager;
    import com.towel.bind.Binder;
    import com.towel.bind.annotation.AnnotaddedBinder;
    import com.towel.bind.annotation.Bindable;
    import com.towel.bind.annotation.Form;
    @Form(Pessoa.class)
    public class PessoaForm extends JFrame {
     @Bindable(field = "nome")
     private JTextField nome;
     @Bindable(field = "idade", formatter = IntFormatter.class)
     private JTextField idade;
     @Bindable(field = "vivo")
     private JCheckBox vivo;
     private Binder binder;
     @Action(method = "add")//Mapeando a ação para o método add
     private JButton add;
     @Action(listener = Listener.class)//Mapeando a ação para o listener
     private JButton load;
     public PessoaForm() {
      super("PessoaForm");
      nome = new JTextField(20);
      idade = new JTextField(20);
      vivo = new JCheckBox("Vivo?");
      add = new JButton("Add");
      load = new JButton("Load");
      setLayout(new GridLayout(5, 2));
      add(new JLabel("Nome:"));
      add(nome);
      add(new JLabel("Idade:"));
      add(idade);
      add(new JLabel());// Por causa do GridLayout
      add(vivo);
      add(add);
      add(load);
      pack();
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLocationRelativeTo(null);
      binder = new AnnotaddedBinder(this);
      new ActionManager(this);// Mapeia as @Action para os JButtons.
     }
     public static void main(String[] args) {
      new PessoaForm().setVisible(true);
     }
     // Listener mapeado para o botão "load"
     private class Listener implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
       Pessoa pessoa = new Pessoa();
       pessoa.setNome("Marky");
       pessoa.setIdade(18);
       pessoa.setVivo(true);// Claro
       binder.updateView(pessoa);
      }
     }
     // Metodo mapeado para o botão "add"
     private void add() {
      Pessoa pessoa = new Pessoa();
      binder.updateModel(pessoa);
      pessoa.printAttrs();
     }
     // IntFormatter sera usado para transformar a String em numero.
     public static class IntFormatter implements mark.utils.bean.Formatter {
      public Object format(Object obj) {
       Integer d = (Integer) obj;
       return d.toString();
      }
      public Object parse(Object obj) {
       return Integer.valueOf(Integer.parseInt((String) obj));
      }
      public String getName() {
       return "int";
      }
     }
    }
    




    Binder 2.0 – Agora com Annotations!

    3 03 2010

    Olá. Demorei um tempo mas agora o Binder esta totalmente reformulado.

    Essa versão pode ser obtida baixando o jar pela pagina Towel Project.

    Para usar da antiga forma apenas instancie um NamedBinder invés de Binder em si.

    Mas agora o novo.

    Para fazer o bind de um objeto para uma tela nós precisamos indicar que a classe sera um formulario e anotar os componentes com os dados do atributo que vamos fazer a união.

    Por exemplo vou usar a classe Pessoa.

    public class Pessoa {
    	private String nome;
    	private int idade;
    	private boolean vivo;// sem ideias pra outro atributo boolean o.0
    	public void printAttrs() {// Método que imprime no console os atributos de
    		// pessoa
    		System.out.println("Nome: " + getNome());
    		System.out.println("Idade: " + getIdade());
    		System.out.println("Vivo?: " + vivo);
    	}
    	public void setNome(String nome) {
    		this.nome = nome;
    	}
    	public String getNome() {
    		return nome;
    	}
    	public void setIdade(int idade) {
    		this.idade = idade;
    	}
    	public int getIdade() {
    		return idade;
    	}
    	public boolean isVivo() {
    		return vivo;
    	}
    	public void setVivo(boolean vivo) {
    		this.vivo = vivo;
    	}
    }
    

    Usamos a anotação @Form para indicar qual classe vamos unir com este formulario e anotação @Bindable para indicar qual atributo da classe esse campo indica.

    Parametros do @Bindable
    String field: que indica o atributo da classe. (parametro obrigatorio)
    Class handler: forma que sera acessado o campo (opcional, default é FieldHandler)
    Class formatter: formatter para o campo, caso precise ser formatado ou transformado para outro tipo de atributo se não String (opcional, default é o DefaultFormatter)

    Agora no formulario.

    import java.awt.GridLayout;
    import java.awt.event.*;
    import javax.swing.*;
    import com.towel.bean.Formatter;
    import com.towel.bind.Binder;
    import com.towel.bind.annotation.*;
    @Form(Pessoa.class)
    // Form para Pessoa
    public class PessoaForm extends JFrame {
    	@Bindable(field = "nome")
    	private JTextField nome;
    	@Bindable(field = "idade", formatter = IntFormatter.class)
    	private JTextField idade;
    	@Bindable(field = "vivo")
    	// o Binder assume que o JCheckBox trata-se de um atributo boolean
    	private JCheckBox vivo;
    	private Binder binder;
    	public PessoaForm() {
    		super("PessoaForm");
    		nome = new JTextField(20);
    		idade = new JTextField(20);
    		vivo = new JCheckBox("Vivo?");
    		JButton add = new JButton("Add");
    		JButton load = new JButton("Load");
    		setLayout(new GridLayout(4, 2));
    		add(new JLabel("Nome:"));
    		add(nome);
    		add(new JLabel("Idade:"));
    		add(idade);
    		add(new JLabel());// Por causa do GridLayout
    		add(vivo);
    		add(add);
    		add(load);
    		load.addActionListener(new ActionListener() {
    			public void actionPerformed(ActionEvent evt) {
    				Pessoa pessoa = new Pessoa();
    				pessoa.setNome("Marky");
    				pessoa.setIdade(18);
    				pessoa.setVivo(true);// Claro
    				binder.updateView(pessoa);
    			}
    		});
    		add.addActionListener(new ActionListener() {
    			public void actionPerformed(ActionEvent evt) {
    				Pessoa pessoa = new Pessoa();
    				binder.updateModel(pessoa);
    				pessoa.printAttrs();
    			}
    		});
    		pack();
    		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		setLocationRelativeTo(null);
    		binder = new AnnotatedBinder(this);
    	}
    	public static void main(String[] args) {
    		new PessoaForm().setVisible(true);
    	}
    	// IntFormatter sera usado para transformar a String em numero.
    	public static class IntFormatter implements Formatter {
    		public Object format(Object obj) {
    			Integer d = (Integer) obj;
    			return d.toString();
    		}
    		public Object parse(Object obj) {
    			return Integer.valueOf(Integer.parseInt((String) obj));
    		}
    		public String getName() {
    			return "int";
    		}
    	}
    }

    Repare que rodando este código aparecera uma tela com estes campos e ao apertar o botão load as informações do objeto pessoa sera mostrado nos campos. E o método add coloca os valores dos campos no objeto e imprime o valor dos atributos.

    Repare que não foi necessario varios sets para atualizar o objeto nem gets para atualizar a View.

    Em breve vou unir um Validator no Binder para garantir a consistencia dos dados.

    Até mais.





    Binder – Atualizando objetos facilmente.

    29 07 2009

    Algo comum em programação Swing é ter métodos para atualizar objetos e a view como o seguinte.

    public void atualizarObjeto(Pessoa x){
    x.setNome(nomeField.getText());
    x.setCpf(cpfField.getText());
    x.setIdade(Integer.parseInt(idadeField.getText()));
    }
    
    public void atualizarObjeto(Pessoa x){
    nomeField.setText(x.getNome());
    cpfField.setText(x.getCpf());
    idadeField.setText(x.getIdade());
    }
    

    Nesse exemplo temos apenas 3 atributos mas ao adicionar mais um vemos a necessidade de colocar mais uma linha em cada um desses métodos. Com o tempo esse processo começa a ficar no minimo chato.
    Pensando nisso criei a classe Binder.

    PS: Para usar precisa baixar os fontes ou o jar na pagina Towel Project.

    Considere para o exemplo a seguinte classe:

    public class Person {
    	private String name;
    	private int age;
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	public Person getParent() {
    		return parent;
    	}
    
    	public void setParent(Person parent) {
    		this.parent = parent;
    	}
    }
    

    E um IntFormatter:

    public 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);
    	}
    }
    

    “[name:nomeDoAtributo] [fmt:formatter][pfx:prefixo][dflt:valorDefault]“

    Onde.
    nomeDoAtributo: nome do atributo na classe.
    formatter (Opcional): deve ser igual ao retornado pelo método getName do Formatter.
    prefixo (Opcional): quando precisamos na mesma tela ter mais de um Binder(Para dois tipos de objetos diferentes). Esse nome deve ser passado no construtor do Binder.
    valorDefault (Opcional): no caso em que o valor no objeto for null ou vazio esse valor é assumido para a view.

    Todos os parametros são colocados no name dos JComponents.

    Exemplo funcional.

    
    import com.towel.bind.Binder;
    
    public class BinderTest extends JFrame {
    	private JTextField nomeField, idadeField;
    	private JButton button;
    	private Binder binder;
    
    	public BinderTest() {
    		super("Binder");
    		nomeField = new JTextField(10);
    		idadeField = new JTextField(10);
    		button = new JButton("Run");
    
    		JPanel content = new JPanel(new FlowLayout());
    		content.add(new JLabel("Nome:"));
    		content.add(nomeField);
    		content.add(new JLabel("Idade:"));
    		content.add(idadeField);
    		content.add(button);
    
    		nomeField.setName("[name:name]");
    		idadeField.setName("[name:age][fmt:int]");
    
    		setContentPane(content);
    
    		binder = new Binder(this, Person.class, new IntFormatter());
    
    		button.addActionListener(new ActionListener() {
    			@Override
    			public void actionPerformed(ActionEvent arg0) {
    				Person person = new Person();
    				try {
    					binder.updateModel(person);
    				} catch (Exception e) {
    					// Um formatter pode lançar uma RuntimeException.
    				}
    				System.out.println(person.getName());
    				System.out.println(person.getAge());
    
    				Person newOne = new Person();
    				binder.updateView(newOne);
    			}
    		});
    
    		pack();
    		setLocationRelativeTo(null);
    		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		setVisible(true);
    	}
    
    	public static void main(String[] args) {
    		new BinderTest();
    	}
    }
    

    Ao clicar no botão podemos ver o que digitamos no console pois após binder.updateModel temos os valores no objeto.
    Após isso os campos ficam limpos pois é chamado binder.updateView com um novo Person e esses campos ainda estão vazios.

    O construtor com pfx seria como o seguinte.

    binder = new Binder("prefixoUsado", this, Person.class, new IntFormatter());
    

    Mas teriamos que colocar esse atributo em cada componente que queiramos.

    Binder usa um método recursivo para pegar os componentes, então não se preocupe em alinhar seus componentes em varios JPanels.ributo





    SelectTable

    2 06 2009

    Telas de seleção padronizadas.

    Olá. Outro problema comum em desenvolvimento Swing é o fato das telas de consultas. Na maioria das vezes temos que criar muitas telas que fazem praticamente a mesma coisa. Então decedi implementar uma tela tão genérica quando o TableModel que descrevi no post anterior.

    Primeiro: baixar o código fonte do projeto na página Towel Project desse blog.

    Vou usar a classe Person como exemplo novamente.

    import com.towel.el.annotation.Resolvable;
    public class Person {
    	@Resolvable(colName = "Name)
    	private String name;
    	@Resolvable(colName = "Age", formatter = IntFormatter.class)
    	private int age;
    	private Person parent;
    	public Person(String name, int age) {
    		this(name, age, null);
    	}
    	public Person(String name, int age, String bd, Person parent) {
    		this.name = name;
    		this.age = age;
    		this.parent = parent;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	public Person getParent() {
    		return parent;
    	}
    	public void setParent(Person parent) {
    		this.parent = parent;
    	}
    	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);
    	}
    }
    }
    

    Se voce não sabe pra que serve a anotação @Resolvable sugiro ver no meu post anterior sobre o ObjectTableModel.

    Agora para nossa tela de seleção.

    Precisamos apenas do seguinte código para ter uma tela para seleção.

    	FieldResolver[] cols = new AnnotationResolver(Person.class)
    		.resolve("name:Nome,age:Idade,parent.name:Pai");
    	SelectTable st =new SelectTable(cols,getData());
    	st.addObjectSelectListener(new ObjectSelectListener() {
    		@Override
    		public void notifyObjectSelected(SelectEvent selectevent) {
    				Person p = (Person) selectevent.getObject();
    				System.out.println(p.getName());
    				System.out.println(p.getAge());
    			}
    	});
    	st.showSelectTable();
    

    Com esse código temos uma tela como a seguinte.
    Exemplo SelectTable

    Algumas vantagens dessa tabela:

    • Paginação e navegação pelos resultados evitando scrolls.
    • Ordenação da pagina clicando nos nomes das colunas.
    • Busca “on-the-fly” pelo atributo da coluna clicada.
    • Botão search filtra o resultado.

    Após um duplo clique em um resultado ou então selecionar pelo botão um SelectEvent sera disparado ao Listener que foi adicionado para a tela.
    No caso ele só vai imprimir o nome e a idade da pessoa. Mas em uma aplicação real o uso poderia ser muito mais eficiente.

    Para alterar o nome dos botões existem os métodos
    setSearchButtonText
    setSelectButtonText
    setCloseButtonText

    Que alteram o nome dos botoes search, select e close respectivamente.

    Para alterar o titulo da janela ao invés de mostrarmos a tabela com st.showSelectTable podemos passar uma String como parametro que será o nome da janela.

    Também existem casos que nosso resultado não está em forma de List.
    Na verdade o construtor que recebe um List como argumento apenas encapsula em uma implentação de com.towel.collections.paginator.Paginator feita para Lists.

    Mas voce pode implementar o seu próprio e receber dados de qualquer forma.

    Caso precisem receber uma lista como resultado é necessario passar outro argumento para o construtor.

    SelectTable st = new SelectTable(cols,
    				new ListPaginator(getData(), 20), SelectTable.LIST);
    

    Note também o uso do ListPaginator também que dividi a lista em varias com no maximo 20 resultados em cada.

    E nese caso é possivel fazer um cast do selectevent.getObject() para List.

    As vezes nossa intenção não é abrir uma nova janela. Para isso existe o método getContent da SelectTable que retorna um JPanel com todo o conteudo para ser adicionado onde necessario.





    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 = &quot;Name&quot;)
        private String name;
        @Resolvable(colName = &quot;Age&quot;)
        private int age;
        @Resolvable(colName = &quot;Lives&quot;) //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&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(
                    resolver, &quot;name,age,live&quot;);
    	//Here we use the list to be the data of the table.
            tableModel.setData(getData());
            JTable table = new JTable(tableModel);
            JFrame frame = new JFrame(&quot;ObjectTableModel&quot;);
            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&lt;Person&gt; getData() {
            List&lt;Person&gt; list = new ArrayList&lt;Person&gt;();
            list.add(new Person(&quot;Marky&quot;, 17, new Person(&quot;Marcos&quot;, 40)));
            list.add(new Person(&quot;Jhonny&quot;, 21));
            list.add(new Person(&quot;Douglas&quot;, 50, new Person(&quot;Adams&quot;, 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&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(
                    resolver, &quot;name,age,parent.name,parent.age&quot;);
    

    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&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(
       resolver, &quot;name:Person Name,age:Person Age,parent.name:Parent Name,parent.age:Parent Age&quot;);
    

    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=&quot;America/Sao_Paulo&quot;,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(&quot;dd/MM/yyyy&quot;);
        @Override
        public String format(Object obj) {
            Calendar cal = (Calendar) obj;
            return formatter.format(cal.getTime());
        }
        @Override
        public String getName() {
            return &quot;calendar&quot;;
        }
        @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&lt;Person&gt; model = new ObjectTableModel&lt;Person&gt;(new AnnotationResolver(Person.class).resolve(&quot;name,age&quot;));
    	Person person1 = new Person(&quot;Marky&quot;, 17);
    	Person person2 = new Person(&quot;MarkyAmeba&quot;, 18);
    	model.add(person1);
    	model.add(person2);
    	List&lt;Person&gt; list = new ArrayList&lt;Person&gt;();
    	list.add(new Person(&quot;Marcos&quot;, 40));
    	list.add(new Person(&quot;Rita&quot;, 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 = &quot;Name&quot;)
    	private String name;
    	@Resolvable(colName = &quot;Age&quot;, 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 &quot;int&quot;;
    	}
    	@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&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(resolver,				&quot;name,age,parent.name:Parent,parent.age:Parent age&quot;);
    		tableModel.setData(getData());
    		tableModel.setEditableDefault(true);
    		JTable table = new JTable(tableModel);
    		JFrame frame = new JFrame(&quot;ObjectTableModel&quot;);
    		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&lt;Person&gt; getData() {
    		List&lt;Person&gt; list = new ArrayList&lt;Person&gt;();
    		list.add(new Person(&quot;Marky&quot;, 17, new Person(&quot;Marcos&quot;, 40)));
    		list.add(new Person(&quot;Jhonny&quot;, 21, new Person(&quot;&quot;,0)));
    		list.add(new Person(&quot;Douglas&quot;, 50, new Person(&quot;Adams&quot;,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&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(resolver, &quot;name,age,parent.name:Parent,parent.age:Parent age&quot;);
    		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, &quot;name&quot;);
    		FieldResolver ageResolver = new FieldResolver(Person.class, &quot;age&quot;);
    		ageResolver.setFormatter(new IntFormatter());
    		FieldResolver parentNameResolver = new FieldResolver(Person.class,&quot;parent.name&quot;, &quot;Parent&quot;);
    		FieldResolver parentAgeResolver = new FieldResolver(Person.class,&quot;parent.age&quot;, &quot;Parent age&quot;);
    		FieldResolver birthResolver = new FieldResolver(Person.class, &quot;birth&quot;,&quot;Birth day&quot;);
    		birthResolver.setFormatter(new CalendarFormatter());
    		ObjectTableModel&lt;Person&gt; model = new ObjectTableModel&lt;Person&gt;(
    				new FieldResolver[] { nameResolver, ageResolver, parentNameResolver, parentAgeResolver, birthResolver });
    

    É equivalente ao seguinte.

    		AnnotationResolver resolver = new AnnotationResolver(Person.class);
    		ObjectTableModel&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(
    				resolver, &quot;name,age,parent.name:Parent,parent.age:Parent age,birth: Birth day&quot;);
    		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(&quot;name&quot;);
    		FieldResolver ageRslvr = fac.createResolver(&quot;age&quot;, new IntFormatter());
    		FieldResolver parentNameRslvr = fac.createResolver(&quot;paren.name&quot;,&quot;Parent&quot;);
    		FieldResolver parentAgeRslvr = fac.createResolver(&quot;parent.age&quot;,&quot;Parent age&quot;, new IntFormatter());
    		FieldResolver birthRslvr = fac.createResolver(&quot;birth&quot;, &quot;Birth day&quot;,	new CalendarFormatter());
    		ObjectTableModel&lt;Person&gt; model = new ObjectTableModel&lt;Person&gt;(
    				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 = &quot;Name&quot;, accessMethod = MethodHandler.class)
    	private String name;
    	@Resolvable(colName = &quot;Age&quot;, formatter = IntFormatter.class)
    	private int age;
    	public Person(String name, int age) {
    		this.name = name;
    		this.age = age;
    	}
    	public String getName() {
    		return &quot;The name is: &quot; + 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&lt;Person&gt; tableModel = new ObjectTableModel&lt;Person&gt;(resolver,	&quot;name,age&quot;);
    		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.








    Seguir

    Obtenha todo post novo entregue na sua caixa de entrada.

    Junte-se a 150 outros seguidores