Como inserir JCheckBox em apenas algumas linhas do JTable?

Pessoal, já li alguns tutoriais e posts aqui do fórum, mas ainda não consegui desenhar checkboxes nas linhas pares da primeira coluna da tabela. As malditas aparecem em todas as linhas.
Meu model:

[code]private class ResultadosTableModel extends AbstractTableModel {
private Object[][] dados;
private String[] cabecalho;

    public ResultadosTableModel(Object[][] dados, String[] cabecalho) {
        this.dados = dados;
        this.cabecalho = cabecalho;
    }

    public int getRowCount() {
        return dados.length;
    }

    public int getColumnCount() {
        return cabecalho.length;
    }
    
    @Override
    public String getColumnName(int col) {
        return cabecalho[col];
    }

    public Object getValueAt(int row, int col) {
        return dados[row][col];
    }

    @Override
    public Class<?> getColumnClass(int col) {
        Object o = getValueAt(0, col);
        return o == null ? null : getValueAt(0, col).getClass();
    }

    @Override
    public boolean isCellEditable(int row, int col) {
        return col == 0 && row % 2 == 0; // Só linhas pares da primeira coluna
    }

    @Override
    public void setValueAt(Object value, int row, int col) {
        dados[row][col] = value;
        fireTableCellUpdated(row, col);
    }
}[/code]

Coloquei o Boolean apenas nas linhas pares (os outros dados já foram inseridos):

for (int i = 0; i < dados.length; i++) { if (i % 2 == 0) dados[i][0] = new Boolean(true); }

Obrigado.

ja tentou um parênteses?

        @Override   
        public boolean isCellEditable(int row, int col) {   
            return col == 0 && ( row % 2 == 0 ); // Só linhas pares da primeira coluna   
        }

e que objetos vc está passando para linhas ímpares? Tente passar Strings vazias como teste, se der certo depois vc vê o que fazer.

for (int i = 0; i < dados.length; i++) {  
             if (i % 2 == 0)  
                 dados[i][0] = new Boolean(true);  
             else
                 dados[i][0] = "";  

} 

qq coisa posta aí.

Não influencia… além do mais, essa linha só define se é editável ou não :slight_smile:

Nenhum, estou deixando o default (null). Com String, lança ClassCastException.

Não influencia… além do mais, essa linha só define se é editável ou não :slight_smile:
[/quote]

Verdade viajei legal. Mas, pra fazer aparecer checkBox na table nao tem q mexer com CellRenderer nao? nao vi nada disso no codigo.

O que você quer que apareça nas linhas ímpares?

Acho que você terá que fazer o seu próprio CustomCellEditor e CustomCellRenderer. E daí xunxar um pouquinho as coisas.

  1. Faça um CustomCellRenderer que:
    1.1. Teste o tipo do objeto sendo editado.
    1.2. Se for um Boolean, usa o CellRenderer de Boolean.
    1.3. Caso contrário, usa o DefaultCellRenderer;

  2. Faça o mesmo para o CellEditor;

  3. Altere o CellEditor e o CellRenderer da coluna um para os criados acima;

  4. Faça o seu TableModel retornar um Boolean para as colunas com CheckBox e uma string vazia para as sem. Também faça ele retornar que as linhas sem o checkbox não são editáveis.

Tudo isso soa mais difícil do que parece.

Nas linhas ímpares não deve aparecer nada. Lembrando que isso é apenas para a primeira coluna, nas demais aparecerão String, Long, Double, etc.

Do 1 ao 3 vou ter que olhar essas classes que não conheço.

Como fazer isso? Assim, não vai.

