Skip to content

10. [TD]: Implementação da camada [ui] com uma interface do Swing

Palavras-chave: arquitetura multicamadas, Spring, injeção de dependências, biblioteca de componentes Swing.

10.1. Support

Na camada [UI], queremos criar uma interface gráfica Swing. O NetBeans possui uma ferramenta [Matisse] para criar essas interfaces Swing, que é superior ao que o Eclipse pode oferecer. As interfaces Swing tendem a ser substituídas por interfaces JavaFx. O NetBeans e o Eclipse utilizam a mesma ferramenta para criar essas últimas. Portanto, se criarmos interfaces JavaFx, podemos manter o Eclipse em toda a arquitetura em camadas.

O NetBeans pode abrir qualquer projeto Maven. Portanto, vamos usar o projeto Maven anterior e adicionar a ele uma interface Swing. No [2], carregamos (Arquivo / Abrir projeto) os projetos Maven das três camadas que criamos com o Eclipse. Em seguida, compilamos seus binários [3]. As opções [Build] e [Clean and Build] compilam o binário do projeto ao qual são aplicadas. Esses binários são colocados na pasta [target] [4-5] do projeto:

A opção [Clean] exclui essa pasta [target]. A opção [Build] a recria. A experiência mostra que, quando surgem problemas inesperados, a primeira coisa a fazer é executar um [Clean and Build] no projeto para garantir que se está trabalhando com a versão mais recente do mesmo. Isso é especialmente necessário quando há arquivos de configuração que, mesmo que sejam modificados, não provocam uma recompilação automática ao executar o projeto. É preciso, então, forçar essa recompilação por meio de um [Clean and Build] para que suas novas versões sejam instaladas na pasta [target].

10.2. Funcionamento do aplicativo

Voltemos à arquitetura geral do aplicativo [Elections]:

Agora, vamos nos concentrar em uma nova implementação da camada [ui]. A única implementação realizada até o momento é uma interface de console. Agora, vamos criar uma interface gráfica.

O usuário terá à disposição a seguinte interface para interagir com a aplicação [Elections]:

A interface gráfica está localizada na camada [ui]. É ela que interage com o usuário.

  • Ao iniciar, a aplicação de console [main] instancia as três camadas da aplicação por meio do Spring. Isso é feito antes mesmo que a interface gráfica fique visível. Ainda nessa fase de inicialização, as informações que caracterizam a eleição (número de cadeiras a serem preenchidas, limite eleitoral, listas concorrentes) são solicitadas à camada [dao]. Se essa fase de inicialização falhar (por exemplo, impossibilidade de acessar os dados), uma mensagem de erro é exibida no console e a interface gráfica não é exibida.
  • Se a leitura dos dados ocorrer sem problemas, a interface gráfica é exibida com as seguintes informações (ver captura de tela acima):
    • o número de vagas a serem preenchidas em (2)
    • o limite eleitoral em (3)
    • os identificadores e nomes das listas de candidatos em (4)
  • o usuário atribui então a cada lista candidata o número de votos por meio dos campos 4 (ID - nome), 5 (votos) e 6 (para adicionar).

Image

  • é possível, então, usar o link (10) para calcular os assentos:

Image

  • o link [Enregistrer] (12) permite registrar os resultados na fonte de dados.

10.3. A classe de implementação [ElectionsSwing] da camada [ui]

10.3.1. O projeto NetBeans

Observação: o parágrafo 22.4 indica como obter o NetBeans.

O projeto final do NetBeans para o aplicativo terá a seguinte forma: [1]. Compile-o seguindo as etapas [2-5]:

Certifique-se de que o projeto esteja configurado para ser compilado pelo JDK 1.8 [1-6]:

10.3.2. Configuração do Maven

O novo projeto [elections-swing-metier-dao-jdbc] se baseará no projeto anterior [elections-console-metier-dao-jdbc]. Para isso, adiciona-se uma dependência do Maven da seguinte maneira [1-3]:

10.3.3. Criação da interface gráfica

Para criar a interface gráfica, podemos proceder da seguinte forma:

  • [1]: adição de um objeto ao pacote [elections.ui.service]
  • [2]: seleção da opção [JFrame Form] na categoria [Swing GUI Forms]
  • [4]: atribuir um nome à classe
  • [5]: o pacote da classe.
  • Concluir o assistente.
  • [6]: a classe gerada
  • [7]: a classe [AbstractElectionsSwing] no modo [Design]
  • [8]: a aba [Navigator] que exibe a árvore [9] dos componentes da janela
  • [10]: a aba [Properties] que exibe as propriedades do componente [jFrame] selecionado em [9]
  • [11]: [JFrame] é um contêiner de componentes. Esses componentes podem ser dispostos no contêiner de acordo com diversas regras de posicionamento chamadas layouts. Aqui, escolhemos o layout [Free Design] [14], que permite posicionar componentes livremente no contêiner.

