Skip to content

10. [Trabalho]: Implementação da camada [ui] utilizando uma interface Swing -

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

10.1. Suporte

Na camada [UI], pretendemos criar uma GUI Swing. O NetBeans dispõe de uma ferramenta [Matisse] para criar estas interfaces Swing, que é superior à oferecida pelo Eclipse. As interfaces Swing estão a ser cada vez mais substituídas por interfaces JavaFX. O NetBeans e o Eclipse utilizam a mesma ferramenta para criar estas últimas. Por conseguinte, se criarmos interfaces JavaFX, poderemos utilizar o Eclipse em toda a arquitetura em camadas.

O NetBeans pode abrir qualquer projeto Maven. Por isso, vamos utilizar o projeto Maven anterior e adicionar-lhe uma interface Swing. Em [2], carregamos (Arquivo / Abrir Projeto) os projetos Maven para as três camadas que criámos com o Eclipse. Em seguida, compilamos os seus binários [3]. As opções [Compilar] e [Limpar e Compilar] compilam o binário do projeto ao qual são aplicadas. Estes binários são colocados na pasta [target] do projeto [4-5]:

A opção [Limpar] elimina esta pasta [destino]. A opção [Compilar] recompila-a. A experiência mostra que, quando surgem problemas inesperados, a primeira coisa a fazer é executar um [Limpar e Compilar] no projeto para garantir que está a trabalhar com a versão mais recente. Isto é particularmente necessário quando tem ficheiros de configuração que, se modificados, não desencadeiam uma recompilação automática quando o projeto é executado. Deve então forçar esta recompilação com um [Limpar e Compilar] para que as suas novas versões sejam instaladas na pasta [destino].

10.2. Como funciona a aplicação

Voltemos à arquitetura geral da aplicação [Eleições]:

Estamos agora a concentrar-nos numa nova implementação da camada [ui]. A única implementação atualmente em vigor é uma interface de consola. Estamos agora a criar uma interface gráfica de utilizador.

O utilizador terá a seguinte interface para interagir com a aplicação [Eleições]:

A interface gráfica do utilizador está localizada na camada [ui]. É esta camada que interage com o utilizador.

  • Ao iniciar, a aplicação de consola [main] instancia as três camadas da aplicação utilizando o Spring. Isto é feito antes mesmo de a interface gráfica do utilizador ficar visível. Também durante esta fase de inicialização, são solicitadas à camada [dao] as informações que caracterizam a eleição (número de lugares a preencher, limiar eleitoral, listas concorrentes). Se esta fase de inicialização falhar (por exemplo, impossibilidade de aceder aos dados), é apresentada uma mensagem de erro na consola e a interface gráfica do utilizador não é apresentada.
  • Se os dados tiverem sido lidos com sucesso, a interface gráfica é apresentada com as seguintes informações (ver captura de ecrã acima):
    • o número de lugares a preencher (2)
    • o limiar eleitoral referido no n.º 3
    • os números de identificação e os nomes das listas de candidatos em (4)
  • O utilizador atribui então o número de votos a cada lista de candidatos utilizando os campos 4 (ID - Nome), 5 (Votos) e 6 (Adicionar).

Image

  • Pode então utilizar o link (10) para calcular os lugares:

Image

  • O link [Guardar] (12) permite guardar os resultados na fonte de dados.

10.3. A classe [ElectionsSwing] que implementa a camada [ui]

10.3.1. O projeto NetBeans

Nota: A secção 22.4 explica como obter o NetBeans.

O projeto NetBeans final para a aplicação terá este aspeto [1]. Compile-o seguindo os passos [2–5]:

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

10.3.2. Configuração do Maven

O novo projeto [elections-swing-business-dao-jdbc] será baseado no projeto anterior [elections-console-business-dao-jdbc]. Para tal, adicione uma dependência do Maven da seguinte forma [1-3]:

10.3.3. Criação da interface gráfica

Para criar a GUI, podemos proceder da seguinte forma:

  • [1]: Adicione um objeto ao pacote [elections.ui.service]
  • [2]: Selecione a opção [JFrame Form] na categoria [Swing GUI Forms]
  • [4]: Dê um nome à classe
  • [5]: O pacote da classe.
  • Conclua o assistente.
  • [6]: A classe gerada
  • [7]: a classe [AbstractElectionsSwing] no modo [Design]
  • [8]: o separador [Navigator] a apresentar a árvore [9] de componentes da janela
  • [10]: o separador [Properties], que apresenta as propriedades do componente [JFrame] selecionado em [9]
  • [11]: [JFrame] é um contentor de componentes. Os componentes podem ser dispostos dentro do contentor de acordo com várias regras de posicionamento denominadas layouts. Aqui, escolhemos o layout [Free Design] [14], que permite que os componentes sejam posicionados livremente dentro do contentor.

