Uso do windowListener e do windowClosing()

Para evitar que o usuário fique fechando o aplicativo durante operações das classes subalternas ao menu principal, eu coloquei a seguinte linha no meu programa de menu principal (GuiMenuPrincipal.java):

frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

Agora eu quero restaurar o fechamento através do X da janela JFrame, pois neste momento o usuário já se comportou bem e saiu da forma correta da classe de cadastro, por exemplo. Desta forma tentei colocar um leitor de evento window, mas foi um chute e não usei a sintaxe correta, vejam como fiz:

JFrame.addWindowListener(new WindowListener() {
    public void windowClosing() {
        System.out.println("Funcionou, estou saindo e fechando...");
        BD.getInstance().close();
        System.exit(0);
    }
});

Isto não funciona e gera um mundo de erros. Quem puder me ajudar nesta tarefa, por favor mande uma mensagem. Para melhor compreensão do problema, vou anexar o código do meu programa de menu principal (GuiMenuPrincipal.java), abaixo da assinatura do email.
Staroski, dá uma mão aqui!!!

Atenciosamente,
Ronaldo

package empresaiv;

import br.com.gui.GuiCadastroCategorias;
import br.com.gui.GuiCadastroClientes;
import br.com.gui.GuiCadastroEndEntCli;
import br.com.gui.GuiCadastroFornecedores;
import br.com.gui.GuiCadastroProdutos;
import br.com.gui.GuiCadastroUnidades;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 *
 * @author Ronaldo R. Godoi
 */
public class GuiMenuPrincipal extends JFrame {
    private Container contentPane;
    private JMenuBar mnBarra;
    public static JMenu mnArquivos, mnPedidos, mnConsultas, mnRelatorios;
    private JMenuItem miSair, miClientes, miFornecedores, miProdutos,
            miCategorias, miEndEntregaCliente, miEndEntregaFornecedor,
            miForProduto, miUnidades;
    private JMenuItem miPedidoCliente, miPedidoFornecedor;
    private JMenuItem miRelatorioClientes, miRelatorioFornecedores,
            miRelatorioProdutos, miRelatorioCategorias;
    private JMenuItem miConsultaClientes, miConsultaFornecedor, miConsultaProdutos,
            miConsultaCategorias, miConsultaEndEntregaCli, miConsultaEndEntregaFor;
        
    public GuiMenuPrincipal() {
        inicializarComponentes();
        definirEventos();
    }
    