Encontramos os componentes na barra de ferramentas chamada Palette:

  • [1]: a paleta
  • [2]: um componente JLabel é inserido no contêiner de componentes
  • ; ao clicar com o botão direito sobre ele, temos acesso a diversas propriedades: seu nome [4], seu texto [3] ou seus gerenciadores de eventos [5]. Usamos [3] para definir o texto [6].
  • [1]: a aba [Properties] do componente [JLabel] dá acesso às propriedades deste: seu posicionamento horizontal [2], vertical [3], a fonte do texto [4] e o texto [5].

Quando um componente é inserido e configurado na interface gráfica e o trabalho realizado é salvo (Ctrl-S), é gerado código na classe [AbstractElectionsSwing]:

 
 

Não se deve alterar esse código em cinza, pois ele é excluído e regenerado na próxima gravação. As alterações feitas seriam, então, perdidas.

Um tutorial sobre a criação de uma interface gráfica com o NetBeans pode ser encontrado em URL [https://netbeans.org/kb/docs/java/quickstart-gui.html?print=yes#design] (novembro de 2015).

Agora, vamos construir a seguinte interface:

Os componentes da interface são os seguintes:

n.º
tipo
nome
função
1
JMenuBar
jMenuBar1
um menu
2
JLabel
jLabelSAP
o número de vagas a serem preenchidas
3
JLabel
jLabelSE
o limite eleitoral
4
JComboBox
jComboBoxNomsListes
lista dos nomes das chapas concorrentes
5
JTextField
jTextFieldVoixListe
o número de votos de uma lista
6
JLabel
jLabelAjouter
para adicionar uma lista a (8)
7,8
(JScrollPane, JList)
jListNomsVoix
os nomes e as opções das listas
9
JLabel
jLabelSupprimer
para excluir da lista selecionada em (8)
10
JLabel
jLabelCalculer
para calcular os resultados da eleição
11
JLabel
jLabelEffacer
para apagar os resultados da eleição
12
JLabel
jLabelEnregistrer
para registrar os resultados da eleição
13,14
(JScrollPane, JList)
jListResultats
para exibir os resultados da eleição
15,16
(JScrollPane,
JTextPane)
jTextPaneMessages
para exibir mensagens de acompanhamento

A anotação (JScrollPane, JList) [13-14] serve para indicar que, ao arrastar um componente [JList] para a janela, ele é automaticamente inserido em um componente [JScrollPane] que permite a rolagem da lista. É o componente [JScrollPane] que permite visualizar todos os elementos da lista, embora apenas um número limitado deles esteja visível em um determinado momento. O mesmo se aplica aos componentes [JTextPane] e [15-16].

O menu poderá ser criado da seguinte maneira:

  • [1, 2]: um componente [Menu Bar] é inserido na janela
  • [3]: o menu gerado por padrão, conforme apresentado na aba [Navigator]
  • [4,5,6]: ao clicar com o botão direito do mouse em uma opção do menu, é possível:
    • alterar seu texto [4], seu nome [5]
    • gerenciar seus eventos [6]
  • [7]: o menu desejado

O menu desejado é o seguinte:

nível 1
nível 2
Eleições
 
 
Quitter
Listas
 
 
Ajouter
 
Supprimer
Resultados
 
 
Calculer
 
Effacer
 
Enregistrer
Sobre
 

É possível testar a interface gráfica a qualquer momento:

 

Ao criar a interface, é necessário associar um gerenciador de eventos a determinados rótulos e menus [Ajouter, Effacer, ...]. Veja como fazer:

  • [1]: clique com o botão direito do mouse no componente cujo evento se deseja gerenciar
  • [2]: selecione a opção [Events]
  • [3]: selecione uma categoria de eventos
  • [4]: selecione o evento que deseja gerenciar

O código Java gerado por essa operação é o seguinte:


    jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseClicked(java.awt.event.MouseEvent evt) {
        jLabelCalculerMouseClicked(evt);
      }
    });