Encontramos os componentes na barra de ferramentas chamada Palette:

  • [1]: a paleta
  • [2]: Um componente JLabel é colocado no contentor de componentes
  • Ao clicar com o botão direito do rato sobre ele, podemos aceder a várias propriedades: o seu nome [4], o seu texto [3] ou os seus manipuladores de eventos [5]. Utilizamos [3] para definir o texto [6].
  • [1]: O separador [Propriedades] do componente [JLabel] permite aceder às suas propriedades: a sua posição horizontal [2], posição vertical [3], tipo de letra [4] e texto [5].

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

 
 

Não altere este código a cinzento, pois ele é eliminado e regenerado na próxima vez que guardar. Quaisquer alterações feitas serão, então, perdidas.

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

Vamos agora criar 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 lugares disponíveis
3
JLabel
jLabelSE
o limiar eleitoral
4
JComboBox
jComboBoxListNames
lista de nomes das listas concorrentes
5
JTextField
jTextFieldVotesList
o número de votos para uma lista
6
JLabel
jLabelAdd
para adicionar uma lista a (8)
7,8
(JScrollPane, JList)
jListNamesVoices
os nomes e as vozes das listas
9
JLabel
jLabelDelete
para remover da (8) a lista selecionada em (8)
10
JLabel
jLabelCalculate
para calcular os resultados das eleições
11
JLabel
jLabelClear
para limpar os resultados da eleição
12
JLabel
jLabelSave
para guardar os resultados da eleição
13,14
(JScrollPane, JList)
jListResults
para exibir os resultados eleitorais
15,16
(JScrollPane,
JTextPane)
jTextPaneMessages
para exibir mensagens de acompanhamento

A anotação (JScrollPane, JList) [13-14] serve para indicar que, quando um componente [JList] é arrastado para dentro da janela, é automaticamente inserido num componente [JScrollPane] que permite percorrer a lista. É o componente [JScrollPane] que permite visualizar todos os itens da lista, mesmo que apenas um número limitado deles esteja visível em qualquer momento. O mesmo se aplica ao componente [JTextPane] [15-16].

O menu pode ser criado da seguinte forma:

  • [1, 2]: Um componente [Barra de Menus] é colocado na janela
  • [3]: o menu predefinido, conforme mostrado no separador [Navegador]
  • [4,5,6]: ao clicar com o botão direito do rato numa opção do menu, pode:
    • alterar o seu texto [4], o seu nome [5]
    • gerir os seus eventos [6]
  • [7]: O menu pretendido

O menu pretendido é o seguinte:

Nível 1
Nível 2
Eleições
 
 
Saída
Listas
 
 
Adicionar
 
Eliminar
Resultados
 
 
Calcular
 
Limpar
 
Guardar
Sobre
 

Pode testar a interface gráfica a qualquer momento:

 

Ao criar a interface, deve associar um manipulador de eventos a determinados rótulos e menus [Adicionar, Eliminar, ...]. Veja como fazê-lo:

  • [1]: Clique com o botão direito do rato no componente para o qual pretende gerir um evento
  • [2]: Selecione a opção [Eventos]
  • [3]: Selecione uma categoria de evento
  • [4]: Selecione o evento que pretende gerir

O código Java gerado por esta 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 add your handling code here:
}
  • Linhas 1–5: É adicionado um manipulador de eventos ao componente jLabelCalculer. O método addMouseListener espera como parâmetro uma classe que implemente a seguinte interface MouseListener:
 

A interface MouseListener é implementada por várias classes, incluindo a classe MouseAdapter. Esta classe implementa os cinco métodos da interface MouseListener, mas esses métodos não executam nenhuma ação. Por isso, é necessário criar uma subclasse desta classe para implementar o(s) método(s) da sua escolha. Isto é feito no código acima e ilustrado 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 de classes anónimas descrita na Secção 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 é reescrito (linhas 2–4) para que execute uma ação específica.

O método jLabelCalculerMouseClicked chamado na linha 3 é definido da seguinte forma:


  private void jLabelCalculerMouseClicked(java.awt.event.MouseEvent evt) {
    // TODO add your handling code here:
}

