JPanel Com Backgroud Personalizado
A necessidade de colocar uma imagem como fundo de um painel, frame, label, etc., é bastante comum, no entanto, parece ser algo que apesar de simples, escapa à maioria dos programadores de Java pouco habituados à framework de componentes gráficos Swing.
O objectivo deste pequeno tutorial é o de explicar os passos mais usados para se colocar uma imagem como fundo de um componente, para isso iremos criar uma pequena aplicação cuja única funcionalidade é mostrar imagens e usaremos componentes JPanel. Para outros componentes o processo é semelhante.
Princípio Base
- É necessário redefinir um dos componentes contentores (JPanel, JLabel principalmente, mas outros também podem ser usados como JScrollPane, JTable, JList etc.);
- O componente terá de ser não opaco, se o componente não estiver definido como transparente as nossas imagens serão pintadas, normalmente de cizento;
- Precisamos utilizar o método paintComponent(Graphics g) que todos os componentes Swing possuem.
A ideia base está delineada nos 3 pontos acima. O que pretendemos é criar uma sub-classe de um componente, escolheremos o que nos der mais jeito, mas para este exemplo vamos usar o JPanel. Com uma sub-classe de JPanel temos tudo o que precisamos já que o componente tem implementado todo o comportamento que nos poderá ser necessário, a única coisa que precisamos é modificar ligeiramente a forma como o painel é desenhado de modo a desenharmos também uma imagem a nosso gosto.
Classe ImagePanel
A abordagem que iremos seguir é a mais usada, não quer com isto dizer que seja a melhor ou a que devem sempre seguir, mas se procurarem informação sobre como escrever imagens no fundo de painéis Swing, certamente terão as primeiras páginas com soluções similares, senão iguais à que iremos desenvolver.1
public class ImagePanel extends JPanel {
/*
* Podiamos usar qualquer uma das classes abaixo mas para simplificar
* vamos usar um objecto do tipo Image que obtemos através de um ImageIcon
* que tem métodos mais simples para carregar imagens, embora não permite a
* mesma optimização que, por exemplo, um BufferedImage.
*
* Image é uma classe abstracta.
*
* Image, ImageBuffered, ImageIcon
*/
private Image image;
/**
* Construtor que irá receber a imagem e defini-la como existindo no fundo
* do painel.
*
* @param url URL com a imagem a colocar no fundo.
*/
public ImagePanel(URL url) {
/*
* Para evitar repetir código vamos já usar o método seImage. Usar os sets/gets
* no construtor pode ajudar a manter a validade dos dados se, principalmente,
* os sets fizerem alguma validação. Neste caso é simplesmente para não
* repetir o mesmo código
*/
setImage(url);
//Fundamental que a propriedade seja coloca a false ou a imagem não vai aparecer
setOpaque(false);
setPreferredSize(new Dimension(getWidth(), getHeight()));
}
public void setImage(URL url) {
image = new ImageIcon(url).getImage();
}
@Override
public void paintComponent(Graphics g) {
/*
* A imagem vai ser desenhada em x=0, y=0 e usando o tamanho real da imagem
* é fundamental que seja chamado o método drawImage antes de se chamar o
* método paintComponent da superclasse.
*/
g.drawImage(image, 0, 0, this);
/*
* Ao desenharmos primeiro a imagem garantimos que qualquer componente
* que seja adicionado ao painel fique por cima da imagem, criando assim
* o efeito de imagem de background que pretendemos.
*
* Se trocarmos a ordem, os efeitos podem não ser os esperados já que a
* imagem vai ser desenhada em cima dos componentes que estão neste JPanel.
* Se não existirem componente então a ordem não é relevante.
*/
super.paintComponent(g);
}
/**
* A largura do nosso painel será dada pela largura da nossa imagem.
*
* @return int largura da imagem, se disponível
*/
@Override
public int getWidth() {
return image.getWidth(this);
}
/**
* Tal como a largura, também a altura é dependente do painel.
*
* @return int altura da imagem, se disponível
*/
@Override
public int getHeight() {
return image.getHeight(this);
}
}
Et Voilá, aqui está tudo o que precisamos, sim, é assim tão simples. Neste exemplo coloquei alguns métodos que podem não ser necessários ou que podiam ser feitos de outra forma, como é o caso do setImage e dos métodos getWidth e getHeight. O primeiro poderia ser implementado de várias maneiras diferentes, receber string ou já a Image carregada, enfim, as possibilidades são tantas quantas a necessidade ditar. Para os dois últimos métodos a implementação é devia à necessidade que temos de actualizar alguns tamanhos na nossa frame, que iremos ver adiante.
Como indicado no comentário, poderíamos usar várias classes para a leitura de imagens, neste exemplo segui a abordagem mais simples e usei logo um objecto do tipo ImageIcon. Esta classe tem a vantagem de receber um URL ou uma string com o caminho para a imagem, algo que é fácil de obter, e de ser capaz de carregar a imagem e devolver-nos uma instância com a imagem carregada2. Seria possível usar outros métodos, mas com este cortamos algum código.
JFrame
A nossa JFrame foi criada usando o editor de interface gráficas do NetBeans IDE pelo que algum do código poderá parecer desadequado ou estranho, principalmente o código dentro do método initComponents3.
public class JPanelBg extends javax.swing.JFrame {
private ImagePanel imgPanel;
private static final String BASE_PATH = "/org/pap/wiki/resources/";
private ArrayList<JButton> bts;
public JPanelBg() {
bts = new ArrayList<JButton>();
imgPanel = new ImagePanel(getClass().getResource((JPanelBg.BASE_PATH + "129020245952929910.jpg")));
imgPanel.setLayout(new FlowLayout());
//Definir os tamanhos dos paineis de da frame para que a janela se ajuste
//quando se altera a imagem.
imgPanel.setSize(imgPanel.getWidth(), imgPanel.getHeight());
imgPanel.setPreferredSize(new Dimension(imgPanel.getWidth(), imgPanel.getHeight()));
initComponents();
jpContainer.add(imgPanel, BorderLayout.CENTER);
//Definir os tamanhos dos paineis de da frame para que a janela se ajuste
//quando se altera a imagem.
jpContainer.setSize(imgPanel.getWidth() + 5, imgPanel.getHeight() + 5);
jpContainer.setPreferredSize(new Dimension(imgPanel.getWidth() + 5, imgPanel.getHeight() + 5));
jpContainer.validate();
setPreferredSize(new Dimension(imgPanel.getWidth() + 5, imgPanel.getHeight() + 5));
setSize(imgPanel.getWidth() + 10, imgPanel.getHeight() + 110);
validate();
}
private void changeBackgroundImage(URL url) {
/*
* Imagens padrão disponíveis no projecto:
* - 129020245952929910.jpg
* - 129035672930741070.jpg
* - 129053928051112006.jpg
* - 129065956127392161.jpg
* - 129072913489537379.jpg
* - 129072913708288779.jpg
* - 129073114374196211.jpg
* - 129075102110253125.jpg
* - 129077153325887535.jpg
* - 129080486939306349.jpg
* - 129080700981764947.jpg
* - 129081373240734237.jpg
* - 129083297155187948.jpg
* - 129084059224923777.jpg
* - 129086861730952266.jpg
* - cute-baby-animal-hippo.jpg
*/
if (imgPanel != null) {
imgPanel.removeAll();
imgPanel.setImage(url);
//Definir os tamanhos dos paineis de da frame para que a janela se ajuste
//quando se altera a imagem.
imgPanel.setSize(imgPanel.getWidth(), imgPanel.getHeight());
imgPanel.setPreferredSize(new Dimension(imgPanel.getWidth(), imgPanel.getHeight()));
jpContainer.setSize(imgPanel.getWidth() + 5, imgPanel.getHeight() + 5);
jpContainer.setPreferredSize(new Dimension(imgPanel.getWidth() + 5, imgPanel.getHeight() + 5));
jpContainer.validate();
setPreferredSize(new Dimension(imgPanel.getWidth() + 5, imgPanel.getHeight() + 5));
setSize(imgPanel.getWidth() + 10, imgPanel.getHeight() + 110);
validate();
}
}
/**
* Redefinir o método para centrar a frame, apenas por conveniência.
*
* @param visible boolean indica se a frame está visível ou não.
*/
@Override
public void setVisible(boolean visible) {
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
setLocation((int)(screen.getWidth() / 2 - getWidth() / 2), (int)(screen.getHeight() / 2 - getHeight() / 2));
super.setVisible(visible);
}
private void jcbxImageActionPerformed(java.awt.event.ActionEvent evt) {
changeBackgroundImage(getClass().getResource((JPanelBg.BASE_PATH + jcbxImage.getSelectedItem().toString())));
}
private void jbtnAddButtonsActionPerformed(java.awt.event.ActionEvent evt) {
JButton button = new JButton("Click...");
bts.add(button);
imgPanel.add(button);
validate();
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
Logger.getLogger(JPanelBg.class.getName()).log(Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
Logger.getLogger(JPanelBg.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(JPanelBg.class.getName()).log(Level.SEVERE, null, ex);
} catch (UnsupportedLookAndFeelException ex) {
Logger.getLogger(JPanelBg.class.getName()).log(Level.SEVERE, null, ex);
}
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new JPanelBg().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JLabel jLabel1;
private javax.swing.JButton jbtnAddButtons;
private javax.swing.JComboBox jcbxImage;
private javax.swing.JPanel jpContainer;
private javax.swing.JPanel jpTools;
// End of variables declaration
}
Conclusão
Com apenas algumas linhas de código conseguimos colocar uma imagem escolhida por nós no fundo de um JPanel. Este exemplo, como é natural, é bastante simples e deverá ser visto como uma base a partir da qual podem expandir e criar novas aplicações.
-
Podendo não ser a melhor, é certamente a mais simples, o que na maioria dos casos é razão suficiente para a usarmos. ↩
-
Se não usarmos um ImageIcon teremos de escrever mais código para conseguir ler uma imagem, o que não é objectivo deste tutorial ↩
-
Para limitar o tamanho do código na página, não foi colocada toda a classe. ↩