...

  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    // TODO insira seu código de processamento aqui:
}
  • linhas 1-5: um gerenciador de eventos é adicionado ao componente jLabelCalculer. O método addMouseListener espera, como parâmetro, uma classe que implemente a seguinte interface MouseListener:
 

A interface MouseListener é implementada por diversas classes, entre as quais a classe MouseAdapter. Esta classe implementa os cinco métodos da interface MouseListener, mas esses métodos não executam nenhuma ação. Portanto, é necessário derivar essa classe para implementar o(s) método(s) de sua escolha. Isso é feito no código acima e reproduzido a seguir:


    jLabelCalculer.addMouseListener(new java.awt.event.MouseAdapter() {
      public void mouseClicked(java.awt.event.MouseEvent evt) {
        jLabelCalculerMouseClicked(evt);
      }
});

O código acima utiliza a técnica da classe anônima apresentada no parágrafo 2.5 do curso [ref1].

Na linha 1, o parâmetro do método addMouseListener é uma classe anônima, definida dinamicamente. Trata-se de uma instância de uma classe derivada da classe MouseAdapter (linha 1), cujo método mouseClicked (linhas 2-4) é redefinido para que ele execute alguma ação.

O método jLabelCalculerMouseClicked, denominado “linha 3”, está definido da seguinte forma:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    // TODO adicione seu código de processamento aqui:
}

O desenvolvedor lida com o evento “MouseClicked” inserindo código nesse método.

Todos os manipuladores de eventos são gerados pelo NetBeans dessa maneira. O desenvolvedor pode ignorar as linhas de código geradas pelo NetBeans para associar um método a um evento de um componente. Ele pode simplesmente inserir seu código na linha 2 acima. Veja um exemplo:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    System.out.println("Mouse Clicked");
}

Se executarmos a interface gráfica e clicarmos no link [Calculer], aparecerá uma mensagem no console:

  • [1]: clica-se duas vezes no rótulo [Calculer]
  • [2]: o gerenciador desse evento foi executado e gerou as mensagens mouseClicked no console do NetBeans.

Os componentes [jComboBoxNomsListes, jListNomsVoix, jListResultats] são declarados da seguinte forma:


protected javax.swing.JComboBox jComboBoxNomsListes;
protected javax.swing.JList jListNomsVoix;
protected javax.swing.JList jListResultats;

Esses componentes são listas que normalmente são configuradas por um tipo T: o tipo dos elementos do modelo exibido pelos componentes. Esse tipo T pode ser qualquer um. O valor exibido no componente de lista é do tipo [String]. Por padrão, o método [T.toString()] é utilizado para a exibição. Para ter maior controle sobre o que será exibido, o tipo T será, neste caso, o tipo String. Portanto, a declaração correta de nossas listas é a seguinte:


protected javax.swing.JComboBox<String> jComboBoxNomsListes;
protected javax.swing.JList<String> jListNomsVoix;
protected javax.swing.JList<String> jListResultats;

Chega-se a esse resultado alterando uma das propriedades do componente:

10.3.4. Separação do código

Voltemos à estrutura do nosso aplicativo:

A classe [AbstractElectionsSwing] deve implementar a camada [ui] acima. Seu código, gerado pelo NetBeans, contém, por enquanto, apenas código de gerenciamento da janela e manipuladores de eventos que, no momento, não realizam nenhuma ação. Acima, vemos que a classe [AbstractElectionsSwing] deverá gerenciar as interações com a camada [métier]. Esse gerenciamento será feito nos manipuladores de eventos. Para esclarecer a estrutura do código, decidimos dividi-lo em duas classes:

  • [AbstractElectionsSwing], que permanecerá como foi gerada pelo NetBeans, com algumas pequenas diferenças. Essa classe não gerenciará nenhum evento por conta própria. Os manipuladores de eventos estarão vazios e serão declarados abstratos. Eles serão implementados por uma classe derivada de [AbstractElectionsSwing].
  • [ElectionsSwing], classe derivada de [AbstractElectionsSwing], que implementará todos os manipuladores de eventos.

Esse tipo de separação não é incomum. Ela pode ser encontrada, por exemplo, nas páginas da web ASP.NET (versão diferente de MVC). O projeto NetBeans evolui da seguinte forma:

 

Já o código da classe [AbstractElectionsSwing] evolui da seguinte maneira:


public abstract class AbstractElectionsSwing {
....
    private void jMenuItemCalculerActionPerformed(java.awt.event.ActionEvent evt) {
        doCalculer();
    }

...

    private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
        if (jLabelCalculer.isEnabled()) {
            doCalculer();
        }
    }

