Dúvida com JTable e AbstractTableModel [ RESOLVIDO ]

Salve galera do GUJ!
Estou trabalhando com JTable em minha aplicação, li alguns posts sobre AbstractTableModel indicados pelo ViniGodoy em sua campanha “morte ao DefaultTableModel”, peguei um deles como exemplo para o meu modelo http://www.guj.com.br/java/225793-exemplo-de-tablemodel#1156821.
Agora tenho algumas dúvidas:
1- Eu tenho um String[] colunas com os nomes das minhas colunas. Como fazer com que esses nomes apareçam na primeira linha de minha tabela(titulo das colunas)?
2- Quando eu carrego a tabela com os dados e dou 2 clicks para editar a célula, quando clico fora para finalizar a edição, aquele valor é copiado para todas as células a direita(na mesma linha) é um comportamento muito bizarro. Por que acontece e como resolver?
3- Como definir a largura das colunas? usei o método abaixo, mas ele não obedece a minha vontade(deve ser revoltadinho, coitado).

Quem puder me ajudar com qualquer dúvida ficarei muito agradecido. Abaixo segue minha classe AbstractTableModel.

[code]public class ClienteTableModel extends AbstractTableModel {
private ArrayList linhas;
private String[] colunas = new String[] { “CODIGO”, “NOME”, “RG”, “CPF”, “TELEFONE”, “AVALIACAO” };

public ClienteTableModel() {
	linhas = new ArrayList<Clientes>();
}

public ClienteTableModel(ArrayList<Clientes> lista) {
	linhas = new ArrayList<Clientes>(lista);
}

/* Retorna a quantidade de colunas. */
@Override
public int getColumnCount() {
	// Está retornando o tamanho do array "colunas".
	return colunas.length;
}

/* Retorna o nome da coluna no índice especificado.
 * Este método é usado pela JTable para saber o texto do cabeçalho. */
@Override
public String getColumnName(int columnIndex) {
	// Retorna o conteúdo do Array que possui o nome das colunas
	// no índice especificado.
	return colunas[columnIndex];
}

/* Retorna a classe dos elementos da coluna especificada.
 * Este método é usado pela JTable na hora de definir o editor da célula. */
@Override
public Class<?> getColumnClass(int columnIndex) {
	return String.class;
}

/* Retorna a quantidade de linhas. */
@Override
public int getRowCount() {
	// Retorna o tamanho da lista de Clientes.
	return linhas.size();
}

/* Retorna o valor da celula especificada pelos indices da  
 * linha e da coluna.*/
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
	Clientes cliente = linhas.get(rowIndex);

	// Retorna o campo referente a coluna especificada.
	// Aqui é feito um switch para verificar qual é a coluna
	// e retornar o campo adequado. As colunas são as mesmas
	// que foram especificadas no array "colunas".
	switch (columnIndex) {
	case 0: // Primeira coluna é o codigo.
		return cliente.getCodigo();
	case 1: // Segunda coluna é o nome.
		return cliente.getNome();
	case 2: // Terceira coluna é o RG.
		return cliente.getRG();
	case 3: // Querta coluna é o CPF.
		return cliente.getCPF();
	case 4: // Quinta coluna é o telefone.
		return cliente.getTelefone();
	case 5: // Sexta coluna é a avaliacao.
		return cliente.getAvaliacao();
	default:
		// Se o índice da coluna não for válido, lança um
		// IndexOutOfBoundsException (Exceção de índice fora dos limites).
		// Não foi necessário verificar se o índice da linha é inválido,
		// pois o próprio ArrayList lança a exceção caso seja inválido.
		throw new IndexOutOfBoundsException("columnIndex out of bounds");
	}
}