O programador trata o evento "MouseClicked" colocando código neste método.

Todos os manipuladores de eventos são gerados pelo NetBeans desta forma. O programador pode ignorar as linhas de código geradas pelo NetBeans para associar um método ao evento de um componente. Basta colocar o seu código na linha 2 acima. Aqui está um exemplo:


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

Se executar a GUI e clicar no botão [Calcular], aparece uma mensagem na consola:

  • [1]: Clique duas vezes no rótulo [Calcular]
  • [2]: O manipulador de eventos foi executado e gerou as mensagens mouseClicked na consola 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;

Estes componentes são listas que são normalmente configuradas com um tipo T: o tipo dos elementos no modelo apresentado pelos componentes. Este tipo T pode ser qualquer tipo. O valor apresentado no componente de lista é do tipo [String]. Por predefinição, o método [T.toString()] é utilizado para a apresentação. Para controlar melhor o que é apresentado, o tipo T será aqui o tipo String. Portanto, a declaração correta das nossas listas é a seguinte:


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

Conseguimos este resultado modificando uma das propriedades do componente:

10.3.4. Separação de código

Voltemos à estrutura da nossa aplicação:

A classe [AbstractElectionsSwing] deve implementar a camada [ui] acima. O seu código, gerado pelo NetBeans, contém atualmente apenas código de gestão de janelas e manipuladores de eventos que, neste momento, não realizam nenhuma ação. Acima, vemos que a classe [AbstractElectionsSwing] precisará de lidar com as interações com a camada [business]. Este tratamento ocorrerá nos manipuladores de eventos. Para clarificar a estrutura do código, decidimos colocá-lo em duas classes:

  • [AbstractElectionsSwing], que permanecerá tal como gerada pelo NetBeans com algumas pequenas alterações. Esta classe não irá tratar de quaisquer eventos por si própria. Os manipuladores de eventos estarão vazios e serão declarados como abstratos. Serão implementados por uma classe derivada de [AbstractElectionsSwing].
  • [ElectionsSwing], uma classe derivada de [AbstractElectionsSwing] que irá implementar todos os manipuladores de eventos.

Este tipo de separação não é invulgar. Pode ser encontrado, por exemplo, em páginas web ASP.NET (versão não-MVC). O projeto NetBeans evolui da seguinte forma:

 

O código da classe [AbstractElectionsSwing] evolui da seguinte forma:


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();
        }
    }
 
....
    // event managers
    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 abstract
  • linhas 3–5: tratamento do clique na opção de menu [jMenuItemCalculer]. Vemos que o tratamento do evento é delegado ao método doCalculer na linha 19. Este método não está implementado e é declarado como abstrato. Será implementado pela classe derivada [ElectionsSwing];
  • linhas 9–13: o manipulador para o evento de clique no rótulo [jLabelCalculer]. Um clique desencadeia sempre um evento, quer o componente [jLabel] esteja ativo (enabled=true) ou inativo (enabled=false). Aqui, asseguramos que está efetivamente ativo para tratar o evento;
  • linhas 15 e seguintes: esta técnica de delegar o tratamento de eventos a 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 {
 
    // event managers
 
    @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] estende [AbstractElectionsSwing]
  • linhas 7–50: os manipuladores de eventos para a janela gráfica

Os métodos da classe derivada [ElectionsSwing] irão manipular os componentes da classe pai [AbstractElectionsSwing]. Atualmente, estes componentes têm um âmbito privado, impedindo a classe filha [ElectionsSwing] de aceder aos mesmos:


private JMenuItem jMenuItemAPropos = null;
 
private JLabel jLabelAjouter = null;

Para resolver este problema, vamos garantir que o âmbito dos componentes da GUI seja [protected]:

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

10.3.5. Implementação da interface [IElectionsUI]

Voltemos à estrutura da nossa aplicação:

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();
}

Esta interface foi definida no projeto [elections-console-metier-dao-jdbc] e descrita na Secção 9.4. Uma vez que este projeto é uma dependência do projeto [swing], esta interface é conhecida.

Uma vez que a classe [AbstractElectionsSwing] passou a ser abstrata, já não pode ser instanciada pelo Spring. Em vez disso, deve agora ser instanciada a classe [ElectionsSwing]. A classe [ElectionsSwing] deve implementar a interface [IElectionsUI]. Por conseguinte, o seu código passa a ser o seguinte:


public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
 
    // interface [ElectionsUI] run method
    public void run() {
...
    }
 
  • linha 1: a classe [ElectionsSwing] implementa a interface [IElectionsUI]
  • linhas 4–6: o método [run] desta interface

O que deve fazer o método run? Exibir a janela da GUI. Como fazemos isso? Podemos usar o método [main] gerado pelo NetBeans na classe [AbstractElectionsSwing] como guia, que faz exatamente o que queremos:


public static void main(String args[]) {
    /* Set the Nimbus look and feel */
    //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
    /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * 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>
 
    /* Create and display the form */
    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 de GUI. O seu código não pode ser alterado.

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


  @Override
  public void run() {
    // on affiche l'interface graphique
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
}
  • Linha 6: A GUI é inicializada utilizando o método [init]. Aqui, gostaríamos de chamar o método [initComponents] da classe pai, mas este é privado. Por isso, adicionamos o seguinte método [init] à classe pai [AbstractElectionsSwing]:

  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 ter o atributo [protected], é visível na classe filha [ElectionsSwing];
  • linha 7: a GUI é tornada visível;

Nota: assim que o método [run] for escrito na classe [ElectionsSwing], o método [main] da classe abstrata [AbstractElectionsSwing] pode ser removido.

10.3.6. A classe executável

Voltemos à estrutura da nossa aplicação:

Gostaríamos que o Spring instanciasse a camada [ui] tal como foi feito quando esta foi implementada por uma aplicação de consola. Para tal, a classe de implementação [ElectionsSwing] deve ter uma referência à camada [business]:


@Component
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI{
 
  // reference on the [business] layer
  @Autowired
  private IElectionsMetier metier;
...
  • linha 1: a classe [ElectionsSwing] é um componente Spring;
  • linhas 5–6: o Spring injeta uma referência na camada [business];

A interface gráfica do utilizador é iniciada através da 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);
    }
}

Explicámos código semelhante na Secção 9.5, ao discutir 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 do utilizador

Quando a GUI é exibida, alguns dos seus componentes já foram inicializados:

Image

Conforme mostrado acima:

  • que a caixa de combinação foi preenchida com os nomes das listas;
  • que o número de lugares a preencher e o limiar eleitoral são apresentados;
  • que algumas ligações foram desativadas;
  • é exibida uma mensagem de sucesso na parte inferior da janela;

Quando é que estas inicializações ocorrem? Só podem ocorrer após o campo [electionsMetier] da classe [ElectionsSwing] ter sido inicializado. Isto porque os nomes das listas serão solicitados à camada [metier]. O Spring irá inicializar este campo na seguinte ordem:

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

    @Override
    public void run() {
        // on affiche l'interface graphique
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                init();
                setVisible(true);
            }
        });
}
  • Na linha 12, especificámos que iríamos chamar o método [init] da classe pai, que irá desenhar os componentes da GUI. Iremos substituir este método localmente na classe [ElectionsSwing]. É dentro deste método que iremos inicializar os componentes da janela (caixas de combinação, rótulos) com dados desta vez:

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


public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
 
    ...
  // reference on the [business] layer
  @Autowired
  private IElectionsMetier metier;
 
    // initializations
    public void init() {
      // generation of components by the parent class
    super.init();
 
    // lists are requested from the [metier] layer
        ...
        // associate list names with the jComboBoxNomsListes combo
        ...
        // and election parameters
        ...
        // we initialize the labels linked to these two pieces of information
        ...
        // message of success
        ...
        // initialization status of certain components form
...
}

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

10.3.8. A classe [Utilities]

Vários métodos utilitários estáticos foram agrupados na classe [Utilities]:

 

A classe [Utilities] é a seguinte:


package istia.st.elections.ui;
 
import javax.swing.JLabel;
import javax.swing.JMenuItem;
 
//utility class
class Utilitaires {
    // manage label array status
    public static void setEnabled(JLabel[] labels, boolean value) {
        for (int i = 0; i < labels.length; i++) {
            labels[i].setEnabled(value);
        }
    }
 
    // manage the status of a table of menu options
    public static void setEnabled(JMenuItem[] menuItems, boolean value) {
        ...
    }
 
}
  • Linha 9: O método setEnabled define o estado dos componentes JLabel definidos numa matriz. O método setEnabled de um componente JLabel permite ativar ou desativar o JLabel.

Tarefa: Seguindo o exemplo do método setEnabled na linha 9, escreva o método setEnabled na linha 16 que faça o mesmo com os componentes *JMenuItem*.


10.3.9. O código da classe [ElectionsSwing]