....
    // manipuladores de eventos
    abstract protected void doSupprimer();

    abstract protected void doCalculer();

    abstract protected void doQuitter();

    abstract protected void doEffacer();

    abstract protected void doEnregistrer();

    abstract protected void doAjouter();

    abstract protected void doInformer();

    abstract protected void doMajLabelAjouter();

    abstract protected void doMajLabelSupprimer();
...
}
  • linha 1: a classe é declarada como abstrata
  • linhas 3-5: tratamento do clique na opção de menu [jMenuItemCalculer]. Vemos que o tratamento do evento é delegado ao método doCalculer da linha 19. Esse método não está implementado e é declarado abstrato. Será a classe derivada [ElectionsSwing] que o implementará;
  • linhas 9-13: o manipulador do evento clic no rótulo [jLabelCalculer]. O clique sempre gera um evento, independentemente de o componente [jLabel] estar ativo (enabled=true) ou inativo (enabled=false). Aqui, verifica-se se ele está de fato ativo para processar o evento;
  • linhas 15 e seguintes: essa técnica de delegação do tratamento de eventos para um método abstrato é aplicada a todos os manipuladores de eventos.

A classe [ElectionsSwing], derivada de [AbstractElectionsSwing], implementa todos os manipuladores de eventos não implementados por [AbstractElectionsSwing]:


package elections.ui.service;;
...
public class ElectionsSwing extends AbstractElectionsSwing {

    // gerenciadores de eventos

    @Override
    protected void doInformer() {
...
    }

    @Override
    protected void doAjouter() {
    ...
    }

    @Override
    protected void doCalculer() {
    ...
    }

    @Override
    protected void doEffacer() {
    ...
    }

    @Override
    protected void doEnregistrer() {
...
    }

    @Override
    protected void doQuitter() {
        System.exit(0);
    }

    @Override
    protected void doSupprimer() {
...
    }

    @Override
    protected void doMajLabelAjouter() {
...
    }

    @Override
    protected void doMajLabelSupprimer() {
...
    }

}
  • linha 3: [ElectionsSwing] deriva de [AbstractElectionsSwing]
  • linhas 7-50: os manipuladores de eventos da janela gráfica

Os métodos da classe derivada [ElectionsSwing] irão manipular os componentes da classe pai [AbstractElectionsSwing]. Atualmente, esses componentes têm escopo private, impedindo que a classe filha [ElectionsSwing] tenha acesso a eles:


private JMenuItem jMenuItemAPropos = null;

private JLabel jLabelAjouter = null;

Para resolver esse problema, faremos com que o escopo dos componentes da interface gráfica seja [protected]:

  • definir o atributo [protected] como [3];

10.3.5. Implementação da interface [IElectionsUI]

Voltemos à estrutura do nosso aplicativo:

Acima, a camada [ui] deve apresentar a interface [IElectionsUI] ao objeto [main]:


package elections.ui.service;

public interface IElectionsUI {
    /**
     * lance le dialogue avec l'utilisateur
     */
    public void run();
}

Essa interface foi definida no projeto [elections-console-metier-dao-jdbc] e descrita no parágrafo 9.4. Como esse projeto é uma dependência do projeto [swing], essa interface é reconhecida.

Como a classe [AbstractElectionsSwing] tornou-se abstrata, ela não pode mais ser instanciada pelo Spring. Agora, é a classe [ElectionsSwing] que deve ser instanciada. A classe [ElectionsSwing] deve implementar a interface [IElectionsUI]. Seu código passa a ser o seguinte:


public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {

    // método run da interface [ElectionsUI]
    public void run() {
...
    }

  • linha 1: a classe [ElectionsSwing] implementa a interface [IElectionsUI]
  • linhas 4-6: o método [run] dessa interface

O que o método run deve fazer? Exibir a janela gráfica. Como se faz isso? Podemos nos orientar pelo método [main], gerado pelo NetBeans na classe [AbstractElectionsSwing], que realiza o que se deseja:


public static void main(String args[]) {
    /* Definir a aparência do Nimbus */
    //<editor-fold defaultstate="collapsed" desc="Código de configuração da aparência (opcional)">
    /* Se o Nimbus (introduzido no Java SE 6) não estiver disponível, mantenha a aparência padrão.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
     */
    try {
      for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
          javax.swing.UIManager.setLookAndFeel(info.getClassName());
          break;
        }
      }
    } catch (ClassNotFoundException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (InstantiationException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (IllegalAccessException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (javax.swing.UnsupportedLookAndFeelException ex) {
      java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }
    //</editor-fold>

    /* Criar e exibir o formulário */
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new NewJFrame().setVisible(true);
      }
    });
  }