@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex){
	//pega o produto da linha
	Clientes cliente = linhas.get(rowIndex);
	
	//verifica qual valor vai ser alterado
	switch (columnIndex) {
	case 0: // Primeira coluna é o codigo.
		cliente.setCodigo(Long.parseLong(aValue.toString()));
	case 1: // Segunda coluna é o nome.
		cliente.setNome(aValue.toString());
	case 2: // Terceira coluna é o RG.
		cliente.setRG(aValue.toString());
	case 3: // Querta coluna é o CPF.
		cliente.setCPF(Long.parseLong(aValue.toString()));
	case 4: // Quinta coluna é o telefone.
		cliente.setTelefone(Long.parseLong(aValue.toString()));
	case 5: // Sexta coluna é a avaliacao.
		cliente.setAvaliacao(aValue.toString().charAt(0));
	}
	
	//avisa que os dados mudaram
    fireTableDataChanged();
}

@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
    //no nosso caso todas vão ser editáveis, entao retorna true pra todas
    return true;
}


public void setLinhas(ArrayList<Clientes> linhas) {
	this.linhas = linhas;
}	

}[/code]

1 - Sua JTable está dentro de um JScrollPane? Se não estiver, os títulos das colunas não serão exibidos.

2 - Seu método setValueAt() não tem break’s após cada case, o que faz com que o valor seja propagado inadequadamente;

3 - Creio que, ao incluir a JTable num JScrollPane, este problema será resolvido. Experimente usar valores diferentes em JTable.setAutoResizeMode() para personalizar o redimensionamento das colunas.

Ola Roger
2- foi resolvido

1- ainda não, escrevi o codigo abaixo mas a tabela não aparece dentro do scrollpane

[code]private ClienteTableModel modelo = new ClienteTableModel();
private JTable tabela = new JTable(modelo);
private JScrollPane scrollPane = new JScrollPane(tabela);

// no construtor
scrollPane.setBounds(68, 100, 650, 300);
this.add(scrollPane, BorderLayout.CENTER);

tabela.setBounds(68, 100, 640, 290);
this.add(tabela, BorderLayout.CENTER);[/code]

As duas últimas linhas são desnecessárias, basta adicionar o ScrollPane ao formulário.

comentei elas, o titulo apareceu direitinho, porem a as linhas com os dados não aparecem mais.

Estranhíssimo. Segue um exemplo para você se basear.

[code]import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;

public class TesteJTable {
private static void criaFrame() {
JFrame frame = new JFrame(“Teste JTable”);
frame.setSize(600, 600);
frame.setLocation(50, 50);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());

DefaultTableModel tableModel = new DefaultTableModel(
  new Object[] { "Coluna 1", "Coluna 2" }, 0);
tableModel.addRow(new Object[] { "Linha 1, Coluna 1", "Linha 1, Coluna 2" });
tableModel.addRow(new Object[] { "Linha 2, Coluna 1", "Linha 2, Coluna 2" });
JTable tabela = new JTable(tableModel);

JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(tabela);

frame.add(scrollPane, BorderLayout.CENTER);

frame.setVisible(true);

}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override public void run() {
criaFrame();
}});
}
}
[/code]

ops

Cara agradeço imensamente a sua boa vontade de estar me ajudando.

Seu exemplo usa DefaultTableModel, será que o fato de minha classe usar AbstractTableModel esta interferindo em alguma configuração pré-definida da JTable?

Estou deixando o código da classe que contem a JTable e o ScrollPane;