    public void inicializarComponentes () {
        setTitle("Aplicativo Empresa");
        setBounds(0, 0, 1000, 800);
        contentPane = getContentPane();
        mnBarra = new JMenuBar();
        mnArquivos = new JMenu("Arquivos");
        mnArquivos.setMnemonic('A');
        mnPedidos = new JMenu("Pedidos");
        mnPedidos.setMnemonic('P');
        mnConsultas = new JMenu("Consultas");
        mnConsultas.setMnemonic('C');
        mnRelatorios = new JMenu("Relatórios");
        mnRelatorios.setMnemonic('R');
        
        // Arquivos ou Cadastros
        miClientes = new JMenuItem("Cadastro de Clientes");
        miFornecedores = new JMenuItem("Cadastro de Fornecedores");
        miProdutos = new JMenuItem("Cadastro de Produtos");
        miCategorias = new JMenuItem("Cadastro de Categoria de Produto");
        miEndEntregaCliente = new JMenuItem("Cadastro de Endereço de Entrega de Cliente");
        miEndEntregaFornecedor = new JMenuItem("Cadastro onde o Fornecedor deve entregar");
        miForProduto = new JMenuItem("Cadastro de Fornecedor de Produtos");
        miUnidades = new JMenuItem("Cadastro de Unidades de Venda e Compra");
        miSair = new JMenuItem("Sair", new ImageIcon("c:/icones/sair2.jpg"));
        miSair.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK));
        
        // Pedidos
        miPedidoCliente = new JMenuItem("Pedidos de Clientes");
        miPedidoFornecedor = new JMenuItem("Pedidos para Fornecedor");
        
        // Consultas
        miConsultaClientes = new JMenuItem("Consulta Clientes");
        miConsultaFornecedor = new JMenuItem("Consulta Fornecedor");
        miConsultaProdutos = new JMenuItem("Consulta Produtos");
        miConsultaCategorias = new JMenuItem("Consulta Categorias");
        miConsultaEndEntregaCli = new JMenuItem("Consulta Endereço de Entrega de Cliente");
        miConsultaEndEntregaFor = new JMenuItem("Consulta Endereço de Entrega de Fornecedor");
        
        // Relatórios
        miRelatorioClientes = new JMenuItem("Relatório de Clientes");
        miRelatorioFornecedores = new JMenuItem("Relatório de Fornecedores");
        miRelatorioProdutos = new JMenuItem("Relatório de Produtos");
        miRelatorioCategorias = new JMenuItem("Relatório de Categorias");
                
        //Menu Cadastros
        mnArquivos.add(miClientes);
        mnArquivos.add(miFornecedores);
        mnArquivos.add(miProdutos);
        mnArquivos.add(miCategorias);
        mnArquivos.add(miEndEntregaCliente);
        mnArquivos.add(miEndEntregaFornecedor);
        mnArquivos.add(miForProduto);
        mnArquivos.add(miUnidades);
        mnArquivos.add(miSair);
        
        //Menu Pedidos
        mnPedidos.add(miPedidoCliente);
        mnPedidos.add(miPedidoFornecedor);
        
        //Menu Consultas
        mnConsultas.add(miConsultaClientes);
        mnConsultas.add(miConsultaFornecedor);
        mnConsultas.add(miConsultaProdutos);
        mnConsultas.add(miConsultaCategorias);
        mnConsultas.add(miConsultaEndEntregaCli);
        mnConsultas.add(miConsultaEndEntregaFor);
        
        //Menu Relatórios
        mnRelatorios.add(miRelatorioClientes);
        mnRelatorios.add(miRelatorioFornecedores);
        mnRelatorios.add(miRelatorioProdutos);
        mnRelatorios.add(miRelatorioCategorias);
        
        mnBarra.add(mnArquivos);
        mnBarra.add(mnPedidos);
        mnBarra.add(mnConsultas);
        mnBarra.add(mnRelatorios);
        setJMenuBar(mnBarra);
        liberaMenu();
    }
    
    private void definirEventos() {
        
        //JFrame.addWindowListener(new WindowListener() {
        //    public void windowClosing() {
        //        System.out.println("Funcionou, estou saindo e fechando...");
        //        BD.getInstance().close();
        //        System.exit(0);
        //    }
        //});
        
        miSair.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                BD.getInstance().close();
                System.exit(0);
            }
        });

        miClientes.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                bloqueiaMenu();
                GuiCadastroClientes label = new GuiCadastroClientes();
                contentPane.removeAll();
                contentPane.add(label);
                contentPane.validate();
            }
        });
        
        miFornecedores.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                bloqueiaMenu();
                GuiCadastroFornecedores label = new GuiCadastroFornecedores();
                contentPane.removeAll();
                contentPane.add(label);
                contentPane.validate();
            }
        });

        miProdutos.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                bloqueiaMenu();
                GuiCadastroProdutos label = new GuiCadastroProdutos();
                contentPane.removeAll();
                contentPane.add(label);
                contentPane.validate();
            }
        });
        
        miCategorias.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                bloqueiaMenu();
                GuiCadastroCategorias label = new GuiCadastroCategorias();
                contentPane.removeAll();
                contentPane.add(label);
                contentPane.validate();
            }
        });
        
        miEndEntregaCliente.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                bloqueiaMenu();
                GuiCadastroEndEntCli label = new GuiCadastroEndEntCli();
                contentPane.removeAll();
                contentPane.add(label);
                contentPane.validate();
            }
        });
        
        miUnidades.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                bloqueiaMenu();
                GuiCadastroUnidades label = new GuiCadastroUnidades();
                contentPane.removeAll();
                contentPane.add(label);
                contentPane.validate();
            }
        });
        
    }
    
    public void bloqueiaMenu() {
        mnArquivos.setEnabled(false);
        mnPedidos.setEnabled(false);
        mnConsultas.setEnabled(false);
        mnRelatorios.setEnabled(false);
    }
    
    public static void liberaMenu() {
        mnArquivos.setEnabled(true);
        mnPedidos.setEnabled(true);
        mnConsultas.setEnabled(true);
        mnRelatorios.setEnabled(true);
    }
        
    public static void abrir() {
        GuiMenuPrincipal frame = new GuiMenuPrincipal();
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        Dimension tela = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setLocation((tela.width - frame.getSize().width) / 2,
                (tela.height - frame.getSize().height) / 2);
        frame.setVisible(true);
    }
    
}

