Ir para o conteúdo

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.


  1. Podendo não ser a melhor, é certamente a mais simples, o que na maioria dos casos é razão suficiente para a usarmos. 

  2. Se não usarmos um ImageIcon teremos de escrever mais código para conseguir ler uma imagem, o que não é objectivo deste tutorial 

  3. Para limitar o tamanho do código na página, não foi colocada toda a classe.