JTable + DefaultTableModel + Imagem em Cell
Boa noite pessoal,
Gostaria de poder colocar uma imagem dentro de uma determinada célula de um JTable. Eu determinaria a mesma pela posição na linha e coluna e adicionaria uma imagem. Acredito que a mesma seria colocada dentro de um JLabel.
A minha idéia é fazer, por exemplo:
__________________________________
Gerente | V
__________________________________
Supervisor | X
__________________________________
Sendo que a segunda coluna teria na célula[1,2] uma imagem de V e na célula[2,2] uma imagem de um X. Este é apenas um exemplo simples.
Já tenho implementado o seguinte:
Gostaria de apartir do que eu já tenho fazer a referida implementação das imagens no JTable. Desde já agradeço qualquer ajuda neste sentido.
Abraão
Gostaria de poder colocar uma imagem dentro de uma determinada célula de um JTable. Eu determinaria a mesma pela posição na linha e coluna e adicionaria uma imagem. Acredito que a mesma seria colocada dentro de um JLabel.
A minha idéia é fazer, por exemplo:
__________________________________
Gerente | V
__________________________________
Supervisor | X
__________________________________
Sendo que a segunda coluna teria na célula[1,2] uma imagem de V e na célula[2,2] uma imagem de um X. Este é apenas um exemplo simples.
Já tenho implementado o seguinte:
class JComponentTableCellRendererP implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return (JComponent) value;
}
}
public class Principal extends javax.swing.JFrame {
(...)
//----------------------------------------------------------------------------//
private String headers_registro[] = { "Evento", "Status" };
private final Object rows_registro[][] = { { " ", " " }, { " ", " " }, { " ", " " }};
public DefaultTableCellRenderer renderer_registro = null;
private Border headerBorder_registro = null;
private TableCellRenderer cellrenderer_registro = new JComponentTableCellRendererP();
public TableColumnModel columnModel_registro = null;
private TableColumn column0_registro = null;
private TableColumn column1_registro = null;
private JLabel evento_registroLabel = null;
private JLabel status_registroLabel = null;
private DefaultTableModel modelo_registro = new DefaultTableModel(rows_registro, headers_registro){
@Override
public boolean isCellEditable(int row, int col){
return false;
}
};
//----------------------------------------------------------------------------//
(...)
private void popularTabelaRegistro(){
String[] dados = null;
dados = new String[2];
//-------------- ELIMINANDO TOTALMENTE TODAS AS LINHAS DA TABELA -------------//
modelo_registro.getDataVector().removeAllElements();
//----------------------------------------------------------------------------//
dados[0] = "Programa Inicializado"; // Evento
dados[1] = dt.obterHora() + " - " + dt.obterData(); // Status
modelo_registro.addRow(dados);
//----------------------- CENTRALIZANDO OS DADOS NO JTABLE -------------------//
renderer_registro.setHorizontalAlignment(JLabel.CENTER);
//----------------------------------------------------------------------------//
(...)
Gostaria de apartir do que eu já tenho fazer a referida implementação das imagens no JTable. Desde já agradeço qualquer ajuda neste sentido.
Abraão
Abraão Monteiro
Curtidas 0
Respostas
Anthony Accioly
25/05/2011
Olá Abraão,
O approach tradicional para o que você está tentando fazer é escrever um TableCellRenderer (como você começou a implementar) e associá-lo à coluna através do método setCellRenderer.
Eis um exemplo:
http://www.java2s.com/Code/Java/Swing-JFC/RenderinganimageinaJTablecollumn.htm
Veja que você pode fazer um if em cima do parâmetro value para determinar qual imagem usar.
Um outro approach "espertinho" é usar html com os componentes swing e mandar ver no uso da tag <img>: http://forum.codecall.net/java-tutorials/4418-html-swing-components.html
Qualquer coisa estamos aí.
O approach tradicional para o que você está tentando fazer é escrever um TableCellRenderer (como você começou a implementar) e associá-lo à coluna através do método setCellRenderer.
Eis um exemplo:
http://www.java2s.com/Code/Java/Swing-JFC/RenderinganimageinaJTablecollumn.htm
Veja que você pode fazer um if em cima do parâmetro value para determinar qual imagem usar.
Um outro approach "espertinho" é usar html com os componentes swing e mandar ver no uso da tag <img>: http://forum.codecall.net/java-tutorials/4418-html-swing-components.html
Qualquer coisa estamos aí.
GOSTEI 0
Abraão Monteiro
25/05/2011
Boa noite Anthony Accioly,
Baseado no link eu implementei algo assim:
Eu gostaria de ao longo do projeto ir adicionando tipo o número da linha, a coluna, o caminho da imagem e o texto da célula e em seguida mandar renderizar a mesma. Eu teria que colocar estes quatro parâmetros em uma matriz que fosse dinamicamente expandida na vertical e em seguida chamar novamente a classe? Estou chamando a classe acima uma única vez da seguinte forma:
Teria alguma idéia ou opinião. Desde já agradeço!
Baseado no link eu implementei algo assim:
class RenderizaCelula extends DefaultTableCellRenderer {
// Diretório do JRE
private File diretorio1 = new File("C:\\Arquivos de programas\\Java");
// Diretório de Gerencimento do Banco de Dados (SQLyog Enterprise)
private File diretorio2 = new File("C:\\Arquivos de programas\\SQLyog Enterprise");
// Diretório da localização física do Banco de Dados
private File diretorio3 = new File("C:\\Arquivos de programas\\MySQL\\MySQL Server 5.0\\data\\sagitarius");
// Diretório da localização física dos arquivos do Programa
private File diretorio4 = new File("C:\\Sagitarius");
// Diretório da localização física do log do Programa
private File diretorio5 = new File("C:\\Sagitarius\\LOG_GUI");
// Diretório da localização física do backup do banco de dados
private File diretorio6 = new File("C:\\Sagitarius\\Backup");
public RenderizaCelula(){
// TRABALHAR OS PARÂMETROS DE ENTRADA E MELHORAR O METODO ABAIXO
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,boolean hasFocus, int row, int column) {
if ((row==1)&&(column==1)&&(table==Principal.tabela_registro_sistema))
return(preencherCelula("C:\\Sagitarius\\usuario.PNG"," [ GERENTE ]"));
else if ((row==2)&&(column==1)&&(table==Principal.tabela_registro_sistema))
return(preencherCelula("C:\\Sagitarius\\checagem_inicial.PNG",""));
else if ((row==3)&&(column==1)&&(table==Principal.tabela_registro_sistema)){
if(diretorio1.exists())
return(preencherCelula("C:\\Sagitarius\\setando_certo.PNG",""));
else
return(preencherCelula("C:\\Sagitarius\\setando_errado.PNG",""));
}else if ((row==4)&&(column==1)&&(table==Principal.tabela_registro_sistema)){
if(diretorio2.exists())
return(preencherCelula("C:\\Sagitarius\\setando_certo.PNG",""));
else
return(preencherCelula("C:\\Sagitarius\\setando_errado.PNG",""));
}else if ((row==5)&&(column==1)&&(table==Principal.tabela_registro_sistema)){
if(diretorio3.exists())
return(preencherCelula("C:\\Sagitarius\\setando_certo.PNG",""));
else
return(preencherCelula("C:\\Sagitarius\\setando_errado.PNG",""));
}else if ((row==6)&&(column==1)&&(table==Principal.tabela_registro_sistema)){
if(diretorio4.exists())
return(preencherCelula("C:\\Sagitarius\\setando_certo.PNG",""));
else
return(preencherCelula("C:\\Sagitarius\\setando_errado.PNG",""));
}else if ((row==7)&&(column==1)&&(table==Principal.tabela_registro_sistema)){
if(diretorio5.exists())
return(preencherCelula("C:\\Sagitarius\\setando_certo.PNG",""));
else
return(preencherCelula("C:\\Sagitarius\\setando_errado.PNG",""));
}else if ((row==8)&&(column==1)&&(table==Principal.tabela_registro_sistema)){
if(diretorio6.exists())
return(preencherCelula("C:\\Sagitarius\\setando_certo.PNG",""));
else
return(preencherCelula("C:\\Sagitarius\\setando_errado.PNG",""));
}
else
return(preencherCelula("",(String) value));
}
private Component preencherCelula(String caminho_imagem, String texto){
JLabel imagem_label = new JLabel();
ImageIcon imagem = null;
imagem_label.setText(texto);
imagem_label.setHorizontalAlignment(JLabel.CENTER);
imagem_label.setVerticalAlignment(JLabel.CENTER);
if (!caminho_imagem.isEmpty()){
imagem = new ImageIcon(caminho_imagem);
imagem_label.setIcon(imagem);
}
return(imagem_label);
}
// The following methods override the defaults for performance reasons
// + Fonte: http://www.exampledepot.com/egs/javax.swing.table/CustRend.html
@Override
public void validate() {}
@Override
public void revalidate() {}
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {}
@Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {}
}
//----------------------------------------------------------------------------//
Eu gostaria de ao longo do projeto ir adicionando tipo o número da linha, a coluna, o caminho da imagem e o texto da célula e em seguida mandar renderizar a mesma. Eu teria que colocar estes quatro parâmetros em uma matriz que fosse dinamicamente expandida na vertical e em seguida chamar novamente a classe? Estou chamando a classe acima uma única vez da seguinte forma:
// Renderiza as celulas do corpo da JTable column1_registro.setCellRenderer(new RenderizaCelula()); column0_registro.setCellRenderer(new RenderizaCelula());
Teria alguma idéia ou opinião. Desde já agradeço!
GOSTEI 0
Anthony Accioly
25/05/2011
A coluna que tem a imagem vai conter texto? Não daria para separar uma coluna para a imagem e outra para o texto?
Eu eliminaria todos esses ifs posicionais e leria o caminho da imagem direto do valor da célula.
Exemplo:
Ou ainda, como minha sugestão inicial. Escolher a imagem dinamicamente a partir do valor. Vamos dizer por exemplo que sua célula contivesse um boolean. true exibe a imagem para certo e false exibe a imagem para errado.
Código genérico é sempre preferível a código posicional / caso a caso. Desse jeito você só muda o valor do campo na coleção / matriz / TableModel associado à tabela.
Finalmente, eu sou contra chumbar caminhos absolutos para imagens. Prefiro colocar elas no Classpath e ler através do método getResource. Por exemplo, coloque as imagens no projeto dentro do diretório imagens. Daí abra como no exemplo.
Por que isso? Imagine que seu software vai rodar em Linux, ou mesmo em um computador Windows cujo HD está mapeado em D:. É sempre melhor gerar um arquivo jar auto-contido com caminhos relativos do que depender de recursos externos.
Abraços,
Eu eliminaria todos esses ifs posicionais e leria o caminho da imagem direto do valor da célula.
Exemplo:
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
String caminho = (String) value;
JLabel lbl = new JLabel();
ImageIcon icon = new ImageIcon(caminho);
lbl.setHorizontalAlignment(JLabel.CENTER);
lbl.setVerticalAlignment(JLabel.CENTER);
lbl.setIcon(icon);
return lbl;
}
Ou ainda, como minha sugestão inicial. Escolher a imagem dinamicamente a partir do valor. Vamos dizer por exemplo que sua célula contivesse um boolean. true exibe a imagem para certo e false exibe a imagem para errado.
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
Boolean correto = (Boolean) value;
JLabel lbl = new JLabel();
ImageIcon icon = null;
if (corrreto) {
icon = new ImageIcon("C:\\Sagitarius\\setando_certo.PNG");
} else {
icon = new ImageIcon("C:\\Sagitarius\\setando_errado.PNG");
}
lbl.setHorizontalAlignment(JLabel.CENTER);
lbl.setVerticalAlignment(JLabel.CENTER);
lbl.setIcon(icon);
return lbl;
}
Código genérico é sempre preferível a código posicional / caso a caso. Desse jeito você só muda o valor do campo na coleção / matriz / TableModel associado à tabela.
Finalmente, eu sou contra chumbar caminhos absolutos para imagens. Prefiro colocar elas no Classpath e ler através do método getResource. Por exemplo, coloque as imagens no projeto dentro do diretório imagens. Daí abra como no exemplo.
ImageIcon icon = new ImageIcon(getClass().getResource("/imagens/setando_certo.PNG"));Por que isso? Imagine que seu software vai rodar em Linux, ou mesmo em um computador Windows cujo HD está mapeado em D:. É sempre melhor gerar um arquivo jar auto-contido com caminhos relativos do que depender de recursos externos.
Abraços,
GOSTEI 0
Abraão Monteiro
25/05/2011
Então, em alguns momentos a coluna vai conter somente texto e em
algum momento vai ter texto e imagem. Eu estava pensando em fazer algo
do tipo. Eu envio:
Sendo:
linha -> Row do JTable
coluna -> Column do JTable
caminho_imagem -> Seria o Classpath com a imagem específica e variada para cada linha
texto -> O texto que estaria ao lado da imagem dentro da célula
E este seria setado na renderização do JTable. Não sei se monto uma matriz de string com cada parâmetro e passo para o construtor da classe RenderizaCelula. Tipo:
Como informação complementar, eu escrevo na JTable utilizando:
Ao longo do programa alguns eventos são disparados e com o objeto modelo_registro eu acrescento novas linhas. Com a parametrização acima (1) eu gostaria de poder ao adicionar uma nova linha ter a opção de renderizar a célula com uma imagem ou não, dependendo da situação. Sinceramente eu estou um pouco perdido, não sei se cheguei a esclarecer e passar a idéia correta para você. Qualquer coisa, na medida do possível, posta novamente que eu tento explicar melhor. Agradeço a atenção.
RenderizaCelula(linha,coluna,caminho_imagem,texto); (1)
Sendo:
linha -> Row do JTable
coluna -> Column do JTable
caminho_imagem -> Seria o Classpath com a imagem específica e variada para cada linha
texto -> O texto que estaria ao lado da imagem dentro da célula
E este seria setado na renderização do JTable. Não sei se monto uma matriz de string com cada parâmetro e passo para o construtor da classe RenderizaCelula. Tipo:
RenderizaCelula(String dados_registro[][][][]); // Acredito que ficaria um pouco estranho para escrever
Como informação complementar, eu escrevo na JTable utilizando:
private DefaultTableModel modelo_registro = new DefaultTableModel(rows_registro, headers_registro){
@Override
public boolean isCellEditable(int row, int col){
return false;
}
};
public String[] dados = new String[2];
dados[0] = "Programa Inicializado"; // Evento
dados[1] = dt.obterHora() + " - " + dt.obterData(); // Status
modelo_registro.addRow(dados);
Ao longo do programa alguns eventos são disparados e com o objeto modelo_registro eu acrescento novas linhas. Com a parametrização acima (1) eu gostaria de poder ao adicionar uma nova linha ter a opção de renderizar a célula com uma imagem ou não, dependendo da situação. Sinceramente eu estou um pouco perdido, não sei se cheguei a esclarecer e passar a idéia correta para você. Qualquer coisa, na medida do possível, posta novamente que eu tento explicar melhor. Agradeço a atenção.
GOSTEI 0
Anthony Accioly
25/05/2011
Acho que eu entendi seu requerimento.
De qualquer forma, eu não usaria uma matriz para isso.
Se você está tomando o tempo para escrever um TableModel, por que não usar, por exemplo, uma lista de objetos complexos? Cada entrada na lista é uma linha, e cada atributo do objeto uma coluna.
Por exemplo:
E você alimenta seu TableModel com uma
Dessa forma você consegue aquela solução genérica apontada acima para renderizar os valores da coleção. Para fazer uma linha nova aparecer na tabela, basta que você adicione um item novo na coleção entendeu?
Eu estou insistindo para você que essa abordagem posicional não é indicada. O fato de você precisar de uma matriz de quatro dimensões reflete isso.
Se você precisa tanto de texto quanto da descrição na mesma coluna, então pode fazer uma lógica em cima do objeto ImagemVO.
Seu método para inserir uma linha na tabela seria algo assim:
Você também pode atualizar os dados da lista e indicar para a tabela que ela deve ser atualizada:
Outra coisa, você está sofrendo bastante para reinventar a roda de uma boa solução. Já que o problema deixou de ser trivial, eu recomendo fortemente o uso de uma biblioteca como:
http://www.glazedlists.com
http://kenai.com/projects/betterbeansbinding/pages/Home
http://java.net/projects/binding/
Com essas bibliotecas você não vai precisar se preocupar em escrever o TableModel, apenas configurar o binding entre a lista e a tabela. As bibliotecas também se encarregam de manter os dados da tabela sincronizados com a lista e vice-versa.
Abraços,
De qualquer forma, eu não usaria uma matriz para isso.
Se você está tomando o tempo para escrever um TableModel, por que não usar, por exemplo, uma lista de objetos complexos? Cada entrada na lista é uma linha, e cada atributo do objeto uma coluna.
Por exemplo:
public class FuncionarioVO {
private Integer id;
private String nome;
private ImagemVO imagem;
//... Getters e Setters
}
public class ImagemVO {
private String caminho;
private String descricao;
//... Getters e Setters
}
E você alimenta seu TableModel com uma
List<FuncionarioVO> dados;
Dessa forma você consegue aquela solução genérica apontada acima para renderizar os valores da coleção. Para fazer uma linha nova aparecer na tabela, basta que você adicione um item novo na coleção entendeu?
Eu estou insistindo para você que essa abordagem posicional não é indicada. O fato de você precisar de uma matriz de quatro dimensões reflete isso.
Se você precisa tanto de texto quanto da descrição na mesma coluna, então pode fazer uma lógica em cima do objeto ImagemVO.
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
ImagemVO imagemVO = (ImagemVO) value;
JLabel lbl = new JLabel();
if (imagemVO.getCaminho() != null) {
ImageIcon icon = new ImageIcon(imagemVO.getCaminho());
lbl.setIcon(icon);
}
if (imagemVO.getDescricao() != null) {
lbl.setText(imagemVO.getDescricao());
}
lbl.setHorizontalAlignment(JLabel.CENTER);
lbl.setVerticalAlignment(JLabel.CENTER);
return lbl;
}
Seu método para inserir uma linha na tabela seria algo assim:
void renderizaCelula(FuncionarioVO funcionario) {
dados.add(funcionario);
}
Você também pode atualizar os dados da lista e indicar para a tabela que ela deve ser atualizada:
tableModel.fireTableDataChanged();
Outra coisa, você está sofrendo bastante para reinventar a roda de uma boa solução. Já que o problema deixou de ser trivial, eu recomendo fortemente o uso de uma biblioteca como:
http://www.glazedlists.com
http://kenai.com/projects/betterbeansbinding/pages/Home
http://java.net/projects/binding/
Com essas bibliotecas você não vai precisar se preocupar em escrever o TableModel, apenas configurar o binding entre a lista e a tabela. As bibliotecas também se encarregam de manter os dados da tabela sincronizados com a lista e vice-versa.
Abraços,
GOSTEI 0
Abraão Monteiro
25/05/2011
Blz, agora deu para clarear as idéias aqui. Só gostaria que na medida do possível, você me explicasse o que esta passagem que está dentro do método getTableCellRendererComponent() irá fazer:
Estarei este final de semana colocando em prática todas estas idéias. Já dei uma olhada também na Glazed List e achei bem interessante, posteriormente vou ler com bastante calma a documentação. Agradeço a atenção.
ImagemVO imagemVO = (ImagemVO) value;
Estarei este final de semana colocando em prática todas estas idéias. Já dei uma olhada também na Glazed List e achei bem interessante, posteriormente vou ler com bastante calma a documentação. Agradeço a atenção.
GOSTEI 0
Anthony Accioly
25/05/2011
Blz, agora deu para clarear as idéias aqui. Só gostaria que na medida do possível, você me explicasse o que esta passagem que está dentro do método getTableCellRendererComponent() irá fazer:
ImagemVO imagemVO = (ImagemVO) value;
Opa,
Vamos lá: Cada componente associado ao JTable tem sua responsabilidade. O TableModel, a partir do método getValueAt, diz quais dados devem ser disponibilizados para a tabela. Com esse método você pode dizer, por exemplo, que cada linha deve ser associada a um elemento de uma lista, e que, por exemplo, a coluna 3 deve receber o campo imagemVO do elemento.
No código que foi postado você estava usando exclusivamente Strings. Você não precisa precisa se limitar a elas, podendo associar o tipo que lhe for mais conveniente (no caso, ImagemVO).
Como você pode imaginar o TableCellRenderer é um componente com a responsabilidade de controlar como exibir os valores passados pelo TableModel. O método getTableCellRendererComponent recebe várias informações incluindo o valor passado pela TableModel e suas posições.
No seu código, você está usando o renderer e as informações de linha e coluna para determinar qual imagem deverá ser exibida... Veja que isso fere a divisão de responsabilidade dos componentes... O TableCellRenderer não deve ditar o que será exibido, apenas o "como será exibido".
Pois bem, se estamos trabalhando com um TableCellRenderer específico para uma coluna e sabemos que o TableModel estará voltando um objeto do tipo ImagemVO para ela (caso do exemplo acima), então é seguro fazer casting do valor da célula para o tipo ImagemVO.
Sanei a dúvida?
GOSTEI 0
Anthony Accioly
25/05/2011
Abrãao,
Respondido?
Se sim, por favor feche o chamado.
Respondido?
Se sim, por favor feche o chamado.
GOSTEI 0