O método addWindowListener não é estático.
Você tem que chamá-lo a partir de um objeto e não a partir da classe.
E ele recebe um WindowEvent como parâmetro.
Veja a documentação.

frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent event) {
        System.out.println("Funcionou, estou saindo e fechando...");
        BD.getInstance().close();
        System.exit(0);
    }
});
1 curtida

Não consigo fazer referência ao objeto frame. O NetBeans diz que não consegue encontrar frame e a palavra fica sublinhada em vermelho, tem alguma outra forma de implementar addWindowListener? Ou fazer referência a classe JFrame que não seja usando o objeto frame, criado no método estático abrir() (veja GuiMenuPrincipal.java neste tópico, mensagem anterior)?

Pensei que estivesse adicionando o listener no método abrir

É só fazer assim então:

addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent event) {
        System.out.println("Funcionou, estou saindo e fechando...");
        BD.getInstance().close();
        System.exit(0);
    }
});

Aliás, por qual motivo o abrir é estático?

Funcionou, Staroski. Mas estou pensando em tirar esse addWindowListener porque ficou igual ao JFrame.EXIT_ON_CLOSE. O sistema acessa o evento mesmo quando eu estou em outras classes (cadastros). Então é trocar 6 por meia duzia.

Eu testei o JFrame.DO_NOTHING_ON_CLOSE e funcionou do jeito que eu queria (suprimindo o fechamento do sistema) mas quando eu acrescento o addWindowListener ele captura o X de fechar a janela em qualquer parte do sistema. Entendeu?

Muito obrigado pela atenção, valeu mesmo. Tem alguma outra idéia?

Isto é só um capricho a mais, entende? Queria que ele só aceitasse o X da janela para fechar quando estivesse no GuiMenuPrincipal. Mas com o addWindowListener capta o X de fechar em todo sistema.

Ainda não fiz nada, mas estou pensando em retornar ao JFrame.EXIT_ON_CLOSE e esquecer o que tinha dado certo, que era o JFrame.NOTHING_ON_CLOSE. O que você acha?

Atenciosamente,
Ronaldo

