quinta-feira, 26 de julho de 2012

Crie seu próprio TableModel 3 - Edição em células

Olá pessoal!
Desculpa tanta demora para continuar o material, mas aqui estou.
Dando continuidade aos posts anteriores, vamos aprender como implementar edição em células do JTable.
Desta forma, podemos modificar nossos registros diretamente no table.

Inicialmente, vamos acrescentar dois novos métodos em nossa classe FuncionarioTableModel.


@Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        Funcionario f = listaFuncionario.get(rowIndex);
        switch (columnIndex) {
            case 0:
                f.setNome(aValue.toString());
                break;
            case 1:
                f.setIdade(Integer.parseInt(aValue.toString()));
                break;
            case 2:
                f.setDepartamento(aValue.toString());
                break;
            case 3:
                f.setCargo(aValue.toString());
                break;
        }
        fireTableCellUpdated(rowIndex, columnIndex);
    }

O método isCellEditable determina quais células serão editadas na table. Neste exemplo todas as células serão editáveis. Mas, vamos imaginar que gostaríamos de editar somente a primeira coluna. Dessa forma iremos ter algo semelhante a isto: 



@Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return  columnIndex == 0;
    }


Tudo depende de sua necessidade.
Sabendo que a table somente cuida da parte visual, temos de setar os valores editados em nosso objeto Funcionario após a edição. Isso trás a necessidade da implementação do método setValueAt. Não há mistério com o entendimento deste método, basta ter em mente que cada linha corresponde a um index da List e que cada coluna representa um atributo do objeto contido na List. E não se esqueça  do disparo fireTableCellUpdated para garantir a renderização da célula editada.
Pronto!
Agora você já tem um JTable com as células editáveis. Fácil né?!
O que há de mais legal nisso é que a validação do valor já é feito de forma automática. Tente editar a idade colocando um valor diferente de Integer. Além de não permitir ainda destaca de vermelho! Isto acontece porque no nosso TableModel determinamos que a coluna idade seria do tipo Integer no método getColumnClass.
Mas não seria melhor se a idade fosse editada em um JSpinner e o departamento e o cargo em um JComboBox?
Assim sendo, lhes apresento a classe DefaultCellEditor.
Acrescente as seguintes linhas na classe TableModelTeste:


this.tableFuncionario.getColumn("Cargo").setCellEditor(
     new DefaultCellEditor(new JComboBox(
          new String[]{"Desenvolvedor", "Estagiario", "Gerente de Projetos", "Auxiliar Administrativo", "Contador", "Supervisor"})));
this.tableFuncionario.getColumn("Departamento").setCellEditor(
     new DefaultCellEditor(new JComboBox(
          new String[]{"Centro Tecnologico", "Recepcao", "Administracao", "RH"})));

A classe DefaultCellEditor já tem em seu construtor três possíveis componentes para edição: JTextField, JCheckBox e JComboBox. Basta adiciona-los sem mais delongas que todo o "trabalho sujo" será feito. Agora vamos fazer nosso próprio CellEditor com o JSpinner.



import java.awt.Component;
import javax.swing.AbstractCellEditor;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerNumberModel;
import javax.swing.table.TableCellEditor;

public class IdadeCellEditor  extends AbstractCellEditor implements TableCellEditor{

    private JSpinner spinner;
    private Funcionario funcionario;
    public IdadeCellEditor() {
    }
 
    @Override
    public Object getCellEditorValue() {
        return spinner.getValue();
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        spinner  = new JSpinner(new SpinnerNumberModel(0, 0, null, 1));
        spinner.setValue(value);
        funcionario = ((FuncionarioTableModel)table.getModel()).getFuncionario(row);
        return spinner;
    }

    @Override
    public boolean stopCellEditing() {
        funcionario.setIdade(Integer.parseInt(spinner.getValue().toString()));
        return super.stopCellEditing();
    }
}


Com isto podemos editar a célula usando o JSpinner.
O método getCellEditorValue retorna o novo valor após a edição para renderização. No método getTableCellEditorComponent definimos qual o componente vai ser apresentado para edição, no nosso caso, o JSpinner. O spinner é iniciado com o valor da célula e temos que recuperar também o funcionário para que possamos em seguida atualizar o objeto da lista. No método stopCellEditing setamos o novo valor para o funcionário em questão.
Para utilizar o nosso CellEditor criado, acrescentamos na classe TableModelTeste:

this.tableFuncionario.setDefaultEditor(Integer.class, new IdadeCellEditor());

Com isto, agora podemos editar a idade com o spinner.
Concluímos aqui este material. Ainda existe uma série de coisas que podemos fazer com o JTable. Ainda podemos brincar com a renderização do objeto. Por exemplo, podemos colocar imagens nas células.
Caso tenham curiosidade de como fazer isso, só deixar comentário.
Espero que isso seja de grande ajuda e até a próxima.

quarta-feira, 6 de junho de 2012

Crie seu próprio TableModel 2

Olá pessoal.
Anteriormente vimos qual a vantagem de se implementar nosso próprio TableModel. Como dito, iremos agora fazer alguma melhorias em nosso exemplo. Hoje vamos implementar as operações básicas (adicionar ,editar, excluir) em nossa tabela.
Inicialmente vamos adicionar o código abaixo no método FuncionarioTableModel.

public void adicionar(Funcionario funcionario){
        listaFuncionario.add(funcionario);
        this.fireTableRowsInserted(listaFuncionario.size() - 1, listaFuncionario.size() - 1);
    }
 
    public void excluir(int row){
        listaFuncionario.remove(row);
        this.fireTableRowsDeleted(row, row);
    }
 
    public void atualizar(int row, Funcionario funcionario){
        listaFuncionario.set(row, funcionario);
        this.fireTableRowsUpdated(row, row);
    }
 
    public Funcionario getFuncionario(int row){
        return listaFuncionario.get(row);
    }

Note que nos métodos adicionar, excluir e atualizar existe um disparo de métodos do AbstractTableModel. Isto se faz necessário para forçar que a renderização seja refeita naquela linha para aquela operação. Se não existirem estes disparos, o item pode ficar visualmente no seu estado antigo porém na lista do model estará o item modificado.

A classe TableModelTeste sofreu várias alterações como segue:

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import net.miginfocom.swing.MigLayout;

public class TableModelTeste extends JFrame implements ActionListener {

    private JTable tableFuncionario;
    private JScrollPane scrollPane;
    private List<Funcionario> listaFuncionarios;
    private JTextField nome;
    private JTextField idade;
    private JComboBox departamento;
    private JComboBox cargo;
    private JPanel panel;
    private JButton novo;
    private JButton salvar;
    private JButton excluir;
    private boolean nv = true;

    public TableModelTeste() {
        init();
    }