Vamos rever a estrutura geral da classe [ElectionsSwing]:


package istia.st.elections.ui;
 
...
public class ElectionsSwing extends AbstractElectionsSwing implements IElectionsUI {
 
  // reference on the [business] layer
  @Autowired
  private IElectionsMetier metier;
 
 
    // initializations
    public void init() {
    ...
    }
 
    // event managers
 
    @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 examinar 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 a caixa de combinação [4] com os IDs e nomes das listas no formato [ID - nome]
  • exibir uma mensagem de sucesso em [15]
  • inicializar os rótulos [2] e [3]
  • desativar determinados links

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


@Override
    protected void init() {
        // génération des composants par la classe parent
        super.init();
        // initialisations locales
        modèleNomsVoix = new DefaultListModel<>();
        jListNomsVoix.setModel(modèleNomsVoix);
        modèleRésultats = new DefaultListModel<>();
        jListResultats.setModel(modèleRésultats);
        String info;
        try {
            // on demande les listes à la couche [métier]
            listes = ...
            // on associe les noms des listes au combo jComboBoxNomsListes
            ...
            // ainsi que les paramètres de l'election
            int nbSiegesAPourvoir = ...
            double seuilElectoral = ...
            // on initialise les labels liés à ces deux informations
            ...
            // message de succès
            info = "Source de données lue avec succès";
        } catch (ElectionsException ex1) {
            // on note l'error
            info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
        } catch (RuntimeException ex2) {
            // on note l'error
            info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
        }
        // on affiche l'info
        jTextPaneMessages.setText(info);
        jTextPaneMessages.setCaretPosition(0);
        // état formulaire
        Utilitaires.setEnabled(new JLabel[] { jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer }, false);
        Utilitaires.setEnabled(
                new JMenuItem[] { jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer }, false);
        // centrer la fenêtre
        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) {
        // on affiche le message
        StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
        info.append(String.format("Code erreur : %d\n", ex.getCode()));
        // on affiche les erreurs
        for (String erreur : ex.getErreurs()) {
            info.append(String.format("-- %s\n", erreur));
        }
        return info.toString();
    }
 
    private String getInfoForException(String message, RuntimeException ex) {
        // on affiche le message
        StringBuffer info = new StringBuffer(String.format("%s -------------\n", message));
        // on affiche la pile des exceptions
        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 para o método [init].


Leia no curso: Componentes JTextField e JLabel

Nota:

Um componente JList apresenta os dados de um modelo. Por predefinição, este modelo é do tipo DefaultListModel (linhas 2 e 3). Um objeto DefaultListModel comporta-se de forma semelhante a um 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 i do modelo:

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

[DefaultListModel].elementAt(int i);

Para adicionar um item à caixa de combinação [jComboBoxNomsListes], utilize o método [addItem]:


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

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

O botão [Adicionar] [6] só está ativo quando o campo [5] para votos não estiver vazio. Na classe [AbstractElectionsSwing], o manipulador que rastreia 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() {
        // on fixe l'état du label [jLabelAjouter]
        ...
        // on fixe l'état du menu [jMenuItemAjouter]
        ...
}

Tarefa: Complete o código para o método [doMajLabelAjouter].


10.3.9.3. Atribua votos a cada lista

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

  • selecione uma lista de (4)
  • introduza o número de votos em (5)
  • confirme clicando na ligação [Adicionar]

Os erros de introdução de dados são assinalados conforme mostrado no exemplo seguinte:

Image

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

Na classe [AbstractElectionsSwing], o manipulador que trata do clique no link [Adicionar] é 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]:


  // modèles des listes JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;
 
  // les listes en compétition
  private ListeElectorale[] listes;
 