P.S.: Para provar que o addWindowListener estava funcionando ele mostra a mensagem da linha System.out.println(“Funcionou, estou saindo e fechando…”, pelo menos ele executa o BD.getInstance().close() ao sair.

Não é não, o EXIT_ON_CLOSE não vai chamar o método close da sua classe BD.

Mas aí é uma questão de você por um if no seu windowClosing e só encerrar o sistema se não houver nenhuma outra tela sendo exibida.
:man_shrugging:

1 curtida

Ok. Entendi. Eu já estava gostando da nova rotina (addWindowListener) executar um close () e fechar explicitamente a conexão. Ponto para você mas para mim também.

Eu preciso testar com um if se existe algum objeto da super classe JPanel sendo exibido, é isso? Só quero que ele feche o aplicativo se o controle da execução estiver no GUIMenuPrincipal que é um JFrame. Posso também testar se o menu Arquivos esta enabled (se é que existe este método) pois quando estou com uma classe diferente de GUIMenuPrincipal aberta os menus estão desabilitados. Enfim qual condição pode ser testada? Não sei testar se JPanel esta ativo. Vou dar uma pesquisada aqui. Mas se você quiser pode dar a melhor opção.

Obrigado,
Ronaldo

Não, eu testaria se alguma de suas telas está visível, pelo que entendi, se alguma das suas telas é exibida, não pode sair do sistema.
(Condição estranha, mas o sistema é seu e você sabe o que quer fazer)

O que você seria esse “se controle da execução estiver no GUIMenuPrincipal”?

Perceba que aí tem uma palavra importante: controle.
Você criou uma classe contadora para a comunicação entre suas telas?

1 curtida

Staroski, eu testei se mnArquivos está enabled, e deu certo. O aplicativo só é fechado se estiver no GUIMenuPrincipal como eu queria. Mas daí eu percebi um probleminha, meio desagradável. Queria saber sua opinião, veja o código que deu certo, abaixo:

addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent event) {
        if(mnArquivos.isEnabled()) {
            System.out.println("Funcionou, estou saindo e fechando...");
            BD.getInstance().close();
            System.exit(0);
        } else {
            JOptionPane.showMessageDialog(null, "Volte ao menu principal e click no X para fechar a janela");
        }                    
    }
});

Toda vez que eu chego na linha BD.getInstance().close() o banco de dados está fechado, porque já corrigimos isto nas classes de cadastro. E o que o sistema faz? O sistema encontra a linha BD.getInstance().close() e cria uma nova conexão e então fecha ela com o close(). Estou pensando em tirar essa linha tando deste trecho de código acima quanto do abaixo:

miSair.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        BD.getInstance().close();
        System.exit(0);
    }
});

Os dois trechos estão na classe GuiMenuPrincipal e quando a operação do sistema esta nesta classe, se eu fiz tudo direito, a conexão tem que estar fechada. Ou seja, o programa está abrindo a conexão para fechá-la. O que você acha?

Porque tem códigos repetidos em uma mesma classe?
Em quantos lugares você chama esse close?
Cria um método privado sair e chama esse método ao invés de ficar repetindo código.

Acho que você deve ter modificado alguma coisa na classe BD e também acho que está chamando no close em vários lugares.

1 curtida

Eu modifiquei o método close para fazer instance=null toda vez que for chamado. Fiz isso porque depois de fechar a conexão e sair de uma classe quando chamava BD para abrir novamente em outra classe o método getInstance () entendia que instance era diferente de null e não abria conexão nova ficando sem operação com banco de dados. Dava erro.

Só posso chamar getInstance () em duas situações, quando quero abrir o banco de dados:
bd = BD.getInstance();

Ou quando quero fechar pela primeira vez a conexão:
BD.getInstance ().close ();

Se eu repetir esse close acima ele abre a conexão somente para fechar. Se eu forço o usuário a sair das classes de maneira adequada, desabilitando o acesso aos menus enquanto estiver numa classe qualquer, obrigatoriamente o banco de dados vai estar fechado.

Mas vamos a questão central é porque tenho que chamar getInstance() para chamar o close(). O getInstance () abre a conexão se ela estiver fechada.

Staroski, se puder me esclareça ou de uma sugestão.

Grande abraço ao pessoal do GUJ,
Ronaldo

Tira esse instance = null, e só chama o close antes de fazer o System.exit, como tenho dito nos seus outros posts.

Você só precisa fechar a conexão quando SAI DO SEU SISTEMA, e não quando alterna entre as telas de cadastro.

1 curtida

Tem algum problema eu transformar close() em um método estático e fazer referência direta ao close() da forma:

BD.close();

Tem algum problema? As funções estáticas sobrecarregam demais o sistema?
Porque se for para deixar o banco de dados empresa aberto todo tempo, era só fazer a abertura e o fechamento dentro da classe GuiMenuPrincipal. Você não acha elegante trabalhar com o banco de dados fechado enquanto estiver no menu e só abrir o banco quando for usar? Supondo que você vai trabalhar horas e muito deste tempo você fica em espera no menu principal.