    final void init() {
        this.setTitle("Exemplo de TableModel");
        //populando a lista para inserir no table
        this.listaFuncionarios = new LinkedList<Funcionario>();
        this.listaFuncionarios.add(new Funcionario("Marlon Meneses", 26, "Centro Tecnologico", "Desenvolvedor"));
        this.listaFuncionarios.add(new Funcionario("Fulano Teste", 21, "Centro Tecnologico", "Estagiario"));
        this.listaFuncionarios.add(new Funcionario("Siclano Anonimo", 32, "Recepcao", "Auxiliar Administrativo"));
        this.listaFuncionarios.add(new Funcionario("Beltrano Nao Sei O Nome", 30, "Centro Tecnologico", "Gerente de Projetos"));
        //adicionando no table
        this.tableFuncionario = new JTable(new FuncionarioTableModel(this.listaFuncionarios));
        this.tableFuncionario.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        this.tableFuncionario.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                listaMouseClick(e);
            }
        });
        this.scrollPane = new JScrollPane(this.tableFuncionario);

        this.nome = new JTextField();
        this.idade = new JTextField();
        this.departamento = new JComboBox(new String[]{"Centro Tecnologico", "Recepcao", "Administracao", "RH"});
        this.cargo = new JComboBox(new String[]{"Desenvolvedor", "Estagiario", "Gerente de Projetos", "Auxiliar Administrativo", "Contador", "Supervisor"});

        this.novo = new JButton("Novo");
        this.novo.addActionListener(this);
        this.salvar = new JButton("Salvar");
        this.salvar.addActionListener(this);
        this.excluir = new JButton("Excluir");
        this.excluir.addActionListener(this);


        this.panel = new JPanel(new MigLayout());
        this.panel.add(new JLabel("Nome"));
        this.panel.add(nome, "pushx, growx");

        this.panel.add(new JLabel("Idade"), "gap 10");
        this.panel.add(idade, "pushx, growx, wrap");

        this.panel.add(new JLabel("Departamento"));
        this.panel.add(departamento, "growx");
        this.panel.add(new JLabel("Cargo"), "gap 10");
        this.panel.add(cargo, "growx, wrap");

        this.panel.add(novo, "skip, split 3");
        this.panel.add(salvar);
        this.panel.add(excluir);

        this.setLayout(new BorderLayout());
        this.add(this.scrollPane, BorderLayout.CENTER);
        this.add(this.panel, BorderLayout.PAGE_END);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();
        this.setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                new TableModelTeste().setVisible(true);
            }
        });
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource().equals(novo)) {
            resetForm();
            nv = true;
        } else if (e.getSource().equals(salvar)) {
            try {
                if (nv) {
                    ((FuncionarioTableModel) tableFuncionario.getModel()).adicionar(new Funcionario(nome.getText(),
                            Integer.parseInt(idade.getText()), departamento.getSelectedItem().toString(), cargo.getSelectedItem().toString()));
                    resetForm();
                } else {
                    ((FuncionarioTableModel) tableFuncionario.getModel()).atualizar(tableFuncionario.getSelectedRow(), new Funcionario(nome.getText(),
                            Integer.parseInt(idade.getText()), departamento.getSelectedItem().toString(), cargo.getSelectedItem().toString()));
                    resetForm();
                }
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(this, "Erro ao realizar operacao");
                ex.printStackTrace();
            }
        } else if (e.getSource().equals(excluir)) {
            try {
                ((FuncionarioTableModel) tableFuncionario.getModel()).excluir(tableFuncionario.getSelectedRow());
                resetForm();
            } catch (IndexOutOfBoundsException i) {
                JOptionPane.showMessageDialog(this, "Nenhum item selecionado");
            }
        }
    }

    private void resetForm() {
        this.nome.setText("");
        this.idade.setText("");
        this.departamento.setSelectedIndex(0);
        this.cargo.setSelectedIndex(0);
        nv = true;
    }

    private void listaMouseClick(MouseEvent evt) {
        nv = false;
        Funcionario f = ((FuncionarioTableModel) tableFuncionario.getModel()).getFuncionario(tableFuncionario.getSelectedRow());
        nome.setText(f.getNome());
        idade.setText(f.getIdade().toString());
        cargo.setSelectedItem(f.getCargo());
        departamento.setSelectedItem(f.getDepartamento());
    }
}

Provavelmente você deve ter notado algo novo: MigLayout.
MigLayout é uma framework que facilita a criação de interfaces gráficas através de simples configurações, economizando várias linhas de código e simplificando o entendimento. Para este exemplo, faça o download da API no site http://www.miglayout.com/.
Futuramente aprenderemos um pouco mais sobre esta framework.
Finalmente, observe atentamente os nossos 4 novos métodos em funcionamento. Estes são incomparavelmente mais simples e diretos do que quando se usa DefaultTableModel, isto derruba todas as suas dúvidas quanto a vantagem de se criar um TableModel.
No próximo post iremos continuar com nosso exemplo fazendo com que seja possível editar o item na própria célula da tabela.
Att.

terça-feira, 5 de junho de 2012

Crie o seu próprio TableModel

Pessoal...
Este é o meu primeiro post (aeee...). E vou logo começando com algo que percebo que causa muito nó na cabeça de alguns programadores: Por que não utiliza DefaultTableModel?
Aqui vai a resposta: o código fica confuso e mais difícil de manter; não representa o objeto em si, havendo a necessidade de duplicação de objetos (o objeto propriamente dito, e o que representará na lista); força com que seja apresentado o id (ou algo único que identifique o objeto) dentre vários outros que podemos citar.
Todo os problemas citados acima e até mesmo os que não me lembrei são solucionados quando implementamos nosso próprio TableModel.
Para este exemplo, vamos cria um table com informações de funcionários (nome, idade, departamento, cargo).