O construtor [AbstractElectionsSwing] utilizado na linha 28 é o seguinte:


  public AbstractElectionsSwing() {
    initComponents();
}
  • linha 2: o método [initComponents] é um método privado gerado pelo gerador da interface gráfica. Não é possível alterar seu código.

O método [run] da classe [ElectionsSwing] poderia, então, ser o seguinte:


  @Override
  public void run() {
    // exibimos a interface gráfica
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
}
  • linha 6: a interface gráfica é inicializada por meio do método [init]. Aqui, gostaríamos de chamar o método [initComponents] da classe pai, mas ele é privado. Adiciona-se, então, na classe pai [AbstractElectionsSwing], o seguinte método [init]:

  protected void init(){
    initComponents();
}
  • (continuação)
    • por estar na classe [AbstractElectionsSwing], o método [init] tem acesso ao método privado [initComponents] da mesma classe;
    • por possuir o atributo [protected], ela é visível na classe filha [ElectionsSwing];
  • linha 7: a interface gráfica é tornada visível;

Observação: quando o método [run] foi escrito na classe [ElectionsSwing], o método [main] da classe abstrata [AbstractElectionsSwing] pode ser excluído.

10.3.6. A classe executável

Voltemos à estrutura do nosso aplicativo:

Gostaríamos que o Spring instanciasse a camada [ui] da mesma forma que era feito quando ela era implementada por uma aplicação console. Para isso, é necessário que a classe de implementação [ElectionsSwing] tenha uma referência à camada [métier]:


@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{

  // referência na camada [métier]
  @Autowired
  private IElectionsMetier metier;
...
  • linha 1: a classe [ElectionsSwing] é um componente Spring;
  • linhas 5-6: injeção pelo Spring de uma referência à camada [métier];

A interface gráfica é iniciada pela execução da seguinte classe [BootElectionsSwing]:

  

package elections.ui.boot;

import elections.ui.service.IElectionsUI;

public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsSwing", IElectionsUI.class);
    }
}

Já explicamos um código semelhante no parágrafo 9.5, ao descrevermos o código das classes [AbstractBootElections] e [BootElectionsConsole]. Na linha 12, recuperamos o bean denominado [electionsSwing], que corresponde ao nome padrão do Spring para a classe [ElectionsSwing].

10.3.7. Inicialização da interface gráfica

Quando a interface gráfica é exibida, alguns de seus componentes já foram inicializados:

Image

Vemos acima:

  • que a lista suspensa foi preenchida com os nomes das listas;
  • que o número de vagas a serem preenchidas e o limite eleitoral estão indicados;
  • que alguns links foram desativados;
  • que uma mensagem de sucesso é exibida na parte inferior da janela;

Em que momento essas inicializações serão realizadas? Elas só podem ocorrer após a inicialização do campo [electionsMetier] da classe [ElectionsSwing]. De fato, os nomes das listas serão solicitados à camada [metier]. A inicialização desse campo será feita pelo Spring na seguinte ordem:

  • uso do construtor sem parâmetros da classe [ElectionsSwing];
  • injeção de dependências, neste caso a referência da camada [métier];
  • execução do método [run] da classe [ElectionsSwing]:

    @Override
    public void run() {
        // exibe-se a interface gráfica
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                init();
                setVisible(true);
            }
        });
}
  • na linha 12, indicamos que chamamos o método [init] da classe pai, que irá desenhar os componentes da interface gráfica. Vamos redefinir esse método, localmente, na classe [ElectionsSwing]. É nesse método que inicializaremos, desta vez com dados, os componentes da janela (lista suspensa, rótulos):

O método local [init] poderia ter a seguinte estrutura:


public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {

    ...
  // referência à camada [métier]
  @Autowired
  private IElectionsMetier metier;

    // inicializações
    public void init() {
      // geração dos componentes pela classe pai
    super.init();
    
    // solicita-se as listas à camada [metier]
        ...
        // associa os nomes das listas ao menu suspenso jComboBoxNomsListes
        ...
        // assim como os parâmetros da eleição
        ...
        // inicializam-se os rótulos associados a essas duas informações
        ...
        // mensagem de sucesso
        ...
        // inicialização do estado de certos componentes do formulário
...
}

Observe, na linha 11, a chamada ao método [init] da classe pai.

10.3.8. A classe [Utilitaires]

Vários métodos utilitários estáticos foram reunidos na classe [Utilitaires]:

 

A classe [Utilitaires] é a seguinte:


package istia.st.elections.ui;

import javax.swing.JLabel;
import javax.swing.JMenuItem;

//classe utilitária
class Utilitaires {
    // gerenciar o estado de uma matriz de rótulos
    public static void setEnabled(JLabel[] labels, boolean value) {
        for (int i = 0; i < labels.length; i++) {
            labels[i].setEnabled(value);
        }
    }

    // gerenciar o estado de uma matriz de opções de menu
    public static void setEnabled(JMenuItem[] menuItems, boolean value) {
        ...
    }

}
  • linha 9: o método setEnabled define o estado dos componentes JLabel definidos em uma tabela. O método setEnabled de um componente JLabel permite ativar ou desativar o JLabel.

Tarefa a ser realizada: seguindo o exemplo do método setEnabled da linha 9 (,), escreva o método setEnabled da linha 16, que realiza a mesma função com os componentes JMenuItem.


10.3.9. O código da classe [ElectionsSwing]

Vamos relembrar a estrutura geral da classe [ElectionsSwing]:


package istia.st.elections.ui;

...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {

  // referência na camada [métier]
  @Autowired
  private IElectionsMetier metier;


    // inicializações
    public void init() {
    ...
    }

    // manipuladores de eventos

    @Override
    protected void doInformer() {
...
    }

    @Override
    protected void doAjouter() {
...
    }

    @Override
    protected void doCalculer() {
    ...
    }

    @Override
    protected void doEffacer() {
...
    }

    @Override
    protected void doEnregistrer() {
...
    }

    @Override
    protected void doQuitter() {
        System.exit(0);
    }

    @Override
    protected void doSupprimer() {
...
    }

    @Override
    protected void doMajLabelAjouter() {
    ...
    }

    @Override
    protected void doMajLabelSupprimer() {
...
    }

}

Vamos estudar os métodos da classe, um por um.

10.3.9.1. O método [init]

Voltemos à interface gráfica:

O método [init] tem os seguintes objetivos:

  • preencher o campo combinado [4] com os identificadores e nomes das listas no formato [id - nom]
  • exibir uma mensagem de sucesso em [15]
  • inicializar os rótulos [2] e [3]
  • desativar certos links

A estrutura do método [init] poderia ser a seguinte:


@Override
    protected void init() {
        // geração de componentes pela classe pai
        super.init();
        // inicializações locais
        modèleNomsVoix = new DefaultListModel<>();
        jListNomsVoix.setModel(modèleNomsVoix);
        modèleRésultats = new DefaultListModel<>();
        jListResultats.setModel(modèleRésultats);
        String info;
        try {
            // solicita-se as listas à camada [métier]
            listes = ...
            // associa-se os nomes das listas ao combo jComboBoxNomsListes
            ...
            // assim como os parâmetros da eleição
            int nbSiegesAPourvoir = ...
            double seuilElectoral = ...
            // inicializam-se os rótulos associados a essas duas informações
            ...
            // mensagem de sucesso
            info = "Source de données lue avec succès";
        } catch (ElectionsException ex1) {
            // registra-se o erro
            info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
        } catch (RuntimeException ex2) {
            // registra-se o erro
            info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
        }
        // exibe a informação
        jTextPaneMessages.setText(info);
        jTextPaneMessages.setCaretPosition(0);
        // estado do formulário
        Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
        Utilitaires.setEnabled(
                new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
        // centralizar a janela
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize = getSize();
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
    }

    private String getInfoForException(String message, ElectionsException ex) {
        // exibe a mensagem
        StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
        info.append(String.format("Code erreur : %d\n", ex.getCode()));
        // exibe os erros
        for (String erreur : ex.getErreurs()) {
            info.append(String.format("-- %s\n", erreur));
        }
        return info.toString();
    }

    private String getInfoForException(String message, RuntimeException ex) {
        // exibe a mensagem
        StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
        // exibe a pilha de exceções
        Throwable cause = ex;
        while (cause != null) {
            info.append(String.format("-- %s\n", cause.getMessage()));
            cause = cause.getCause();
        }
        return info.toString();
    }

Tarefa: complete o código do método [init].


Leitura recomendada no curso: componentes JTextField, JLabel

Informações importantes:

Um componente JList exibe os dados presentes em um modelo. Por padrão, esse modelo é do tipo DefaultListModel (linhas 2 e 3). Um objeto DefaultListModel se comporta de maneira semelhante a um tipo ArrayList:

  • para adicionar um objeto o ao modelo:

[DefaultListModel].addElement(Object o);

Nesta aplicação, o objeto o será sempre do tipo String.

  • para remover o elemento nº i do modelo:

[DefaultListModel].remove(int i);
  • Para obter o elemento nº i do modelo:

[DefaultListModel].elementAt(int i);

Para adicionar um elemento à lista suspensa [jComboBoxNomsListes], utilizar-se-á o método [addItem]:


jComboBoxNomsListes.addItem(chaîne de caractères)

Um componente JTextPane possui os métodos getText() e setText() para ler/gravar o texto exibido.

O link [Ajouter] [6] só fica ativo quando o campo [5] das vozes não estiver vazio. Na classe [AbstractElectionsSwing], o gerenciador que acompanha os movimentos do cursor no campo [5] é o seguinte:


  private void jTextFieldVoixListeCaretUpdate(javax.swing.event.CaretEvent evt) {
    doMajLabelAjouter()
}

A linha 2 chama o método [doMajLabelAjouter] da classe [ElectionsSwing].


    protected void doMajLabelAjouter() {
        // define-se o estado do rótulo [jLabelAjouter]
        ...
        // define-se o estado do menu [jMenuItemAjouter]
        ...
}

Tarefa a ser realizada: complete o código do método [doMajLabelAjouter].


10.3.9.3. Atribuir votos a cada lista

Para cada lista candidata de (4), proceda da seguinte forma:

  • escolha de uma lista em (4)
  • inserir o número de votos em (5)
  • confirmação clicando no link [Ajouter]

Erros de inserção são sinalizados conforme mostra o exemplo a seguir:

Image

Se o número de votos estiver correto, a lista é adicionada ao componente (8), o número de votos é apagado e o link [Ajouter] é desativado:

Na classe [AbstractElectionsSwing], o manipulador responsável por processar o clique no link [Ajouter] é o seguinte:


    private void jLabelAjouterMouseClicked(java.awt.event.MouseEvent evt) {
        if (jLabelAjouter.isEnabled()) {
            doAjouter();
        }
}

A linha 3 chama o método [doAjouter] da classe [ElectionsSwing]:


  // modelos das listas JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // as listas em disputa
  private ListeElectorale[] listes;

  // listas inseridas pelo usuário
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;
...
  @Override
  protected void doAjouter() {
    // o número de votos está correto?
    ...
    // se houver erro, ele é sinalizado
    if (erreur) {
      JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
              JOptionPane.INFORMATION_MESSAGE);
      jTextFieldVoixListe.requestFocus();
      // retorno à interface gráfica
      return;
    }
    // sem erro — a lista é salva
    listesSaisies.add(...);
    modèleNomsVoix.addElement(...);
    // zeramos o número de votos
    jTextFieldVoixListe.setText("");
    // estado do formulário (menus, rótulos)
...
}
  • linha 25: sempre que o usuário adiciona opções a uma lista e confirma sua escolha, essa lista é inserida no campo [listesSaisies] da linha 9. Nele será registrada a lista com as informações [id, version, nom, voix]. As três primeiras informações provêm das listas registradas inicialmente na tabela da linha 6. O método [getSelectedIndex] do menu suspenso permite obter o índice da lista selecionada;

Tarefa a ser realizada: complete o código do método [doAjouter].


O link [Supprimer] [9] só fica ativo quando um elemento é selecionado em [8].

Na classe [AbstractElectionsSwing], o manipulador que responde ao clique em um elemento da lista [8] é o seguinte:


  private void jListNomsVoixValueChanged(javax.swing.event.ListSelectionEvent evt) {
    doMajLabelSupprimer();
}

A linha 2 chama o método [doMajLabelSupprimer] da classe [ElectionsSwing].


    @Override
    protected void doMajLabelSupprimer() {
            // ativa-se o rótulo [jLabelSupprimer] e a opção de menu correspondente
            ...
}

Tarefa: complete o código do método [doMajLabelSupprimer].


10.3.9.5. Excluir uma lista de candidatos

O link [Supprimer] [9] permite excluir o par (nome, voz) selecionado em (8). Após a exclusão, o link [Supprimer] é desativado. Ele só será reativado quando uma nova lista for selecionada em (8).

Na classe [AbstractElectionsSwing], o gerenciador que responde ao clique no link [Supprimer] é o seguinte:


  private void jLabelSupprimerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelSupprimer.isEnabled()){
      doSupprimer();
    }
}