Você notou que ficamos com dois objetos iguais bd e instance quando estamos com o banco aberto? Sim, porque ao chamar bd = BD.getInstance(); dentro de uma classe DAO ele vai criar a conexão dentro de instance e passar por um return para bd da classe DAO.

Veja abaixo, o método getInstance():

public static synchronized BD getInstance() {
    if (instance == null) {
        System.out.println("Criando objeto da classe BD");
        instance = new BD();
    }
    System.out.println("Retornando objeto existente da classe BD");
    return instance;
}

O objeto instance já é uma conexão aberta, quando ele retorna este objeto para bd, eu acho que ele faz uma duplicata de si mesmo. Estou errado? De qualquer forma esta funcionando.

Atenciosamente,
Ronaldo

P.S.: Lembrei de uma coisa importante. Quando declaro um objeto, no caso instance, que é uma conexão aberta com um banco de dados, igual a null, o sistema encerra adequadamente instance?

Tem, a função do close da classe BD é chamar o método close do atributo connection.

Não é questão de sobrecarregar, é questão de programação orientada à objetos.
Se você fizer tudo estático, estará programando de forma procedural, não faz sentido.

Ainda tenho a impressão que você sempre quer fazer tudo estático pois ainda não entendeu o que é classe e o que é objeto.

É a forma mais elegante e correta para trabalhar quando o seu banco de dados é acessado por vários usuários simultâneos.
No seu caso você tem uma aplicação standalone que acessa sozinha esse banco de dados, então é bem melhor abrir a conexão ao abrir o sistema e fechar a conexão antes de encerrar o sistema.

Tá aí, você ainda não entendeu em que momento é criado um objeto.
O único momento em que é criado um novo objeto é ao usar a instrução new.
O padrão de projeto singleton, que é o que o getInstance implementa, garante que uma determinada classe só permita a criação de um único objeto dela.

Indiferente da quantidade de variáveis do tipo BD que você criar, o getInstance garante que todas essas variáveis estejam referenciando o MESMO OBJETO, ou seja, o mesmo endereço de memória.

Está errado, primeiro que o instance não é a conexão, a conexão é o atributo connection.
E ele não cria duplicata nenhuma ele apenas retorna o objeto que já foi instanciado.

Da uma olhada no código abaixo e responda a pergunta que vem em seguida:

BD bd1 = BD.getInstance();
BD bd2 = BD.getInstance();
BD bd3 = BD.getInstance();
BD bd4 = bd1;
BD bd5 = bd2;

Quantos objetos da classe BD são criados no código acima?

O que você quer dizer com “encerra adequadamente instance”?
Se está perguntando se atribuir null vai desconectar o banco, a resposta é não.
Quando você faz variável = null, você só está dizendo que aquela variável não aponta pra ligar nenhum.
Pra desconectar do banco tem que chamar o close do atributo connection.

Prezado Staroski, gostei muito de suas respostas as minhas perguntas.

Gostaria de te responder a questão que me propôs, vou dizer o que acho, não sei se estou certo. São criados dois objetos da classe BD. Apenas bd1 e bd4, além do objeto instance que seria um terceiro. bd2, bd3 e bd5 foram apenas instanciados.

Essa seria minha primeira resposta, mas olhando o código BD.java, eu comecei a acreditar que cinco objetos iguais foram criados.