public Object getValueAt(int row, int col) { if (col == 0 && row % 2 != 0) return ""; // ClassCastException return dados[row][col]; }
Retornar null continua desenhando as checkbox. O não-editável nas ímpares já está funcionando.

Obrigado.

Vai continuar dando ClassCastException enquanto você não alterar o Renderer e o Editor. Eles é que tentam fazer cast do valor das colunas Boolean para o tipo Boolean. Quando você fizer o seu próprio Renderer, você só vai delegar para o Renderer de boolean default se o tipo do valor for efetivamente boolean. Caso contrário, você vai delegar para o DefaultRenderer, que trata strings. O mesmo pro editor.

É isso? Como delegar para o Renderer de boolean default?
Outra coisa: eu adiciono ele e o CellEditor apenas na primeira coluna ou como default da tabela?

private class ResultadosTableRenderer implements TableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { if (value instanceof Boolean) { // return ??? } return table.getCellRenderer(row, col).getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); } };

Se for Boolean, você retorna uma instância de

Que vai te retornar um objeto dessa classe aqui:

[code]
static class BooleanRenderer extends JCheckBox implements TableCellRenderer, UIResource
{
private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

public BooleanRenderer() {
    super();
    setHorizontalAlignment(JLabel.CENTER);
        setBorderPainted(true);
}

    public Component getTableCellRendererComponent(JTable table, Object value,
					       boolean isSelected, boolean hasFocus, int row, int column) {
    if (isSelected) {
        setForeground(table.getSelectionForeground());
        super.setBackground(table.getSelectionBackground());
    }
    else {
        setForeground(table.getForeground());
        setBackground(table.getBackground());
    }
        setSelected((value != null && ((Boolean)value).booleanValue()));

        if (hasFocus) {
            setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
        } else {
            setBorder(noFocusBorder);
        }

        return this;
    }
}[/code]

O seu código está certo.

Adicione o renderer só para a coluna. Pensando um pouco, é provável que você nem tenha que fazer a mesma coisa para o CellEditor.
Basta definir o CellEditor de Boolean para a coluna também. Afinal, as colunas sem Booleans dentro não são editáveis. E o CellEditor só entra em ação quando o usuário consegue editar a célula.

Nem tanto… quando não era Boolean e estava na coluna zero, ele chamava o próprio Renderer recursivamente até estourar a pilha :slight_smile:
Usando o Renderer default de String (aparentemente) deu certo.

[code]
private class ResultadosTableRenderer implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
if (value instanceof Boolean)
return table.getDefaultRenderer(Boolean.class).getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);

    return table.getDefaultRenderer(String.class).getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
}

};[/code]
Alguns pontos:
1- Não defini CellEditor.
2- Acho meio tosco um clique em qualquer local da célula ativar o checkbox. Tem algum jeito simples de resolver isso ou vou precisar escutar eventos de mouse? Em caso contrário, posso deixar a coluna estreita para isso não ficar muito perceptível.
3- Para definir ações dos checkboxes, é preciso implementar TableModelListener?

É, sou meio leigo em Swing :mrgreen:

Obrigado de novo.

Beleza.

Bem, esse é o comportamento padrão da maior parte das tabelas com checks.
Não tem um jeito simples de fazer isso, e checar eventos do mouse não é uma boa também. Você pode deixar a coluna mais estreita usando o método setWidth do TableColumn (a JTable, q eu me lembre, tem um método para retornar o TableColumn. Na pior das hipóteses, consulte o TableColumnModel dela).

Via de regra, não é uma boa prática alterar o comportamento padrão dos componentes do Swing, a menos que existem ganchos especificamente para isso. Além de complicado, é sujeito a erros, difícil de manter e ainda não respeita os vários look&feels (e se for respeitar, vai ser mais difícil ainda).

Que tipo de ações? Se forem ações que atuem sobre os próprios dados do model, não, você pode fazer isso no model, desde que propague o evento TableRowChanged na linha em questão. Caso contrário, sim, implemente o listener. Uma outra alternativa é implementar um dos listeners da própria JTable.

Viajei.
Não preciso de ação alguma. Na verdade, só preciso posteriormente saber quais são as linhas checadas.

for (int i = 0; i < model.getRowCount(); i += 2) { model.getValueAt(i, 0); // codigo }

O único problema que restou é que eu gostaria que algumas células em colunas Double ficassem vazias. Antes, bastava eu setar a célula null, mas, desde que implementei meu próprio TableModel, isso lança um NullPointerException.

Bom, voltei a mexer na classe e encontrei o erro. O getColumnClass() retornava null no caso de null. Resolvi retornar String.
Como ficou:

public Class<?> getColumnClass(int col) { Object o = getValueAt(0, col); return o == null ? String.class : getValueAt(0, col).getClass(); }

Obrigado pelas respostas.