[code]public class EditarClientes extends JPanel implements ActionListener{
private static final long serialVersionUID = 1L;
private JLabel titulo = new JLabel(“Edicao de Clientes”);
private JLabel fundo = new JLabel();
private JLabel textoCodigo0 = new JLabel(“Codigo”);
private JLabel textoNome0 = new JLabel(“Nome”);
private JTextField campoCodigo0 = new JTextField();
private JTextField campoNome0 = new JTextField();
private JButton pesquisar = new JButton(“Pesquisar”);

private ClienteTableModel modelo = new ClienteTableModel();
private JTable tabela = new JTable(modelo);
private JScrollPane scrollPane = new JScrollPane(tabela);

public EditarClientes(){
	setLayout(new BorderLayout());
	
	titulo.setFont(new Font("Serif", Font.BOLD, 30));
	titulo.setBounds(240, 20, 310, 40);
	this.add(titulo, BorderLayout.CENTER);
	
	int a=180, b=70;
	textoCodigo0.setBounds(a + 0, b + 0, 50, 20);
	this.add(textoCodigo0, BorderLayout.CENTER);
	
	campoCodigo0.setBounds(a + 55, b + 0, 40, 20);
	this.add(campoCodigo0, BorderLayout.CENTER);
	
	textoNome0.setBounds(a + 120, b + 0, 40, 20);
	this.add(textoNome0, BorderLayout.CENTER);
	
	campoNome0.setBounds(a + 165, b + 0, 150, 20);
	this.add(campoNome0, BorderLayout.CENTER);
	
	pesquisar.setBounds(a + 320, b + 0, 105, 20);
	this.add(pesquisar, BorderLayout.CENTER);
	pesquisar.addActionListener(this);
	
	scrollPane.setBounds(68, 100, 650, 100);
	this.add(scrollPane, BorderLayout.CENTER);
	
	this.add(fundo);
}

public void actionPerformed(ActionEvent e) {
	if(e.getSource() == pesquisar){
		String sql = "SELECT * FROM Clientes";// WHERE CODIGO = "+ campoCodigo0.getText();
		
		ArrayList<Clientes> linhas = new ArrayList<Clientes>();
		
		linhas = MysqlConnection.consultaClientes(sql);
		
		modelo.setLinhas(linhas);
		//scrollPane.repaint();
		//tabela.repaint();
		this.repaint();
		
	}

}[/code]

fuçando na minha aplicação descobri algo bizarro. Quando eu mexo na tabulação de alguma coluna(para aumenta ou diminuir a largura) "magicamente " os dados aparecem, o que me pareceu falta de um .repaint, porém quando faço isso, não funciona.
Quando faço uma nova pesquisa não atualiza a tabela, mantendo os valores antigos.

[code]public void actionPerformed(ActionEvent e) {
if(e.getSource() == pesquisar){
String sql = “SELECT * FROM Clientes”;// WHERE CODIGO = "+ campoCodigo0.getText();
ArrayList linhas = new ArrayList();
linhas = MysqlConnection.consultaClientes(sql);
modelo.setLinhas(linhas);

              // repaints
		scrollPane.repaint();
		tabela.repaint();
		this.repaint();
		
	}[/code]

[quote=BeR]fuçando na minha aplicação descobri algo bizarro. Quando eu mexo na tabulação de alguma coluna(para aumenta ou diminuir a largura) "magicamente " os dados aparecem, o que me pareceu falta de um .repaint, porém quando faço isso, não funciona.
Quando faço uma nova pesquisa não atualiza a tabela, mantendo os valores antigos.[/quote]
Está faltando avisar para a JTable que o conteúdo do model foi modificado no método setLinhas:

public void setLinhas(ArrayList<Clientes> linhas) { this.linhas = linhas; fireTableDataChanged(); }
E o layout está setado como BorderLayout, então não precisa do setBounds, e somente um componente deve ser adicionado na posição Center.

Eric, a solução é essa mesma.

Não entendo, pois se eu não definir um setBounds o componente não aparecera, e caso eu faça apenas um setSize o componente ficara na posição 0,0 do meu JPanel.

Você poderia explicar melhor essa afirmação. Desconheço essa prática.

Quero parabenizar toda a comunidade GUJ que são sempre prestativos, em especial ao Roger e Eric que me ajudaram de tão boa vontade…valeu GUJ!

Não entendo, pois se eu não definir um setBounds o componente não aparecera, e caso eu faça apenas um setSize o componente ficara na posição 0,0 do meu JPanel.[/quote]
Acontece que o setBounds serve para definir posição e tamanho do componente em questão. Se setarmos o layout como “null”, precisamos especificar a posição e o tamanho pelo setBounds. Mas no seu caso, quem tem esta função é o BorderLayout, portanto se o seu código é quem está chamando o método setBounds, significa que algo não está do jeito que deveria.

Outro ponto importante a ser considerado é que usar setBounds e null layout pode causar problemas quando executados em diferentes LookAndFeel’s e diferentes resoluções de monitor. Aqui tem um exemplo bem básico do que pode ocorrer: http://devsv.wordpress.com/2010/12/07/layout-manager-i-null/

Você poderia explicar melhor essa afirmação. Desconheço essa prática.[/quote]
Resumidamente, o BorderLayout possui cinco áreas para posicionar os componentes (CENTER, NORTH, SOUTH, WEST e EAST), onde em cada uma delas se posiciona um único componente. Se precisarmos posicionar mais de um componente no centro, poderíamos, por exemplo, criar um painel com vários componentes e adicionar este painel na posição CENTER.

Sobre BorderLayout:
http://download.oracle.com/javase/tutorial/uiswing/layout/border.html
http://devsv.wordpress.com/2011/01/05/layout-manager-iii-border/

Para o seu caso, sugiro que considere a utilização de um LayoutManager diferente, como FlowLayout ou um que seja baseado em grid. Se estiver fazendo as telas na mão, recomendo o uso do MigLayout.

Outra coisa. Sua lista de clientes está especificada pelo tipo “ArrayList”. Você pode deixá-la com o tipo “List”, desta forma, quem for passar os dados para o seu modelo não fica limitado a passar um “ArrayList”, e sim qualquer implementação da interface “List”:

[code]public class ClienteTableModel extends AbstractTableModel {
private List linhas;

public ClienteTableModel(List<Clientes> lista) {  
	linhas = new ArrayList<Clientes>(lista);  
}  
. . .

public void setLinhas(List<Clientes> linhas) {
	this.linhas = linhas;
}[/code]

Acho que cheguei no tópico tarde demais. Mas entro aqui para reforçar o moto de “Morte ao DefaultTableModel”, e para ler futuros problemas, caso existam.

[quote=Eric Yuzo]Outra coisa. Sua lista de clientes está especificada pelo tipo “ArrayList”. Você pode deixá-la com o tipo “List”, desta forma, quem for passar os dados para o seu modelo não fica limitado a passar um “ArrayList”, e sim qualquer implementação da interface “List”:

[code]public class ClienteTableModel extends AbstractTableModel {
private List linhas;

public ClienteTableModel(List<Clientes> lista) {  
	linhas = new ArrayList<Clientes>(lista);  
}  
. . .

public void setLinhas(List<Clientes> linhas) {
	this.linhas = linhas;
}[/code][/quote]

Em todos os exemplos que li os códigos usavam o List, porém não entedia o porque, mas agora ta explicado.

Gostei muito de suas explicações, depois de ler todos os links estranhamente começo a tocar uma musiquinha como essa: http://bit.ly/rcM3xw e apareceu uma flecha para cima , em cima de minha cabeça. Coisa estranha!.

ViniGodoy você será sempre bem vindo!

Pessoal, eu sei que o tópico é um pouco antigo, mas surgiu uma dúvida. Caso eu tenha um atributo boolean em Cliente, e na tabela eu deseje representar Apto(no lugar de true) ou Bloqueado(no lugar de false), como eu devo proceder?

Basta retornar esses valores no getValueAt. Deixe o tipo da coluna também como String.class

if (columnIndex == COL_ESTADO) { return cliente.isApto() ? "Apto" : "Bloqueado"; }

Valeu Vini Godoy! Funcionou direitinho ^^

[quote=ViniGodoy]Basta retornar esses valores no getValueAt. Deixe o tipo da coluna também como String.class

if (columnIndex == COL_ST) { return cliente.isApto() ? "Apto" : "Bloqueado"; }[/quote]

tenho esse codigo;

[code] public String getColumnName(int column) {
//qual o nome da coluna
if (column == COL_NM) {
return “-”;
} else if (column == COL_CHK){
return “Nomes”;
}

    return "";[/code]

porém os nomes não aparecem nas colunas;
tem algo errado?

Sua tabela está num JScrollpane?