A linha 3 chama o método [doSupprimer] da classe [ElectionsSwing].


@Override
    protected void doSupprimer() {
        // exclusão da lista selecionada, do modelo modèleNomsVoix e das listas inseridas
        ...
        // atualização do status dos rótulos e opções de menu do formulário
        Utilitaires.setEnabled(...);
    ...
}

Tarefa a ser realizada: complete o código do método [doSupprimer].


O link [Calculer] [10] só fica ativo quando há pelo menos um elemento em [8].


Tarefa a ser realizada: adicione o código necessário para o gerenciamento desse link nos métodos [doAjouter] e [doSupprimer] escritos anteriormente. A opção de menu correspondente também será gerenciada.


Observação: o número de elementos de um elemento do tipo DefaultListModel é obtido com o método size().

10.3.9.7. Calcular os assentos

O link [Calculer] [10] permite iniciar o cálculo dos assentos e exibir os resultados em (14). Em caso de falha (todas as listas foram eliminadas), uma mensagem de erro é exibida em [15]. Em todos os casos, após o cálculo, o link [Calculer] [10] é desativado.

Na classe [AbstractElectionsSwing], o gerenciador que responde ao clique no link [Calculer] é o seguinte:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelCalculer.isEnabled()){
      doCalculer();
    }
}