Mas a conexão foi feita uma única vez pois tem um if que só executa new BD() uma vez quando instance é null. Então minha resposta final é que existe apenas uma conexão mas existem seis objetos BD. (bd1, bd2, bd3, bd4, bd5 e instance. Se for isso entendi.

Se não, gaste só mais um pouquinho do seu latin, e explique rapidamente se não for te chatear.

Atenciosamente,
Ronaldo

Imaginei que diria isso.
Pois bem, infelizmente você está errado.
É criado apenas um objeto da classe BD.
Temos 5 variáveis distintas, mas todas estão apontando para o mesmo objeto.

Se está difícil enxergar isso, veja quantas vezes a instrução new é executada.
Cada chamada à instrução new cria um novo objeto.

A instrução new foi chamada 5 vezes?
Nesse exemplo tem 5 variáveis do tipo BD, mas todas elas apontam para o mesmo objeto.

Duplicata é quando você tem duas variáveis do mesmo tipo com o mesmo valor dos atributos, mas sendo instâncias diferentes.

Você está confundindo as variáveis com os objetos.
Somente um objeto é criado, a instrução new BD() só é executada uma única vez.
Depois sempre é devolvido o mesmo objeto, o mesmo endereço de memória.

Enquanto isso não ficar claro pra ti, vais ter dificuldade.

1 curtida

Ok. Entendi um new, um objeto; cinco new, cinco objetos. Tudo bem.

Eu tinha pensado que tinham sido criadas cópias do objeto e que poderia trabalhar diferentemente com cada uma. Bom saber.

Atenciosamente,
Ronaldo

Não.

E é muito importante saber que uma vez criado um objeto, você pode enviar ele para outras classes (por passagem de parâmetros) e aí ele pode ser manipulado.

Por isso que fico doido quando você inventa de pôr static em tudo.

Você modificou alguns métodos e alguns atributos para static desnecessariamente.

Acredito que fez isso por não saber como passar uma referência de seu objeto para outra classe poder manipular.

🤷🏻

Por isso que é importante você modelar seu sistema antes de sair “cuspindo código”.
Tem que pensar como será o relacionamento entre as classes, quem vai enviar mensagens pra quem, quem vai controlar o quê.

Se ficar só “remendando retalhos” a medida que vai inventando funcionalidade nova, o sistema vai virar uma bagunça.

Eu, realmente, criei um objeto static mas foi só um. Infelizmente os menus são variáveis estáticas também, mas só mnArquivos, mnPedidos, mnConsultas e mnRelatorios. E também tive que transformar estes menus em públicos para poder acessar fora do GuiMenuPrincipal. Foi só um métodozinho.

Mas estou disposto a alterar essa condição, só que não sei como, veja alguns techos de código, e me de sua opinião:

GuiMenuPrincipal.java
Nesta linha abaixo, sou obrigado a transformar os JMenu em publicos e estáticos.

public static JMenu mnArquivos, mnPedidos, mnConsultas, mnRelatorios;

Nestas linhas abaixo mostro como um item de menu chama o método bloqueiaMenu() que não é estático para desabilitar os menus do sistema:

miClientes.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        bloqueiaMenu();
        GuiCadastroClientes label = new GuiCadastroClientes();
        contentPane.removeAll();
        contentPane.add(label);
        contentPane.validate();
    }
});

Nestas linhas abaixo, crio o método estático liberaMenu() e o método bloqueiaMenu():

public void bloqueiaMenu() {
    mnArquivos.setEnabled(false);
    mnPedidos.setEnabled(false);
    mnConsultas.setEnabled(false);
    mnRelatorios.setEnabled(false);
}

public static void liberaMenu() {
    mnArquivos.setEnabled(true);
    mnPedidos.setEnabled(true);
    mnConsultas.setEnabled(true);
    mnRelatorios.setEnabled(true);
}

E veja só em toda entrada de classe eu faço como em miClientes.addActionListener(new ActionListener() e chamo bloqueiaMenu() e em todas as classes de Gui eu chamo dentro delas o liberaMenu(). Veja como fica no GuiCadastroClientes:

btSair.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        GuiMenuPrincipal.liberaMenu();
        BD.getInstance().close();
        setVisible(false);
    }
});

A programação ficou assimétrica porque um método é estático e o outro não é, incomodou meu senso de estética mas funcionou. Se tiver alguma sugestão, eu reverto este static se não fica assim mesmo, não está lindo, mas ficou bonitinho.

Atenciosamente,
Ronaldo

Fiz umas modificações no GuiMenuPrincipal. Agora nem os menus nem o bloqueiaMenu() são públicos, eu troquei para private e funcionou normal. É outra quebra de simetria, mas fica melhor economizar elementos públicos, correto? Outra coisa os menus continuam sendo static.