Primeiro vamos criar nossa classe Funcionario:

public class Funcionario {
    private String nome;
    private Integer idade;
    private String departamento;
    private String cargo;

    public Funcionario() {
    }

    public Funcionario(String nome, Integer idade, String departamento, String cargo) {
        this.nome = nome;
        this.idade = idade;
        this.departamento = departamento;
        this.cargo = cargo;
    }

    public String getCargo() {
        return cargo;
    }

    public void setCargo(String cargo) {
        this.cargo = cargo;
    }

    public String getDepartamento() {
        return departamento;
    }

    public void setDepartamento(String departamento) {
        this.departamento = departamento;
    }

    public Integer getIdade() {
        return idade;
    }

    public void setIdade(Integer idade) {
        this.idade = idade;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }
}

Vamos ao o que nos interessa de verdade:

import java.util.LinkedList;
import java.util.List;
import javax.swing.table.AbstractTableModel;

public class FuncionarioTableModel extends AbstractTableModel {
    //nome da coluna da table
    private final String[] colunas = new String[]{"Nome", "Idade", "Departamento", "Cargo"};
    //lista para a manipulacao do objeto
    private List<Funcionario> listaFuncionario;

    public FuncionarioTableModel() {
        listaFuncionario = new LinkedList<Funcionario>();
    }

    public FuncionarioTableModel(List<Funcionario> listaFuncionario) {
        this.listaFuncionario = listaFuncionario;
    }
 
    //numero de linhas
    public int getRowCount() {
        return listaFuncionario.size();
    }

    //numero de colunas
    public int getColumnCount() {
        return colunas.length;
    }

    //define o que cada coluna conterá do objeto
    public Object getValueAt(int rowIndex, int columnIndex) {
        Funcionario f = listaFuncionario.get(rowIndex);
        switch(columnIndex){
            case 0:
                return f.getNome();
            case 1:
                return f.getIdade();
            case 2:
                return f.getDepartamento();
            case 3:
                return f.getCargo();
            default:
                return null;
        }
    }

    //determina o nome das colunas
    @Override
    public String getColumnName(int column) {
        return colunas[column];
    }

    //determina que tipo de objeto cada coluna irá suportar
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        switch(columnIndex){
        case 0:
            return String.class;
        case 1:
            return Integer.class;
        case 2:
            return String.class;
        case 3:
            return String.class;
        default:
            return null;
        }
    }
}

Os métodos descritos acima são os necessários para se criar um TableModel básico. Podemos criar também métodos para adicionar, excluir e editar itens diretamente do table (vamos aprimorar isto no próximo post). Observe que neste método, o modelo irá suportar o próprio objeto Funcionario, o que não iria acontecer seu fosse usado DefaultTableModel.

E para finalizar:

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;

public class TableModelTeste extends JFrame {

    private JTable tableFuncionario;
    private JScrollPane scrollPane;
 
    private List<Funcionario> listaFuncionarios;
 
    public TableModelTeste() {
        init();
    }

    final void init() {
        this.setTitle("Exemplo de TableModel");
        //populando a lista para inserir no table
        this.listaFuncionarios = new LinkedList<Funcionario>();
        this.listaFuncionarios.add(new Funcionario("Marlon Meneses", 26, "Centro Tecnologico", "Desenvolvedor"));
        this.listaFuncionarios.add(new Funcionario("Fulano Teste", 19, "Centro Tecnologico", "Estagiario"));
        this.listaFuncionarios.add(new Funcionario("Siclano Anonimo", 32, "Recepcao", "Auxiliar Administrativo"));
        this.listaFuncionarios.add(new Funcionario("Beltrano Nao Sei O Nome", 30, "Centro Tecnologico", "Gerente de Projetos"));
        //adicionando no table
        this.tableFuncionario = new JTable(new FuncionarioTableModel(this.listaFuncionarios));
        this.scrollPane = new JScrollPane(this.tableFuncionario);
     
        this.setLayout(new BorderLayout());
        this.add(this.scrollPane);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();
        this.setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                new TableModelTeste().setVisible(true);
            }
        });
    }
}

Pronto!
Pode até parecer, mas não é difícil.
No próximo post vamos aprimorar este exemplo.
Até a próxima