A linha 3 chama o método [doCalculer] da classe [ElectionsSwing].


  // listas inseridas pelo usuário
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;

...
  @Override
  protected void doCalculer() {
    tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
    // cálculo dos assentos
    try {
      ...
    } catch (ElectionsException ex) {
      // exibição da exceção
      ...
      return;
    }
    // exibição dos resultados
    ...
    // atualização do status do formulário
    Utilitaires.setEnabled(...);
}

Tarefa: complete o código do método [doCalculer].


10.3.9.8. Gravar os resultados na fonte de dados

O link [Enregistrer] (12) permite gravar os resultados do cálculo de assentos na fonte de dados. Após o registro ser realizado com sucesso, o link [Enregistrer] é desativado. Em caso de falha, uma mensagem de erro é exibida em [15]. Em todos os casos, o link [Enregistrer] é desativado em seguida.

Na classe [AbstractElectionsSwing], o manipulador que lida com o clique no rótulo [Enregistrer] é o seguinte:


  private void jLabelEnregistrerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelEnregistrer.isEnabled()){
      doEnregistrer();
    }
}

A linha 3 chama o método [doEnregistrer] da classe [ElectionsSwing]:


@Override
    protected void doEnregistrer() {
        // solicitação de gravação à camada de negócios
        try {
            ...
        } catch (ElectionsException ex) {
            // exibe-se a exceção
            ...
            // retorno à interface gráfica
            return;
        }
        // atualização do formulário
        Utilitaires.setEnabled(...);
...
}

Tarefa: complete o código do método [doEnregistrer].


10.3.9.9. Apagar os resultados

O link [Effacer] (11) permite apagar os resultados exibidos em (14).

Na classe [AbstractElectionsSwing], o manipulador responsável por gerenciar o clique no rótulo [Effacer] é o seguinte:


  private void jLabelEffacerMouseClicked(java.awt.event.MouseEvent evt) {
    if(jLabelEffacer.isEnabled()){
      doEffacer();
    }
}

A linha 3 chama o método [doEffacer] da classe [ElectionsSwing]:


    @Override
    protected void doEffacer() {
        // a lista de resultados é esvaziada
        ....
        // atualização do formulário
        Utilitaires.setEnabled(...);
}

Tarefa a ser realizada: complete o código do método [doEffacer].


Observação: a classe DefaultListModel possui um método clear() que exclui todos os seus elementos.

10.3.10. Melhorias

A interface gráfica anterior pode ser aprimorada de várias maneiras: o usuário pode, por descuido, não inserir os votos de todas as listas presentes na lista suspensa e, além disso, pode, por engano, inserir várias vezes os votos de uma mesma lista.


Tarefa a ser realizada: aprimore o algoritmo para que esses dois casos não possam ocorrer. Uma solução simples é gerenciar um dicionário das listas inseridas, cujas chaves seriam os elementos do menu suspenso. Também faremos com que o link [Calculer] só fique ativo quando todas as listas tiverem sido inseridas.


Leia no curso [ref1]: a classe HashTable no parágrafo 3.8.