  // listes saisies par l'user
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;
...
  @Override
  protected void doAjouter() {
    // le nombre de voix est-il correct ?
    ...
    // si erreur, alors on la signale
    if (erreur) {
      JOptionPane.showMessageDialog(null, "Nombre de voix incorrect", "Elections : erreur",
              JOptionPane.INFORMATION_MESSAGE);
      jTextFieldVoixListe.requestFocus();
      // retour à l'graphic interface
      return;
    }
    // pas d'error - save the list
    listesSaisies.add(...);
    modèleNomsVoix.addElement(...);
    // on nettoie le nombre de voix
    jTextFieldVoixListe.setText("");
    // état formulaire (menus, labels)
...
}
  • Linha 25: Sempre que o utilizador adiciona vozes a uma lista e confirma a sua seleção, essa lista é armazenada no campo [listesSaisies] na linha 9. A lista é guardada nesse local com as informações [id, versão, nome, voz]. As três primeiras informações provêm das listas inicialmente armazenadas na matriz na linha 6. O método [getSelectedIndex] da caixa combinada devolve o índice da lista selecionada;

Tarefa: Complete o código do método [doAjouter].


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

Na classe [AbstractElectionsSwing], o manipulador que responde a um clique num item 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() {
            // on allume le label [jLabelSupprimer] et l'corresponding menu option
            ...
}

Tarefa: Complete o código para o método [doMajLabelSupprimer].


10.3.9.5. Apagar uma lista de candidatos

O link [Delete] [9] permite eliminar o par (nome, voto) selecionado em (8). Assim que a eliminação estiver concluída, o link [Delete] fica desativado. Só voltará a ficar ativado quando for selecionada uma nova lista em (8).

Na classe [AbstractElectionsSwing], o manipulador que responde a um clique no link [Apagar] é 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() {
        // suppression de la liste sélectionnée, du modèle modèleNomsVoix et des listes saisies
        ...
        // maj de l'status of labels and form menu options
        Utilitaires.setEnabled(...);
    ...
}

Trabalho a realizar: completar o código para o método [doSupprimer].


O link [Calculate] [10] só está ativo quando há pelo menos um item em [8].


Tarefa: Adicione o código necessário para tratar deste link nos métodos [doAdd] e [doDelete] escritos anteriormente. A opção de menu correspondente também será tratada.


Nota: O número de itens num DefaultListModel é obtido utilizando o método size().

10.3.9.7. Calcular lugares

O link [Calcular] [10] permite iniciar o cálculo de lugares e apresentar os resultados em (14). Se o cálculo falhar (todas as listas foram eliminadas), é apresentada uma mensagem de erro em [15]. Em qualquer caso, após o cálculo, o link [Calcular] [10] fica desativado.

Na classe [AbstractElectionsSwing], o manipulador que responde a um clique no link [Calcular] é 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].


  // listes saisies par l'utilisateur
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;
 
...
  @Override
  protected void doCalculer() {
    tListesSaisies = listesSaisies.toArray(new ListeElectorale[0]);
    // calcul des sièges
    try {
      ...
    } catch (ElectionsException ex) {
      // on affiche l'exception
      ...
      return;
    }
    // affichage des résultats
    ...
    // maj état formulaire
    Utilitaires.setEnabled(...);
}

Tarefa: Complete o código para o método [doCalculer].


10.3.9.8. Guarde os resultados na fonte de dados

A ligação [Guardar] (12) permite guardar os resultados do cálculo de lugares na fonte de dados. Assim que a gravação for bem-sucedida, a ligação [Guardar] é desativada. Se a gravação falhar, é apresentada uma mensagem de erro em [15]. Em qualquer dos casos, a ligação [Guardar] é então desativada.

Na classe [AbstractElectionsSwing], o manipulador que gere o clique no rótulo [Save] é 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() {
        // on demande l'enregistrement à la couche métier
        try {
            ...
        } catch (ElectionsException ex) {
            // on affiche l'exception
            ...
            // retour à l'interface graphique
            return;
        }
        // maj du formulaire
        Utilitaires.setEnabled(...);
...
}

Tarefa: Complete o código para o método [doEnregistrer].


10.3.9.9. Limpar resultados

O link [Limpar] (11) limpa os resultados apresentados em (14).

Na classe [AbstractElectionsSwing], o manipulador que gere o clique no rótulo [Limpar] é 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() {
        // on vide la liste des résultats
        ....
        // maj du formulaire
        Utilitaires.setEnabled(...);
}

Tarefa: Complete o código para o método [doEffacer].


Nota: A classe DefaultListModel possui um método clear() que remove todos os seus elementos.

10.3.10. Melhorias

A interface gráfica anterior pode ser melhorada de várias formas: o utilizador pode esquecer-se de introduzir os votos para todas as listas presentes na caixa combinada e também pode introduzir acidentalmente os votos para a mesma lista várias vezes.


Tarefa: Melhore o algoritmo para que nenhum destes casos possa ocorrer. Uma solução simples consiste em manter um dicionário das listas introduzidas, em que as chaves são os itens da caixa de combinação. Iremos também garantir que o botão [Calcular] só fica ativado quando todas as listas tiverem sido introduzidas.


Consulte o curso [ref1]: a classe HashTable na secção 3.8.