Skip to content

7. Interfaces gráficas com C# e VS.NET

7.1. Noções básicas sobre interfaces gráficas

7.1.1. Um primeiro projeto

Vamos criar um primeiro projeto do tipo «Aplicação Windows»:

  • [1]: criar um novo projeto
  • [2]: do tipo «Aplicação Windows»
  • [3]: o nome do projeto não é importante por enquanto
  • [4]: o projeto foi criado
  • [5]: guardamos a solução atual
  • [6]: nome do projeto
  • [7]: pasta da solução
  • [8]: nome da solução
  • [9]: será criada uma pasta para a solução [Chap5]. Os projetos desta solução ficarão em subpastas.
  • [10]: o projeto [01] na solução [Chap5]:
  • [Program.cs] é a classe principal do projeto
  • [Form1.cs] é o ficheiro fonte que irá gerir o comportamento da janela [11]
  • [Form1.Designer.cs] é o ficheiro fonte que irá encapsular a informação sobre os componentes da janela [11]
  • [11]: o ficheiro [Form1.cs] no modo «concepção» (design)
  • [12]: a aplicação gerada pode ser executada através de (Ctrl-F5). É apresentada a janela [Form1]. É possível movê-la, redimensioná-la e fechá-la. Temos, assim, os elementos básicos de uma janela gráfica.

A classe principal [Program.cs] é a seguinte:


using System;
using System.Windows.Forms;

namespace Chap5 {
    static class Program {
        /// <summary>
        /// O ponto de entrada principal da aplicação.
        /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
  • linha 2: as aplicações com formulários utilizam o espaço de nomes System.Windows.Forms.
  • linha 4: o espaço de nomes inicial foi renomeado para Chap5.
  • linha 10: ao executar o projeto (Ctrl-F5), o método [Main] é executado.
  • linhas 11-13: a classe Application pertence ao espaço de nomes System.Windows.Forms. Contém métodos estáticos para iniciar/encerrar aplicações gráficas do Windows.
  • linha 11: opcional — permite atribuir diferentes estilos visuais aos controlos inseridos num formulário
  • linha 12: opcional — define o motor de renderização dos textos dos controlos: GDI+ (true), GDI (false)
  • linha 13: a única linha indispensável do método [Main]: instancia a classe [Form1], que é a classe do formulário, e solicita a sua execução.

O ficheiro fonte [Form1.cs] é o seguinte:


using System;
using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
    }
}
  • linha 5: a classe Form1 deriva da classe [System.Windows.Forms.Form], que é a classe-pai de todas as janelas. A palavra-chave «partial» indica que a classe é parcial e que pode ser completada por outros ficheiros-fonte. É o que acontece aqui, onde a classe Form1 está distribuída por dois ficheiros:
  • [Form1.cs]: no qual se encontra o comportamento do formulário, nomeadamente os seus gestores de eventos
  • [Form1.Designer.cs]: no qual se encontram os componentes do formulário e as suas propriedades. Este ficheiro tem a particularidade de ser regenerado sempre que o utilizador altera a janela no modo [conception].
  • linhas 6-8: o construtor da classe Form1
  • linha 7: invoca o método InitializeComponent. Verifica-se que este método não está presente em [Form1.cs]. Encontra-se em [Form1.Designer.cs].

O ficheiro fonte [Form1.Designer.cs] é o seguinte:


namespace Chap5 {
    partial class Form1 {
        /// <summary>
        /// Variável de designer obrigatória.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Limpar quaisquer recursos que estejam a ser utilizados.
        /// </summary>
        /// <param name="disposing">true se os recursos geridos devem ser eliminados; caso contrário, false.</param>
        protected override void Dispose(bool disposing) {
            if (disposing && (components != null)) {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #Código gerado pelo Windows Form Designer

        /// <summary>
        /// Método obrigatório para suporte do Designer — não modificar
        /// o conteúdo deste método com o editor de código.
        /// </summary>
        private void InitializeComponent() {
            this.SuspendLayout();
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(196, 98);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);

        }

         #fim da região

    }
}
  • linha 2: trata-se, mais uma vez, da classe Form1. Note-se que já não é necessário repetir que ela deriva da classe Form.
  • linhas 25-37: o método InitializeComponent chamado pelo construtor da classe [Form1]. Este método irá criar e inicializar todos os componentes do formulário. É regenerado sempre que o formulário é alterado no modo [conception]. É criada uma secção, denominada région, para delimitá-la (linhas 19-39). O programador não deve adicionar código nesta região: este será substituído na próxima regeneração.

É mais simples, numa primeira fase, não se preocupar com o código de [Form1.Designer.cs]. Este é gerado automaticamente e constitui a tradução para a linguagem C# das escolhas que o programador faz no modo [conception]. Vejamos um primeiro exemplo:

  • [1]: selecione o modo [conception] clicando duas vezes no ficheiro [Form1.cs]
  • [2]: clicar com o botão direito do rato no formulário e selecionar [Properties]
  • [3]: a janela de propriedades do [Form1]
  • [4]: a propriedade [Text] representa o título da janela
  • [5]: a alteração da propriedade [Text] é refletida no modo [conception], bem como no código-fonte [Form1.Designer.cs]:

        private void InitializeComponent() {
            this.SuspendLayout();
...
            this.Text = "Mon 1er formulaire";
...
}

7.1.2. Um segundo projeto

7.1.2.1. O formulário

Começamos um novo projeto denominado 02. Para tal, seguimos o procedimento explicado anteriormente para criar um projeto. A janela a criar é a seguinte:

Os componentes do formulário são os seguintes:

n.º
nome
tipo
função
1
labelSaisie
Rótulo
uma descrição
2
textBoxSaisie
TextBox
um campo de introdução de dados
3
buttonAfficher
Botão
para apresentar numa caixa de diálogo o conteúdo do campo de entrada textBoxSaisie

Pode-se proceder da seguinte forma para criar esta janela:

  • [1]: clicar com o botão direito do rato no formulário, fora de qualquer componente, e selecionar a opção [Properties]
  • [2]: a janela de propriedades da janela aparece no canto inferior direito do Visual Studio

Entre as propriedades do formulário a destacar:

BackColor
para definir a cor de fundo da janela
ForeColor
para definir a cor dos desenhos ou do texto na janela
Menu
para associar um menu à janela
Text
para atribuir um título à janela
FormBorderStyle
para definir o tipo de janela
Font
para definir o tipo de letra das entradas na janela
Name
para definir o nome da janela

Aqui, definimos as propriedades Text e Name:

Text
Campos de introdução e botões - 1
Name
frmSaisiesBoutons
  • [1]: selecione a caixa de ferramentas [Common Controls] entre as caixas de ferramentas disponibilizadas pelo Visual Studio
  • [2, 3, 4]: clicar duas vezes, sucessivamente, nos componentes [Label], [Button] e [TextBox]
  • [5]: os três componentes estão no formulário

Para alinhar e dimensionar corretamente os componentes, pode utilizar os elementos da barra de ferramentas:

 
  
   

O princípio da formatação é o seguinte:

  1. selecione os diferentes componentes a formatar em conjunto (mantenha a tecla Ctrl premida enquanto clica para selecionar os componentes)
  2. selecione o tipo de formatação pretendido:
  • (continuação)
    • as opções Align permitem alinhar os componentes pela parte superior, inferior, esquerda ou direita, ou pelo centro
    • as opções «Make Same Size» permitem que os componentes tenham a mesma altura ou a mesma largura
    • a opção «Horizontal Spacing» permite alinhar horizontalmente os componentes com intervalos entre eles de igual largura. O mesmo se aplica à opção «Vertical Spacing» para alinhar verticalmente.
    • A opção Center permite centrar um componente horizontalmente (Horizontally) ou verticalmente (Vertically) na janela

Depois de colocados os componentes, definimos as suas propriedades. Para tal, clique com o botão direito do rato no componente e selecione a opção Properties:

  • [1]: selecione o componente para aceder à sua janela de propriedades. Nesta janela, altere as seguintes propriedades: nome: labelSaisie, texto: Saisie
  • [2]: proceda da mesma forma: nome: textBoxSaisie, texto: não preencha nada
  • [3]: nome: buttonAfficher, texto: Afficher
  • [4]: a própria janela: nome: frmSaisiesBoutons, texto: Campos de introdução de dados e botões - 1
  • [5]: execute (Ctrl-F5) o projeto para obter uma primeira pré-visualização da janela em ação.

O que foi feito no modo [conception] foi traduzido para o código de [Form1.Designer.cs]:


namespace Chap5 {
    partial class frmSaisiesBoutons {
...
        private System.ComponentModel.IContainer components = null;
...
        private void InitializeComponent() {
            this.labelSaisie = new System.Windows.Forms.Label();
            this.buttonAfficher = new System.Windows.Forms.Button();
            this.textBoxSaisie = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // labelSaisie
            // 
            this.labelSaisie.AutoSize = true;
            this.labelSaisie.Location = new System.Drawing.Point(12, 19);
            this.labelSaisie.Name = "labelSaisie";
            this.labelSaisie.Size = new System.Drawing.Size(35, 13);
            this.labelSaisie.TabIndex = 0;
            this.labelSaisie.Text = "Saisie";
            // 
            // buttonAfficher
            // 
            this.buttonAfficher.Location = new System.Drawing.Point(80, 49);
            this.buttonAfficher.Name = "buttonAfficher";
            this.buttonAfficher.Size = new System.Drawing.Size(75, 23);
            this.buttonAfficher.TabIndex = 1;
            this.buttonAfficher.Text = "Afficher";
            this.buttonAfficher.UseVisualStyleBackColor = true;
            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
            // 
            // textBoxSaisie
            // 
            this.textBoxSaisie.Location = new System.Drawing.Point(80, 19);
            this.textBoxSaisie.Name = "textBoxSaisie";
            this.textBoxSaisie.Size = new System.Drawing.Size(100, 20);
            this.textBoxSaisie.TabIndex = 2;
            // 
            // frmSaisiesBoutons
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(292, 118);
            this.Controls.Add(this.textBoxSaisie);
            this.Controls.Add(this.buttonAfficher);
            this.Controls.Add(this.labelSaisie);
            this.Name = "frmSaisiesBoutons";
            this.Text = "Saisies et boutons - 1";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        private System.Windows.Forms.Label labelSaisie;
        private System.Windows.Forms.Button buttonAfficher;
        private System.Windows.Forms.TextBox textBoxSaisie;

    }
}
  • linhas 53-55: os três componentes deram origem a três campos privados da classe [Form1]. Note-se que os nomes destes campos são os nomes atribuídos aos componentes no modo [conception]. O mesmo se aplica ao formulário da linha 2, que é a própria classe.
  • linhas 7-9: são criados os três objetos do tipo [Label], [TextBox] e [Button]. É através deles que os componentes visuais são geridos.
  • linhas 14-19: configuração do rótulo labelSaisie
  • linhas 23-29: configuração do botão buttonAfficher
  • linhas 33-36: configuração do campo de introdução de dados textBoxSaisie
  • linhas 40-47: configuração do formulário frmSaisiesBoutons. Note-se, nas linhas 43-45, a forma de adicionar componentes ao formulário.

Este código é compreensível. Desta forma, é possível criar formulários através de código sem utilizar o modo [conception]. São apresentados vários exemplos disto na documentação MSDN do Visual Studio. Dominar este código permite criar formulários em tempo de execução: por exemplo, criar dinamicamente um formulário que permita a atualização de uma tabela de base de dados, sendo que a estrutura dessa tabela só é descoberta durante a execução.

Resta-nos escrever o procedimento para gerir um clique no botão Afficher. Selecione o botão para aceder à sua janela de propriedades. Esta janela tem várias separadores:

  • [1]: lista de propriedades por ordem alfabética
  • [2]: eventos relacionados com o controlo

As propriedades e os eventos de um controlo estão acessíveis por categorias ou por ordem alfabética:

  • [3]: Propriedades ou eventos por categoria
  • [4]: Propriedades ou eventos por ordem alfabética

O separador Events no modo Catégories para o botão buttonAfficher é o seguinte:

  • [1]: a coluna da esquerda da janela lista os eventos possíveis no botão. Um clique num botão corresponde ao evento Click.
  • [2]: a coluna da direita contém o nome do procedimento chamado quando o evento correspondente ocorre.
  • [3]: se clicar duas vezes na célula do evento Click, passa-se automaticamente para a janela de código para escrever o gestor do evento Click no botão buttonAfficher:

using System;
using System.Windows.Forms;

namespace Chap5 {
    public partial class frmSaisiesBoutons : Form {
        public frmSaisiesBoutons() {
            InitializeComponent();
        }

        private void buttonAfficher_Click(object sender, EventArgs e) {

        }
    }
}
  • linhas 10-12: o esboço do manipulador do evento Click no botão denominado buttonAfficher. É importante notar os seguintes pontos:
    • o método é nomeado de acordo com o esquema nomDuComposant_NomEvénement
    • o método é privado. Recebe dois parâmetros:
    • sender: é o objeto que provocou o evento. Se o procedimento for executado na sequência de um clique no botão buttonAfficher, sender será igual a buttonAfficher. É possível imaginar que o procedimento buttonAfficher_Click seja executado a partir de outro procedimento. Este último teria, então, toda a liberdade para definir como primeiro parâmetro o objeto sender da sua escolha.
    • EventArgs: um objeto que contém informações sobre o evento. Para um evento Click, não contém nada. Para um evento relacionado com os movimentos do rato, encontrar-se-ão as coordenadas (X, Y) do rato.
    • Não utilizaremos nenhum destes parâmetros aqui.

Escrever um gestor de eventos consiste em preencher o esboço de código anterior. Aqui, pretendemos apresentar uma caixa de diálogo com, no seu interior, o conteúdo do campo textBoxSaisie, caso este não esteja vazio ([1]); caso contrário, uma mensagem de erro ([2]):

O código que permite fazer isto poderia ser o seguinte:


        private void buttonAfficher_Click(object sender, EventArgs e) {
            // é exibido o texto que foi introduzido no TextBox textboxSaisie
            string texte = textBoxSaisie.Text.Trim();
            if (texte.Length != 0) {
                MessageBox.Show("Texte saisi= " + texte, "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information);
            } else {
                MessageBox.Show("Saissez un texte...", "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

A classe MessageBox serve para apresentar mensagens numa janela. Utilizámos aqui o seguinte método Show:


public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);

com

text
a mensagem a apresentar
caption
o título da janela
buttons
os botões presentes na janela
icon
o ícone presente na janela

O parâmetro buttons pode assumir valores entre as seguintes constantes (prefixadas por MessageBoxButtons, conforme mostrado na linha 7) acima:

constante
botões
   AbortRetryIgnore 
  OK 
    OKCancel 
    RetryCancel 
    YesNo 
    YesNoCancel 

O parâmetro icon pode assumir valores entre as seguintes constantes (prefixadas por MessageBoxIcon, conforme mostrado na linha 10) acima:

Asterisk
Error
idem Stop
Exclamation
idem Aviso
Hand
Information
idem Asterisk
None
Question
Stop
idem Hand
Warning

 

O método Show é um método estático que devolve um resultado do tipo [System.Windows.Forms.DialogResult], que é uma enumeração:

Image

Para saber em que botão o utilizador clicou para fechar a janela do tipo MessageBox, escrever-se-á:

DialogResult res=MessageBox.Show(..);
if (res==DialogResult.Yes){ // ele clicou no botão «Sim»...}

7.1.2.2. O código relacionado com a gestão de eventos

Para além da função buttonAfficher_Click que escrevemos, o Visual Studio gerou, no método InitializeComponents de [Form1.Designer.cs] — que cria e inicializa os componentes do formulário —, a seguinte linha:


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

«Click» é um evento da classe Button [1, 2, 3]:

  • [5]: a declaração do evento [Control.Click] [4]. Assim, verifica-se que o evento Click não é específico da classe [Button]. Pertence à classe [Control], classe pai da classe [Button].
    • EventHandler é um protótipo (um modelo) de método denominado «delegate». Voltaremos a este assunto mais tarde.
    • event é uma palavra-chave que restringe as funcionalidades do delegate e do EventHandler: um objeto delegate possui funcionalidades mais abrangentes do que um objeto event.

O delegate EventHandler é definido da seguinte forma:

 

O delegate EventHandler designa um modelo de método:

  • cujo primeiro parâmetro é do tipo Object
  • cujo segundo parâmetro é do tipo EventArgs
  • que não devolve qualquer resultado

É o caso do método de gestão do clique no botão buttonAfficher, que foi gerado pelo Visual Studio:


        private void buttonAfficher_Click(object sender, EventArgs e);

Assim, o método buttonAfficher_Click corresponde ao protótipo definido pelo tipo EventHandler. Para criar um objeto do tipo EventHandler, deve-se proceder da seguinte forma:

EventHandler evtHandler=new EventHandler(méthode correspondant au prototype  défini par le type EventHandler);

Uma vez que o método buttonAfficher_Click corresponde ao protótipo definido pelo tipo EventHandler, pode escrever-se:

EventHandler evtHandler=new EventHandler(buttonAfficher_Click);

Uma variável do tipo delegate é, na verdade, uma lista de referências a métodos do tipo delegate. Para adicionar um novo método M à variável evtHandler acima, utilizar-se-á a sintaxe:

evtHandler+=new EvtHandler(M);

A notação += pode ser utilizada mesmo que evtHandler seja uma lista vazia.

Voltemos à linha de [InitializeComponent], que adiciona um gestor de eventos ao evento Click do objeto buttonAfficher:


            this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);

Esta instrução adiciona um método do tipo EventHandler à lista de métodos do campo buttonAfficher.Click. Estes métodos serão chamados sempre que o evento Click no componente buttonAfficher for detetado. Muitas vezes, existe apenas um. É-lhe dado o nome de «gestor do evento».

Voltemos à assinatura de EventHandler:


        private delegate void EventHandler(object sender, EventArgs e);

O segundo parâmetro do delegate é um objeto do tipo EventArgs ou de uma classe derivada. O tipo EventArgs é muito genérico e, na verdade, não fornece qualquer informação sobre o evento que ocorreu. Para um clique num botão, isso é suficiente. Para um movimento do rato num formulário, teríamos um evento MouseMove da classe [Form] definido por:

public event MouseEventHandler MouseMove;

O delegate MouseEventHandler é definido como:

 

Trata-se de uma função delegada (delegate) com assinatura void f (object, MouseEventArgs). A classe MouseEventArgs é definida por:

A classe MouseEventArgs é mais rica do que a classe EventArgs. É possível, por exemplo, obter as coordenadas X e Y do rato no momento em que o evento ocorre.

7.1.2.3. Conclusion

A partir dos dois projetos analisados, podemos concluir que, uma vez construída a interface gráfica com o Visual Studio, o trabalho do programador consiste principalmente em escrever os gestores de eventos que pretende gerir para essa interface gráfica. O código é gerado automaticamente pelo Visual Studio. Este código, que pode ser complexo, pode ser ignorado numa primeira abordagem. Posteriormente, o seu estudo pode permitir uma melhor compreensão da criação e gestão de formulários.

7.2. Os componentes básicos

Apresentamos agora várias aplicações que utilizam os componentes mais comuns, com o objetivo de descobrir os seus principais métodos e propriedades. Para cada aplicação, apresentamos a interface gráfica e o código relevante, principalmente o dos gestores de eventos.

7.2.1. Formulário Form

Começamos por apresentar o componente indispensável: o formulário, no qual se colocam os componentes. Já apresentámos algumas das suas propriedades básicas. Aqui, vamos debruçar-nos sobre alguns eventos importantes de um formulário.

Load
o formulário está a ser carregado
Closing
o formulário está a ser fechado
Closed
O formulário está fechado

O evento Load ocorre antes mesmo de o formulário ser apresentado. O evento Closing ocorre quando o formulário está a ser fechado. Ainda é possível interromper este encerramento por programação.

Criamos um formulário com o nome Form1 sem componentes:

  • [1]: o formulário
  • [2]: os três eventos tratados

O código de [Form1.cs] é o seguinte:


using System;
using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            // carregamento inicial do formulário
            MessageBox.Show("Evt Load", "Load");
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            // o formulário está a fechar-se
            MessageBox.Show("Evt FormClosing", "FormClosing");
            // é solicitada a confirmação
            DialogResult réponse = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (réponse == DialogResult.No)
                e.Cancel = true;
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
            // o formulário vai ser fechado
            MessageBox.Show("Evt FormClosed", "FormClosed");
        }
    }
}

Utilizamos a função MessageBox para sermos notificados sobre os diferentes eventos.

linha 10: O evento Load irá ocorrer no arranque da
aplicação, antes mesmo de o formulário ser apresentado:
  
linha 15: O evento FormClosing ocorrerá quando
o utilizador fechar a janela.
linha 19: Perguntamos-lhe então se deseja mesmo sair
a aplicação:
linha 20: Se ele responder «Não», definimos a propriedade Cancel do
evento CancelEventArgs e que o método recebeu como
parâmetro. Se definirmos esta propriedade como False, o encerramento
da janela é cancelada; caso contrário, prossegue. O evento
FormClosed irá então ocorrer:

7.2.2. Etiquetas e caixas de entrada TextBox

Já nos deparámos com estes dois componentes. Label é um componente de texto e TextBox um componente de campo de introdução de dados. A sua principal propriedade é Text, que designa quer o conteúdo do campo de introdução de dados, quer o texto da legenda. Esta propriedade é de leitura/escrita.

O evento normalmente utilizado para o TextBox é o TextChanged, que sinaliza que o utilizador alterou o campo de introdução de dados. Aqui está um exemplo que utiliza o evento TextChanged para acompanhar as alterações num campo de introdução de dados:

n.º
tipo
nome
função
1
TextBox
textBoxSaisie
campo de introdução
2
Rótulo
labelControle
exibe o texto de 1 em tempo real
AutoSize=False, Text=(nada)
3
Botão
buttonEffacer
para apagar os campos 1 e 2
4
Botão
buttonQuitter
para sair da aplicação

O código desta aplicação é o seguinte:


using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
            // o conteúdo de TextBox foi alterado — está a ser copiado para o rótulo labelControle
            labelControle.Text = textBoxSaisie.Text;
        }

        private void buttonEffacer_Click(object sender, System.EventArgs e) {
            // apaga-se o conteúdo do campo de introdução
            textBoxSaisie.Text = "";
        }

        private void buttonQuitter_Click(object sender, System.EventArgs e) {
            // clique no botão Sair - sai-se da aplicação
            Application.Exit();
        }

        private void Form1_Shown(object sender, System.EventArgs e) {
            // coloca-se o foco no campo de introdução
            textBoxSaisie.Focus();
        }
    }
}
  • linha 24: o evento [Form].Shown ocorre quando o formulário é apresentado
  • linha 26: o foco é então colocado (para introdução de dados) no componente textBoxSaisie.
  • linha 9: o evento [TextBox].TextChanged ocorre sempre que o conteúdo de um componente TextBox muda
  • linha 11: o conteúdo do componente [TextBox] é copiado para o componente [Label]
  • linha 14: gere o clique no botão [Effacer]
  • linha 16: insere-se a cadeia vazia no componente [TextBox]
  • linha 19: gere o clique no botão [Quitter]
  • linha 21: para encerrar a aplicação em execução. Recorde-se que o objeto Application serve para iniciar a aplicação no método [Main] de [Form1.cs]:

        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
}

O exemplo seguinte utiliza um TextBox com várias linhas:

A lista de controlos é a seguinte:

n.º
tipo
nome
função
1
TextBox
textBoxLignes
campo de introdução de texto com várias linhas
Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True
2
TextBox
textBoxLigne
campo de introdução de texto de uma linha
3
Botão
buttonAjouter
Adiciona o conteúdo de 2 a 1

Para que um TextBox passe a ser multilinha, definem-se as seguintes propriedades do controlo:

Multiline=true
para aceitar várias linhas de texto
ScrollBars=( None, Horizontal, Vertical, Both)
para definir se o controlo deve ter barras de deslocamento (Horizontal, Vertical, Both) ou não (None)
AcceptReturn=(True, False)
se for igual a «true», a tecla Enter irá passar para a linha seguinte
AcceptTab=(True, False)
se for igual a «true», a tecla Tab irá inserir uma tabulação no texto

A aplicação permite digitar linhas diretamente em [1] ou adicioná-las através de [2] e [3].

O código da aplicação é o seguinte:


using System.Windows.Forms;
using System;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void buttonAjouter_Click(object sender, System.EventArgs e) {
            // adiciona o conteúdo de textBoxLigne ao de textBoxLignes
            textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
            textBoxLigne.Text = "";
        }

        private void Form1_Shown(object sender, EventArgs e) {
            // coloca-se o foco no campo de introdução
            textBoxLigne.Focus();
        }
    }
}
  • linha 18: quando o formulário é apresentado (eventualmente Shown), coloca-se o foco no campo de introdução de dados textBoxLigne
  • linha 10: gere o clique no botão [Ajouter]
  • linha 12: o texto do campo de entrada textBoxLigne é adicionado ao texto do campo de entrada textBoxLignes, seguido de um salto de linha.
  • linha 13: o campo de introdução de dados textBoxLigne é apagado

7.2.3. Listas suspensas ComboBox

Criamos o seguinte formulário:

n.º
tipo
nome
função
1
ComboBox
comboNombres
contém cadeias de caracteres
DropDownStyle=DropDownList

Um componente ComboBox é uma lista suspensa acompanhada de um campo de introdução de texto: o utilizador pode escolher um elemento em (2) ou introduzir texto em (1). Existem três tipos de ComboBox definidos pela propriedade DropDownStyle:

Simple
lista não suspensa com campo de edição
DropDown
lista suspensa com área de edição
DropDownList
lista suspensa sem campo de edição

Por predefinição, o tipo de um ComboBox é DropDown.

A classe ComboBox tem um único construtor:

new ComboBox()
cria uma lista suspensa vazia

Os elementos do ComboBox estão disponíveis na propriedade Items:

public ComboBox.ObjectCollection Items {get;}

Trata-se de uma propriedade indexada, em que Items[i] designa o elemento i da lista suspensa. É uma propriedade de leitura apenas.

Seja C um combo e C.Items a sua lista de elementos. Temos as seguintes propriedades:

C.Items.Count
número de elementos do combo
C.Items[i]
elemento i da lista suspensa
C.Add(object o)
adiciona o objeto o como último elemento da lista suspensa
C.AddRange(object[] objets)
adiciona uma matriz de objetos ao final da lista suspensa
C.Insert(int i, object o)
adiciona o objeto o na posição i da lista suspensa
C.RemoveAt(int i)
remove o elemento i da lista suspensa
C.Remove(object o)
remove o objeto o da lista suspensa
C.Clear()
elimina todos os elementos da lista suspensa
C.IndexOf(object o)
retorna a posição i do objeto o na lista suspensa
C.SelectedIndex
índice do elemento selecionado
C.SelectedItem
elemento selecionado
C.SelectedItem.Text
texto exibido do elemento selecionado
C.Text
texto exibido do elemento selecionado

Pode parecer surpreendente que um combo possa conter objetos, quando visualmente apresenta cadeias de caracteres. Se um ComboBox contiver um objeto obj, apresenta a cadeia obj.ToString(). Recorde-se que todo o objeto possui um método ToString herdado da classe object, que devolve uma cadeia de caracteres «representativa» do objeto.

O elemento Item selecionado na lista suspensa C é C.SelectedItem ou C.Items[C.SelectedIndex], em que C.SelectedIndex é o n.º do elemento selecionado, começando por zero para o primeiro elemento. O texto selecionado pode ser obtido de várias formas: C.SelectedItem.Text, C.Text

Ao selecionar um elemento na lista suspensa, ocorre o evento SelectedIndexChanged, que pode então ser utilizado para ser notificado da alteração da seleção na lista suspensa. Na aplicação seguinte, utilizamos este evento para apresentar o elemento que foi selecionado na lista.

 

O código da aplicação é o seguinte:


using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        private int previousSelectedIndex=0;

        public Form1() {
            InitializeComponent();
            // preenchimento da lista suspensa
            comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
            // seleção do elemento n.º 0
            comboBoxNombres.SelectedIndex = 0;
        }

        private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
            int newSelectedIndex = comboBoxNombres.SelectedIndex;
            if (newSelectedIndex != previousSelectedIndex) {
                // o elemento selecionado mudou — exibe-se
                MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
                // regista-se o novo índice
                previousSelectedIndex = newSelectedIndex;
            }
        }
    }
}
  • linha 5: previousSelectedIndex armazena o último índice selecionado na lista suspensa
  • linha 10: preenchimento da lista suspensa com uma matriz de cadeias de caracteres
  • linha 12: o primeiro elemento é selecionado
  • linha 15: o método executado sempre que o utilizador seleciona um elemento da lista suspensa. Ao contrário do que o nome do evento possa sugerir, este ocorre mesmo que o elemento selecionado seja o mesmo que o anterior.
  • linha 16: regista-se o índice do elemento selecionado
  • linha 17: se for diferente do anterior
  • linha 19: exibe-se o número e o texto do elemento selecionado
  • linha 21: regista-se o novo índice

7.2.4. Componente ListBox

Propõe-se a construção da seguinte interface:

Os componentes desta janela são os seguintes:

n.º
tipo
nome
função/propriedades
0
Form
Form1
formulário
FormBorderStyle=FixedSingle (moldura não redimensionável)
1
TextBox
textBoxSaisie
campo de introdução de dados
2
Botão
buttonAjouter
botão que permite adicionar o conteúdo do campo de introdução de dados [1] à lista [3]
3
ListBox
listBox1
lista 1
SelectionMode=MultiExtended:
4
ListBox
listBox2
lista 2
SelectionMode=MultiSimple:
5
Botão
botão1para2
transfere os elementos selecionados da lista 1 para a lista 2
6
Botão
botão2para1
faz o inverso
7
Botão
buttonEffacer1
esvazia a lista 1
8
Botão
buttonEffacer2
esvazia a lista 2

Os componentes ListBox têm um modo de seleção dos seus elementos que é definido pela sua propriedade SelectionMode:

One
só é possível selecionar um único elemento
MultiExtended
é possível a seleção múltipla: manter premida a tecla SHIFT e clicar num elemento alarga a seleção do elemento selecionado anteriormente ao elemento atual.
MultiSimple
É possível a seleção múltipla: um elemento é selecionado/desmarcado com um clique do rato ou premindo a barra de espaço.
  • O utilizador digita texto no campo 1. Adiciona-o à lista 1 com o botão Ajouter (2). O campo de introdução (1) é então esvaziado e o utilizador pode adicionar um novo elemento.
  • Pode transferir elementos de uma lista para outra, selecionando o elemento a transferir numa das listas e escolhendo o botão de transferência adequado 5 ou 6. O elemento transferido é adicionado ao final da lista de destino e removido da lista de origem.
  • Pode clicar duas vezes num elemento da lista 1. Esse elemento é então transferido para a caixa de introdução de dados para edição e removido da lista 1.

Os botões ficam ativos ou inativos de acordo com as seguintes regras:

  • o botão Ajouter só fica aceso se houver texto não vazio no campo de introdução
  • o botão [5] de transferência da lista 1 para a lista 2 só fica ativado se houver um elemento selecionado na lista 1
  • o botão [6] para transferência da lista 2 para a lista 1 só fica aceso se houver um elemento selecionado na lista 2
  • os botões [7] e [8] para apagar as listas 1 e 2 só ficam acesos se a lista a apagar contiver elementos.

Nas condições anteriores, todos os botões devem estar desativados no arranque da aplicação. É a propriedade Enabled dos botões que deve, então, ser definida como false. Isto pode ser feito na fase de conceção, o que terá como efeito a geração do código correspondente no método InitializeComponent, ou pode ser feito manualmente no construtor, como se segue:


        public Form1() {
            InitializeComponent();
            // --- inicializações complementares ---
            // desativam-se alguns botões
            buttonAjouter.Enabled = false;
            button1vers2.Enabled = false;
            button2vers1.Enabled = false;
            buttonEffacer1.Enabled = false;
            buttonEffacer2.Enabled = false;
}

O estado do botão Ajouter é controlado pelo conteúdo do campo de introdução de dados. É o evento TextChanged que nos permite acompanhar as alterações desse conteúdo:


        private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
            // o conteúdo de textBoxSaisie foi alterado
            // o botão «Adicionar» só fica ativo se o campo de introdução de dados não estiver vazio
            buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
        }

O estado dos botões de transferência depende do facto de ter sido ou não selecionado um elemento na lista que controlam:


        private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
            // foi selecionado um elemento
            // o botão de transferência de 1 para 2 fica ativo
            button1vers2.Enabled = true;
        }

        private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
            // foi selecionado um elemento
            // o botão de transferência de 2 para 1 fica ativo
            button2vers1.Enabled = true;
}

O código associado ao clique no botão Ajouter é o seguinte:


        private void buttonAjouter_Click(object sender, System.EventArgs e) {
            // adicionado um novo elemento à lista 1
            listBox1.Items.Add(textBoxSaisie.Text.Trim());
            // limpar a entrada
            textBoxSaisie.Text = "";
            // A Lista 1 não está vazia
            buttonEffacer1.Enabled = true;
            // o foco volta para a caixa de introdução de dados
            textBoxSaisie.Focus();
}

De salientar o método Focus, que permite colocar o «foco» num controlo do formulário. O código associado ao clique nos botões Effacer:


        private void buttonEffacer1_Click(object sender, System.EventArgs e) {
            // a Lista 1 é apagada
            listBox1.Items.Clear();
            // botão «Apagar»
            buttonEffacer1.Enabled = false;
        }

        private void buttonEffacer2_Click(object sender, System.EventArgs e) {
            // limpar a lista 2
            listBox2.Items.Clear();
            // botão «Apagar»
            buttonEffacer2.Enabled = false;
}

O código para transferir os elementos selecionados de uma lista para a outra:


        private void button1vers2_Click(object sender, System.EventArgs e) {
            // transferência do elemento selecionado da Lista 1 para a Lista 2
            transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
        }

        private void button2vers1_Click(object sender, System.EventArgs e) {
            // transferência do elemento selecionado da Lista 2 para a Lista 1
            transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
        }

Os dois métodos acima delegam a transferência dos elementos selecionados de uma lista para outra a um único método privado denominado «transferência»:


        // transferência
        private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
            // transferência para a lista l2 dos elementos selecionados da lista l1
            for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
                // índice do elemento selecionado
                int index = l1.SelectedIndices[i];
                // adição à l2
                l2.Items.Add(l1.Items[index]);
                // eliminação da lista l1
                l1.Items.RemoveAt(index);
            }
            // botões «Apagar»
            buttonEffacer2.Enabled = l2.Items.Count != 0;
            buttonEffacer1.Enabled = l1.Items.Count != 0;
            // botões de transferência
            button1vers2.Enabled = false;
}
  • linha b: o método «transfert» recebe seis parâmetros:
  • uma referência à lista que contém os elementos selecionados, aqui designada por l1. Durante a execução da aplicação, l1 é ou listBox1 ou listBox2. Podem ver-se exemplos de chamadas nas linhas 3 e 8 dos procedimentos de transferência buttonXversY_Click.
  • uma referência ao botão de transferência associado à lista l1. Por exemplo, se l1 for listBox2, será button2vers1 (ver chamada na linha 8)
  • uma referência no botão de limpeza da lista l1. Por exemplo, se l1 for listBox1, será buttonEffacer1 (ver chamada na linha 3)
  • as outras três referências são análogas, mas referem-se à lista l2.
  • linha d: a coleção [ListBox].SelectedIndices representa os índices dos elementos selecionados no componente [ListBox]. Trata-se de uma coleção:
  • [ListBox].SelectedIndices.Count é o número de elementos desta coleção
  • [ListBox].SelectedIndices[i] é o elemento n.º i desta coleção

Percorremos a coleção no sentido inverso: começamos pelo fim da coleção para terminarmos pelo início. Explicaremos porquê.

  • linha f: índice de um elemento selecionado da lista l1
  • linha h: este elemento é adicionado à lista l2
  • linha j: e removido da lista l1. Como foi removido, deixa de estar selecionado. A coleção l1.SelectedIndices da linha d será recalculada. Vai perder o elemento que acabou de ser removido. Todos os elementos que se seguem a este verão o seu número passar de n para n-1.
  • Se o ciclo da linha (d) for crescente e tiver acabado de processar o elemento n.º 0, passará a processar o elemento n.º 1. No entanto, o elemento que tinha o n.º 1 antes da eliminação do elemento n.º 0 passará a ter o n.º 0. Será, então, ignorado pelo ciclo.
  • Se o ciclo da linha (d) for decrescente e tiver acabado de processar o elemento n.º n, passará a processar o elemento n.º n-1. Após a eliminação do elemento n.º n, o elemento n.º n-1 não altera o seu número. É, portanto, processado na volta seguinte do ciclo.
  • linhas m-n: o estado dos botões [Effacer] depende da presença ou ausência de elementos nas listas associadas
  • linha p: a lista l2 já não tem elementos selecionados: desativa-se o seu botão de transferência.

7.2.5. Caixas de seleção CheckBox, botões de opção ButtonRadio

Propomos escrever a seguinte aplicação:

Os componentes da janela são os seguintes:

n.º
tipo
nome
função
1
GroupBox
ver [6]
groupBox1
um contentor de componentes. É possível colocar outros componentes neste contentor.
Texto=Botões de opção
2
RadioButton
radioButton1
radioButton2
radioButton3
3 botões de opção - radioButton1 tem a propriedade Checked=True e a propriedade Text=1 - radioButton2 tem a propriedade Text=2 - radioButton3 tem a propriedade Text=3
Os botões de opção presentes num mesmo contentor, neste caso o GroupBox, são mutuamente exclusivos: apenas um deles está selecionado.
3
GroupBox
groupBox2
 
4
CheckBox
checkBox1
checkBox2
checkBox3
3 caixas de seleção. chechBox1 tem a propriedade Checked=True e a propriedade Text=A - chechBox2 tem a propriedade Text=B - chechBox3 tem a propriedade Text=C
5
ListBox
listBoxValeurs
uma lista que apresenta os valores dos botões de opção e das caixas de seleção assim que ocorre uma alteração.
6
  
mostra onde encontrar o contentor GroupBox

O evento que nos interessa para estes seis controlos é o evento CheckChanged, que indica que o estado da caixa de seleção ou do botão de opção mudou. Este estado é representado, em ambos os casos, pela propriedade booleana Checked, cujo valor «verdadeiro» significa que o controlo está marcado. Aqui, utilizaremos apenas um único método para tratar os seis eventos CheckChanged: o método affiche. Para garantir que os seis eventos CheckChanged sejam geridos pelo mesmo método affiche, podemos proceder da seguinte forma:

Selecione o componente radioButton1 e clique com o botão direito do rato para aceder às suas propriedades:

No separador événements [1], associamos o método affiche [2] ao evento CheckChanged. Isto significa que se pretende que o clique na opção A1 seja tratado por um método denominado affiche. O Visual Studio gera automaticamente o método affiche na janela de código:


private void affiche(object sender, EventArgs e) {
        }

O método affiche é um método do tipo EventHandler.

Para os outros cinco componentes, procede-se da mesma forma. Selecionemos, por exemplo, a opção CheckBox1 e os seus eventos [3]. Ao lado do evento Click, existe uma lista suspensa [4] na qual constam os métodos existentes capazes de processar esse evento. Aqui, existe apenas o método affiche.. Selecionamo-lo. Repetimos este processo para todos os restantes componentes.

No método InitializeComponent, foi gerado o código. O método affiche foi declarado como gestor dos seis eventos CheckedChanged da seguinte forma:


this.radioButton1.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton2.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton3.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox1.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox2.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox3.CheckedChanged += new System.EventHandler(this.affiche);

O método affiche é preenchido da seguinte forma:


        private void affiche(object sender, System.EventArgs e) {
            // mostra o estado do botão de opção ou da caixa de seleção
            // é uma caixa de seleção?
            if (sender is CheckBox) {
                CheckBox chk = (CheckBox)sender;
                listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
            }
            // é um botão de opção?
            if (sender is RadioButton) {
                RadioButton rdb = (RadioButton)sender;
                listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
            }
}

A sintaxe


            if (sender is CheckBox) {

permite verificar se o objeto sender é do tipo CheckBox. Isto permite-nos, em seguida, efetuar uma conversão de tipo para o tipo exato de sender. O método affiche insere na lista listBoxValeurs o nome do componente que originou o evento e o valor da sua propriedade Checked. Na execução de [7], verifica-se que um clique num botão de opção provoca dois eventos CheckChanged: um no antigo botão marcado, que passa para «desmarcado», e outro no novo botão, que passa para «marcado».

7.2.6. Variadores ScrollBar

Existem vários tipos de variadores:
o variador horizontal (HscrollBar),
o variador vertical (VscrollBar),
o incrementador (NumericUpDown).

Vamos criar a seguinte aplicação:

n.º
tipo
nome
função
1
hScrollBar
hScrollBar1
um variador horizontal
2
hScrollBar
hScrollBar2
um variador horizontal que acompanha as variações do variador 1
3
Etiqueta
labelValeurHS1
exibe o valor do variador horizontal
4
NumericUpDown
numericUpDown2
permite definir o valor do regulador 2

Um controlador ScrollBar permite ao utilizador selecionar um valor num intervalo de valores inteiros, simbolizado pela «faixa» do controlador, na qual se desloca um cursor. O valor do controlador está disponível na sua propriedade Value.

  • Num regulador horizontal, a extremidade esquerda representa o valor mínimo do intervalo, a extremidade direita o valor máximo e o cursor o valor atual selecionado. Num regulador vertical, o mínimo é representado pela extremidade superior e o máximo pela extremidade inferior. Estes valores são representados pelas propriedades Minimum e Maximum e têm, por predefinição, os valores 0 e 100.
  • Um clique nas extremidades do regulador altera o valor num incremento (positivo ou negativo), dependendo da extremidade clicada, designada por SmallChange, cujo valor predefinido é 1.
  • Um clique em qualquer um dos lados do cursor faz com que o valor varie num incremento (positivo ou negativo), dependendo da extremidade clicada, designada por LargeChange, cujo valor predefinido é 10.
  • Quando se clica na extremidade superior de um regulador vertical, o seu valor diminui. Isto pode surpreender o utilizador comum, que normalmente espera ver o valor «subir». Este problema é resolvido atribuindo um valor negativo às propriedades SmallChange e LargeChange
  • Estas cinco propriedades (Value, Minimum, Maximum, SmallChange, LargeChange) estão acessíveis para leitura e escrita.
  • O evento principal do variador é aquele que sinaliza uma alteração de valor: o evento Scroll.

Um componente NumericUpDown encontra-se próximo do variador: também possui as propriedades Minimum, Maximum e Value, com valores por predefinição 0, 100, 0. Mas, neste caso, a propriedade Value é apresentada numa caixa de entrada que faz parte integrante do controlo. O utilizador pode alterar este valor por si próprio, a menos que a propriedade ReadOnly do controlo tenha sido definida como «verdadeiro». O valor do incremento é definido pela propriedade Increment, cujo valor predefinido é 1. O evento principal do componente NumericUpDown é aquele que sinaliza uma alteração de valor: o evento ValueChanged

O código da aplicação é o seguinte:


using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
            // definem-se as características do variador 1
            hScrollBar1.Value = 7;
            hScrollBar1.Minimum = 1;
            hScrollBar1.Maximum = 130;
            hScrollBar1.LargeChange = 11;
            hScrollBar1.SmallChange = 1;
            // atribuem-se ao variador 2 as mesmas características que ao variador 1
            hScrollBar2.Value = hScrollBar1.Value;
            hScrollBar2.Minimum = hScrollBar1.Minimum;
            hScrollBar2.Maximum = hScrollBar1.Maximum;
            hScrollBar2.LargeChange = hScrollBar1.LargeChange;
            hScrollBar2.SmallChange = hScrollBar1.SmallChange;
            // o mesmo se aplica ao incrementador
            numericUpDown2.Value = hScrollBar1.Value;
            numericUpDown2.Minimum = hScrollBar1.Minimum;
            numericUpDown2.Maximum = hScrollBar1.Maximum;
            numericUpDown2.Increment = hScrollBar1.SmallChange;

            // atribui-se ao Label o valor do variador 1
            labelValeurHS1.Text = hScrollBar1.Value.ToString();
        }

        private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
            // alteração do valor do variador 1
            // o seu valor é repercutido no variador 2 e no Label
            hScrollBar2.Value = hScrollBar1.Value;
            labelValeurHS1.Text = hScrollBar1.Value.ToString();
        }

        private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
            // o incrementador alterou o seu valor
            // define-se o valor do variador 2
            hScrollBar2.Value = (int)numericUpDown2.Value;
        }
    }
}

7.3. Eventos do rato

Quando se desenha num contentor, é importante conhecer a posição do rato para, por exemplo, exibir um ponto ao clicar. Os movimentos do rato provocam eventos no contentor em que se desloca.

  • [1]: os eventos que ocorrem quando o rato se desloca sobre o formulário ou sobre um controlo
  • [2]: os eventos que ocorrem durante um arrastar e largar (Drag'nDrop)
MouseEnter
o rato acabou de entrar na área do controlo
MouseLeave
o rato acabou de sair da área do controlo
MouseMove
o rato está a mover-se na área de controlo
MouseDown
Clicou no botão esquerdo do rato
MouseUp
Soltar o botão esquerdo do rato
DragDrop
O utilizador solta um objeto no controlo
DragEnter
O utilizador entra na área do controlo ao arrastar um objeto
DragLeave
o utilizador sai da área do controlo arrastando um objeto
DragOver
o utilizador passa por cima da área do controlo ao arrastar um objeto

Eis uma aplicação que permite compreender melhor em que momentos ocorrem os diferentes eventos do rato:

n.º
tipo
nome
função
1
Etiqueta
lblPositionSouris
para exibir a posição do rato no formulário 1, na lista 2 ou no botão 3
2
ListBox
listBoxEvts
para apresentar os eventos do rato que não sejam o MouseMove
3
Botão
buttonEffacer
para apagar o conteúdo de 2

Para acompanhar os movimentos do rato nos três controlos, escreve-se apenas um único gestor, o gestor affiche:

O código da procedimento affiche é o seguinte:


        private void affiche(object sender, MouseEventArgs e) {
            // movimento do rato - exibem-se as coordenadas (X, Y) do mesmo
            labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}

Sempre que o cursor entra no domínio de um controlo, o seu sistema de coordenadas altera-se. A sua origem (0,0) é o canto superior esquerdo do controlo em que se encontra. Assim, durante a execução, quando se passa o cursor do formulário para o botão, observa-se claramente a alteração das coordenadas. Para observar melhor estas mudanças de área do rato, pode-se utilizar a propriedade Cursor [1] dos controlos:

Esta propriedade permite definir a forma do cursor do rato quando este entra na área do controlo. Assim, no nosso exemplo, definimos o cursor como «Default» para o próprio formulário [2], «Hand» para a lista 2 [3] e «Cross» para o botão 3 [4].

Além disso, para detetar as entradas e saídas do rato na lista 2, processamos os eventos MouseEnter e MouseLeave dessa mesma lista:


        private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
            // sinaliza-se o evento
            listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
        }

        private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
            // é sinalizado o evento
            listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}

Para processar os cliques no formulário, tratamos os eventos MouseDown e MouseUp:


        private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
            // sinaliza-se o evento
            listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
        }

        private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
            // sinaliza o evento
            listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
  • linhas 3 e 8: as mensagens são colocadas na primeira posição no ListBox para que os eventos mais recentes sejam os primeiros da lista.
 

Por fim, o código do gestor de cliques no botão Effacer:


        private void buttonEffacer_Click(object sender, EventArgs e) {
            listBoxEvts.Items.Clear();
}

7.4. Criar uma janela com menu

Vamos agora ver como criar uma janela com menu. Vamos criar a seguinte janela:

Para criar um menu, seleciona-se o componente «MenuStrip» na barra «Menus & Tollbars»:

  • [1]: seleção do componente [MenuStrip]
  • [2]: obtém-se assim um menu que é inserido no formulário com campos em branco intitulados «Type Here». Basta indicar neles as diferentes opções do menu.
  • [3]: o texto «Opções A» foi introduzido. Passa-se para o texto [4].
  • [5]: os títulos das opções A foram introduzidos. Passamos ao título [6]
  • [6]: as primeiras opções B
  • [7]: em B1, insere-se um separador. Este está disponível numa lista suspensa associada ao texto «Type Here»
  • [8]: para criar um submenu, utilize a seta [8] e introduza o submenu em [9]

Resta nomear os diferentes componentes do formulário:

n.º
tipo
nome(s)
função
1
Etiqueta
labelStatut
para apresentar o texto da opção do menu selecionada
2
toolStripMenuItem
toolStripMenuItemOptionsA
toolStripMenuItemA1
toolStripMenuItemA2
toolStripMenuItemA3
opções de menu na opção principal «Opções A»
3
toolStripMenuItem
toolStripMenuItemOptionsB
toolStripMenuItemB1
toolStripMenuItemB2
toolStripMenuItemB3
opções do menu na opção principal «Opções B»
4
toolStripMenuItem
toolStripMenuItemB31
toolStripMenuItemB32
opções de menu sob a opção principal «B3»

As opções de menu são controlos, tal como os outros componentes visuais, e possuem propriedades e eventos. Por exemplo, as propriedades da opção de menu A1 são as seguintes:

 

No nosso exemplo, são utilizadas duas propriedades:

Name
o nome do controlo de menu
Text
o texto da opção do menu

Na estrutura do menu, selecionemos a opção A1 e cliquemos com o botão direito do rato para aceder às propriedades do controlo:

No separador événements [1], associamos o método affiche [2] ao evento Click. Isto significa que se pretende que o clique na opção A1 seja tratado por um método denominado affiche. O Visual Studio gera automaticamente o método affiche na janela de código:


private void affiche(object sender, EventArgs e) {
        }

Neste método, limitar-nos-emos a apresentar no rótulo labelStatut a propriedade Text da opção do menu em que se clicou:


private void affiche(object sender, EventArgs e) {
            // exibe no TextBox o nome do submenu selecionado
            labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}

A origem do evento sender é do tipo object. As opções do menu são do tipo ToolStripMenuItem, pelo que é necessário efetuar uma conversão de tipo de object para ToolStripMenuItem.

Para todas as opções de menu, definimos o gestor de cliques para o método affiche [3,4].

Executemos a aplicação e selecionemos um elemento do menu:

 

7.5. Componentes não visuais

Vamos agora debruçar-nos sobre alguns componentes não visuais: estes são utilizados durante a conceção, mas não são visíveis durante a execução.

7.5.1. Caixas de diálogo , OpenFileDialog e SaveFileDialog

Vamos criar a seguinte aplicação:

Os controlos são os seguintes:

N.º
tipo
nome
função
1
TextBox
TextBoxLignes
texto digitado pelo utilizador ou carregado a partir de um ficheiro
MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True
2
Botão
buttonSauvegarder
permite guardar o texto de [1] num ficheiro de texto
3
Botão
buttonCharger
permite carregar o conteúdo de um ficheiro de texto no [1]
4
Botão
buttonEffacer
apaga o conteúdo de [1]
5
SaveFileDialog
saveFileDialog1
componente que permite escolher o nome e a localização do ficheiro de cópia de segurança de [1]. Este componente é selecionado na barra de ferramentas [7] e simplesmente arrastado para o formulário. É então guardado, mas não ocupa espaço no formulário. Trata-se de um componente não visual.
6
OpenFileDialog
openFileDialog1
Componente que permite selecionar o ficheiro a carregar no [1].

O código associado ao botão Effacer é simples:


        private void buttonEffacer_Click(object sender, EventArgs e) {
            // coloca-se a cadeia vazia no TexBox
            textBoxLignes.Text = "";
}

Iremos utilizar as seguintes propriedades e métodos da classe SaveFileDialog:

Campo
Tipo
Função
string Filter
Propriété
os tipos de ficheiros disponíveis na lista suspensa de tipos de ficheiros da caixa de diálogo
int FilterIndex
Propriété
o número do tipo de ficheiro proposto por predefinição na lista acima. Começa em 0.
string InitialDirectory
Propriété
a pasta inicialmente apresentada para guardar o ficheiro
string FileName
Propriété
o nome do ficheiro de cópia de segurança indicado pelo utilizador
DialogResult.ShowDialog()
Méthode
método que apresenta a caixa de diálogo de gravação. Devolve um resultado do tipo DialogResult.

O método ShowDialog apresenta uma caixa de diálogo semelhante à seguinte:

1
lista suspensa criada a partir da propriedade Filter. O tipo de ficheiro proposto por predefinição é definido por FilterIndex
2
pasta atual, definida por InitialDirectory, caso esta propriedade tenha sido preenchida
3
nome do ficheiro selecionado ou introduzido diretamente pelo utilizador. Estará disponível na propriedade FileName
4
Botões «Guardar»/«Anular». Se for utilizado o botão Enregistrer, a função ShowDialog devolve o resultado DialogResult.OK

O procedimento de gravação pode ser escrito da seguinte forma:


private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
            // guarda-se a caixa de entrada num ficheiro de texto
            // configura-se a caixa de diálogo savefileDialog1
            saveFileDialog1.InitialDirectory = Application.ExecutablePath;
            saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
            saveFileDialog1.FilterIndex = 0;
            // exibe-se a caixa de diálogo e recupera-se o seu resultado
            if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
                // recupera-se o nome do ficheiro
                string nomFichier = saveFileDialog1.FileName;
                StreamWriter fichier = null;
                try {
                    // abre-se o ficheiro para gravação
                    fichier = new StreamWriter(nomFichier);
                    // escreve-se o texto no ficheiro
                    fichier.Write(textBoxLignes.Text);
                } catch (Exception ex) {
                    // problema
                    MessageBox.Show("Problème à l'écriture du fichier (" +
                    ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                } finally {
                    // fecha-se o ficheiro
                    if (fichier != null) {
                        fichier.Dispose();
                    }
                }
            }
        }
  • linha 4: define-se a pasta inicial (InitialDirectory) como a pasta (Application.ExecutablePath) que contém o executável da aplicação.
  • linha 5: definem-se os tipos de ficheiros a apresentar. Note-se a sintaxe dos filtros: filtre1|filtre2|..|filtren com filtro i = Texto|modelo de ficheiro. Aqui, o utilizador poderá escolher entre os ficheiros *.txt e *.*.
  • linha 6: define-se o tipo de ficheiro a apresentar em primeiro lugar ao utilizador. Aqui, o índice 0 refere-se aos ficheiros *.txt.
  • linha 8: a caixa de diálogo é apresentada e o seu resultado é recuperado. Enquanto a caixa de diálogo estiver visível, o utilizador não tem acesso ao formulário principal (caixa de diálogo dita «modal»). O utilizador define o nome do ficheiro a guardar e sai da caixa de diálogo, quer através do botão Enregistrer, quer através do botão Annuler,, quer fechando a caixa de diálogo. O resultado do método ShowDialog é DialogResult.OK apenas se o utilizador tiver utilizado o botão Enregistrer para sair da caixa de diálogo.
  • Feito isto, o nome do ficheiro a criar encontra-se agora na propriedade FileName do objeto saveFileDialog1. Passamos então à criação clássica de um ficheiro de texto. Escreve-se nele o conteúdo de TextBox: textBoxLignes.Text, gerindo simultaneamente as exceções que possam ocorrer.

A classe OpenFileDialog é muito semelhante à classe SaveFileDialog. Utilizar-se-ão os mesmos métodos e propriedades que anteriormente. O método ShowDialog apresenta uma caixa de diálogo semelhante à seguinte:

1
lista suspensa criada a partir da propriedade Filter. O tipo de ficheiro proposto por predefinição é definido por FilterIndex
2
pasta atual, definida por InitialDirectory, caso esta propriedade tenha sido preenchida
3
nome do ficheiro selecionado ou introduzido diretamente pelo utilizador. Estará disponível na propriedade FileName
4
botões Abrir/Cancelar. Se for utilizado o botão Ouvrir, a função ShowDialog produz o resultado DialogResult.OK

O procedimento para carregar o ficheiro de texto pode ser escrito da seguinte forma:


private void buttonCharger_Click(object sender, EventArgs e) {
            // carrega-se um ficheiro de texto na caixa de entrada
            // configura-se a caixa de diálogo openfileDialog1
            openFileDialog1.InitialDirectory = Application.ExecutablePath;
            openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            // exibe-se a caixa de diálogo e recupera-se o seu resultado
            if (openFileDialog1.ShowDialog() == DialogResult.OK) {
                // recupera-se o nome do ficheiro
                string nomFichier = openFileDialog1.FileName;
                StreamReader fichier = null;
                try {
                    // abre-se o ficheiro em modo de leitura
                    fichier = new StreamReader(nomFichier);
                    // lê-se todo o ficheiro e coloca-se no TextBox
                    textBoxLignes.Text = fichier.ReadToEnd();
                } catch (Exception ex) {
                    // problema
                    MessageBox.Show("Problème à la lecture du fichier (" +
                    ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                } finally {
                    // fecha-se o ficheiro
                    if (fichier != null) {
                        fichier.Dispose();
                    }
                }//finalmente
            }//se
        }
  • linha 4: define-se a pasta inicial (InitialDirectory) como a pasta (Application.ExecutablePath) que contém o executável da aplicação.
  • linha 5: definem-se os tipos de ficheiros a apresentar. Note-se a sintaxe dos filtros: filtre1|filtre2|..|filtren com filtro i = Texto|modelo de ficheiro. Aqui, o utilizador poderá escolher entre os ficheiros *.txt e *.*.
  • linha 6: define-se o tipo de ficheiro a apresentar em primeiro lugar ao utilizador. Aqui, o índice 0 refere-se aos ficheiros *.txt.
  • linha 8: a caixa de diálogo é apresentada e o seu resultado é recuperado. Enquanto a caixa de diálogo estiver visível, o utilizador não tem acesso ao formulário principal (caixa de diálogo dita «modal»). O utilizador define o nome do ficheiro a guardar e sai da caixa de diálogo, quer através do botão Ouvrir, quer através do botão Annuler,, quer fechando a caixa de diálogo. O resultado do método ShowDialog é DialogResult.OK apenas se o utilizador tiver utilizado o botão Enregistrer para sair da caixa de diálogo.
  • Feito isto, o nome do ficheiro a criar encontra-se agora na propriedade FileName do objeto openFileDialog1. Passa-se então à leitura clássica de um ficheiro de texto. Note-se, na linha 16, o método que permite ler a totalidade de um ficheiro.

7.5.2. Caixas de diálogo FontColor e ColorDialog

Continuamos o exemplo anterior, adicionando-lhe dois novos botões e dois novos controlos não visuais:

6

7

N.º
tipo
nome
função
1
Botão
buttonCouleur
para definir a cor dos caracteres do TextBox
2
Botão
buttonPolice
para definir o tipo de letra do TextBox
3
ColorDialog
colorDialog1
o componente que permite a seleção de uma cor — incluído na caixa de ferramentas [5].
4
FontDialog
colorDialog1
o componente que permite a seleção de um tipo de letra — incluído na caixa de ferramentas [5].

As classes FontDialog e ColorDialog têm um método ShowDialog análogo ao método ShowDialog das classes OpenFileDialog e SaveFileDialog.

O método ShowDialog da classe ColorDialog permite selecionar uma cor [1]. O método da classe FontDialog permite selecionar um tipo de letra [2]:

  • [1]: se o utilizador sair da caixa de diálogo com o botão OK, o resultado do método ShowDialog é DialogResult.OK e a cor selecionada encontra-se na propriedade Color do objeto ColorDialog utilizado.
  • [2]: se o utilizador sair da caixa de diálogo através do botão OK, o resultado do método ShowDialog é DialogResult.OK e o tipo de letra selecionado encontra-se na propriedade Font do objeto FontDialog utilizado.

Temos agora os elementos necessários para tratar os cliques nos botões Couleur e Police:


        private void buttonCouleur_Click(object sender, EventArgs e) {// escolha de uma cor de texto
            if (colorDialog1.ShowDialog() == DialogResult.OK) {
                // alteramos a propriedade Forecolor do TextBox
                textBoxLignes.ForeColor = colorDialog1.Color;
            }//se
        }

        private void buttonPolice_Click(object sender, EventArgs e) {
            // escolha de um tipo de letra
            if (fontDialog1.ShowDialog() == DialogResult.OK) {
                // alteração da propriedade Font do TextBox
                textBoxLignes.Font = fontDialog1.Font;
}
  • linha [4]: a propriedade [ForeColor] de um componente TextBox define a cor do tipo [Color] dos caracteres do TextBox. Neste caso, esta cor é a escolhida pelo utilizador na caixa de diálogo do tipo [ColorDialog].
  • linha [12]: a propriedade [Font] de um componente TextBox designa o tipo de letra de tipo [Font] dos caracteres do TextBox. Neste caso, esta fonte é a escolhida pelo utilizador na caixa de diálogo do tipo [FontDialog].

7.5.3. Temporizador

Propomos aqui escrever a seguinte aplicação:

n.º
Tipo
Nome
Função
1
Etiqueta
labelChrono
exibe um cronómetro
2
Botão
buttonArretMarche
botão de parar/iniciar do cronómetro
3
Temporizador
timer1
componente que emite aqui um evento a cada segundo

Em [4], vemos o cronómetro em funcionamento; em [5], o cronómetro está parado.

Para alterar a cada segundo o conteúdo do Label LabelChrono, precisamos de um componente que gere um evento a cada segundo, evento esse que poderemos interceptar para atualizar a exibição do cronómetro. Esse componente é o Timer [1], disponível na caixa de ferramentas Components [2]:

As propriedades do componente Timer utilizadas aqui serão as seguintes:

Interval
número de milissegundos após os quais é emitido um evento Tick.
Tick
o evento gerado ao fim de Interval milissegundos
Enabled
torna o temporizador ativo (true) ou inativo (false)

No nosso exemplo, o temporizador chama-se timer1 e o timer1.Interval está definido para 1000 ms (1 s). O evento Tick ocorrerá, portanto, a cada segundo. O clique no botão Parar/Iniciar é processado pela seguinte rotina buttonArretMarche_Click:


using System;
using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        // variável de instância
        private DateTime début = DateTime.Now;
...
        private void buttonArretMarche_Click(object sender, EventArgs e) {
            // parar ou continuar?
            if (buttonArretMarche.Text == "Marche") {
                // regista-se a hora de início
                début = DateTime.Now;
                // exibe-se
                labelChrono.Text = "00:00:00";
                // inicia-se o temporizador
                timer1.Enabled = true;
                // alterar o texto do botão
                buttonArretMarche.Text = "Arrêt";
                // fim
                return;
            }//
            if (buttonArretMarche.Text == "Arrêt") {
                // paragem do temporizador
                timer1.Enabled = false;
                // alterar o texto do botão
                buttonArretMarche.Text = "Marche";
                // fim
                return;
            }
        }

    }
}
  • linha 13: o procedimento que trata do clique no botão «Parar/Ligar».
  • linha 15: o texto do botão «Parar/Ligar» é «Parar» ou «Ligar». Por isso, é necessário verificar esse texto para saber o que fazer.
  • linha 17: no caso de «Ligar», regista-se a hora de início numa variável début, que é uma variável global (linha 11) do objeto formulário
  • linha 19: inicializa o conteúdo do rótulo LabelChrono
  • linha 21: o temporizador é iniciado (Enabled=true)
  • linha 23: o texto do botão passa a ser «Parar».
  • linha 27: no caso de «Parar»
  • linha 29: o temporizador é parado (Enabled=false)
  • linha 31: o texto do botão passa a ser «Ligar».

Resta-nos tratar o evento Tick no objeto timer1, evento que ocorre a cada segundo:


private void timer1_Tick(object sender, EventArgs e) {
            // passou um segundo
            DateTime maintenant = DateTime.Now;
            TimeSpan durée = maintenant - début;
            // atualização do cronómetro
            labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
        }
  • linha 3: registamos a hora atual
  • linha 4: calcula-se o tempo decorrido desde o momento em que o cronómetro foi iniciado. Obtém-se um objeto do tipo TimeSpan, que representa um intervalo de tempo.
  • linha 6: esta deve ser apresentada no cronómetro na forma hh:mm:ss. Para tal, utilizamos as propriedades Hours, Minutes, Seconds do objeto TimeSPan, que representam, respetivamente, as horas, minutos e segundos da duração que apresentamos no formato ToString("d2") para obter uma exibição com 2 dígitos.

7.6. Aplicação de exemplo - versão 6

Retomamos a aplicação de exemplo IMPOTS. A última versão foi analisada no parágrafo 6.4. Tratava-se da seguinte aplicação de três camadas:

  • as camadas [metier] e [dao] estavam encapsuladas em DLL
  • a camada [ui] era uma camada [console]
  • a instanciação das camadas e a sua integração na aplicação eram asseguradas pelo Spring.

Nesta nova versão, a camada [ui] será assegurada pela seguinte interface gráfica:

 

7.6.1. A solução do Visual Studio

A solução do Visual Studio é composta pelos seguintes elementos:

  • [1]: o projeto é constituído pelos seguintes elementos:
  • [Program.cs]: a classe que inicia a aplicação
  • [Form1.cs]: a classe de um primeiro formulário
  • [Form2]: a classe de um segundo formulário
  • [lib], detalhado em [2]: aqui foram incluídas todas as DLL necessárias ao projeto:
  • [ImpotsV5-dao.dll]: o DLL da camada [dao] gerado no parágrafo 6.4.3;
  • [ImpotsV5-metier.dll]: a DLL da camada [dao] gerada no parágrafo 6.4.4;
  • [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: as DLL do Spring já utilizadas na versão anterior (ver parágrafo 6.4.6).
  • [references], detalhado em [3]: as referências do projeto. Foi adicionada uma referência para cada um dos ficheiros DLL da pasta [lib]
  • [App.config]: o ficheiro de configuração do projeto. É idêntico ao da versão anterior, descrito no parágrafo 6.4.6;
  • [DataImpot.txt]: o ficheiro das faixas de imposto, configurado para ser copiado automaticamente para a pasta de execução do projeto [4]

O formulário [Form1] é o formulário de introdução dos parâmetros para o cálculo do imposto [A], já apresentado anteriormente. O formulário [Form2] [B] serve para apresentar uma mensagem de erro:

7.6.2. A classe [Program.cs]

A classe [Program.cs] inicia a aplicação. O seu código é o seguinte:


using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;

namespace Chap5 {
    static class Program {
        /// <summary>
        /// O ponto de entrada principal da aplicação.
        /// </summary>
        [STAThread]
        static void Main() {
            // código gerado pelo Visual Studio
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // --------------- Código do programador
            // instâncias das camadas [metier] e [dao]
            IApplicationContext ctx = null;
            Exception ex = null;
            IImpotMetier metier = null;
            try {
                // contexto Spring
                ctx = ContextRegistry.GetContext();
                // é solicitada uma referência na camada [metier]
                metier = (IImpotMetier)ctx.GetObject("metier");
            } catch (Exception e1) {
                // registo de exceção
                ex = e1;
            }
            // formulário a apresentar
            Form form = null;
            // ocorreu alguma exceção?
            if (ex != null) {
                // sim — cria-se a mensagem de erro a apresentar
                StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                Exception e = ex;
                while (e != null) {
                    msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
                    msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                    e = e.InnerException;
                }
                // criação da janela de erro para a qual se passa a mensagem de erro a apresentar
                Form2 form2 = new Form2();
                form2.MsgErreur = msgErreur.ToString();
                // esta será a janela a apresentar
                form = form2;
            } else {
                // correu tudo bem
                // criação da interface gráfica [Form1], à qual é passada a referência na camada [metier]
                Form1 form1 = new Form1();
                form1.Metier = metier;
                // esta será a janela a apresentar
                form = form1;
            }
            // exibição da janela
            Application.Run(form);
        }
    }
}

O código gerado pelo Visual Studio foi completado a partir da linha 19. A aplicação utiliza o ficheiro [App.config] seguinte:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
                <constructor-arg index="0" value="DataImpot.txt"/>
            </object>
            <object name="metier" type="Metier.ImpotMetier, ImpotsV5-metier">
                <constructor-arg index="0" ref="dao"/>
            </object>
        </objects>
    </spring>
</configuration>
  • linhas 24-32: utilização do ficheiro [App.config] anterior para instanciar as camadas [metier] e [dao]
  • linha 26: processamento do ficheiro [App.config]
  • linha 28: recuperação de uma referência na camada [metier]
  • linha 31: registo de uma eventual exceção
  • linha 34: a referência form indicará o formulário a apresentar (form1 ou form2)
  • linhas 36-50: se tiver ocorrido uma exceção, prepara-se a exibição de um formulário do tipo [Form2]
  • linhas 38-44: constrói-se a mensagem de erro a apresentar. Esta é constituída pela concatenação das mensagens de erro das diferentes exceções presentes na cadeia de exceções.
  • linha 46: é criado um formulário do tipo [Form2].
  • linha 47: como veremos mais adiante, este formulário tem uma propriedade pública MsgErreur que corresponde à mensagem de erro a apresentar:

        public string MsgErreur { private get; set; }

Esta propriedade é preenchida.

  • linha 49: a referência form, que designa a janela a apresentar, é inicializada. Note-se o polimorfismo em ação. form2 não é do tipo [Form], mas sim do tipo [Form2], um tipo derivado de [Form].
  • linhas 50-57: não ocorreu nenhuma exceção. Preparamo-nos para apresentar um formulário do tipo [Form1].
  • linha 53: é criado um formulário do tipo [Form1].
  • linha 54: como veremos mais adiante, este formulário tem uma propriedade pública Metier que é uma referência à camada [metier]:

                public IImpotMetier Metier { private get; set; }

Preenche-se esta propriedade.

  • linha 56: a referência form, que designa a janela a apresentar, é inicializada. Note-se, mais uma vez, o polimorfismo em ação. form1 não é do tipo [Form], mas sim do tipo [Form1], um tipo derivado de [Form].
  • linha 59: a janela referenciada por form é apresentada.

7.6.3. O formulário [Form1]

No modo [conception], o formulário [Form1] é o seguinte:

Os controlos são os seguintes

n.º
tipo
nome
função
0
GroupBox
groupBox1
Text=É casado(a)?
1
RadioButton
radioButtonOui
assinalar se for casado(a)
2
RadioButton
radioButtonNon
marcado se não for casado
Checked=True
3
NumericUpDown
numericUpDownEnfants
número de filhos do contribuinte
Mínimo=0, Máximo=20, Incremento=1
4
TextBox
textSalaire
salário anual do contribuinte em euros
5
Rótulo
labelImpot
montante do imposto a pagar
BorderStyle=Fixed3D
6
Botão
buttonCalculer
inicia o cálculo do imposto
7
Botão
buttonEffacer
repor o formulário ao estado em que se encontrava no momento do carregamento
8
Botão
buttonQuitter
para sair da aplicação

Regras de funcionamento do formulário

  • o botão Calculer permanece desativado enquanto não houver nada no campo do salário
  • se, quando o cálculo for iniciado, se verificar que o salário está incorreto, o erro é sinalizado [9]

O código da classe é o seguinte:


using System.Windows.Forms;
using Metier;
using System;

namespace Chap5 {
    public partial class Form1 : Form {
        // camada [métier]
        public IImpotMetier Metier { private get; set; }

        public Form1() {
            InitializeComponent();
        }

        private void buttonCalculer_Click(object sender, System.EventArgs e) {
            // o salário está correto?
            int salaire;
            bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
            if (! ok  || salaire < 0) {
                // mensagem de erro
                MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
                // regresso ao campo com erro
                textSalaire.Focus();
                // seleção do texto do campo de introdução
                textSalaire.SelectAll();
                // regresso à interface de introdução de dados
                return;
            }
            // o salário está correto — é possível calcular o imposto
            labelImpot.Text = Metier.CalculerImpot(radioButtonOui.Checked, (int)numericUpDownEnfants.Value, salaire).ToString();
        }

        private void buttonQuitter_Click(object sender, System.EventArgs e) {
            Environment.Exit(0);
        }

        private void buttonEffacer_Click(object sender, System.EventArgs e) {
            // limpar formulário
            labelImpot.Text = "";
            numericUpDownEnfants.Value = 0;
            textSalaire.Text = "";
            radioButtonNon.Checked = true;
        }

        private void textSalaire_TextChanged(object sender, EventArgs e) {
            // estado do botão [Calculer]
            buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
        }

    }
}

Apenas comentamos as partes importantes:

  • linha [8]: a propriedade pública Metier que permite à classe de lançamento [Program.cs] inserir na [Form1] uma referência à camada [metier].
  • linha [14]: o procedimento de cálculo do imposto
  • linhas 15-27: verificação da validade do salário (um número inteiro >=0).
  • linha 29: cálculo do imposto utilizando o método [CalculerImpot] da camada [metier]. De salientar a simplicidade desta operação, obtida graças ao encapsulamento da camada [metier] numa DLL.

7.6.4. O formulário [Form2]

No modo [conception], o formulário [Form2] é o seguinte:

Os controlos são os seguintes

n.º
tipo
nome
função
1
TextBox
textBoxErreur
Multiline=True, Scrollbars=Both

O código da classe é o seguinte:


using System.Windows.Forms;

namespace Chap5 {
    public partial class Form2 : Form {
        // mensagem de erro
        public string MsgErreur { private get; set; }

        public Form2() {
            InitializeComponent();
        }

        private void Form2_Load(object sender, System.EventArgs e) {
            // exibe-se a mensagem de erro
            textBoxErreur.Text = MsgErreur;
            // desmarca-se todo o texto
            textBoxErreur.Select(0, 0);
        }
    }
}
  • linha 6: a propriedade pública MsgErreur, que permite à classe de lançamento [Program.cs] inserir na [Form2] a mensagem de erro a apresentar. Esta mensagem é exibida durante o processamento do evento Load, linhas 12-16.
  • linha 14: a mensagem de erro é inserida no TextBox
  • linha 16: remove-se a seleção efetuada na operação anterior. [TextBox].Select(início,comprimento) seleciona (destaca) longueur caracteres a partir do caractere n.º début. [TextBox].Select(0,0) equivale a desmarcar todo o texto.

7.6.5. Conclusão

Voltemos à arquitetura de três camadas utilizada:

Esta arquitetura permitiu-nos substituir a implementação de consola da camada [ui] existente por uma implementação gráfica, sem alterar nada nas camadas [metier] e [dao]. Pudemos concentrar-nos na camada [ui] sem nos preocuparmos com possíveis impactos nas outras camadas. É aí que reside o principal interesse das arquiteturas de três camadas. Veremos outro exemplo mais adiante, quando a camada [dao], que atualmente utiliza os dados de um ficheiro de texto, for substituída por uma camada [dao] que utilize os dados de uma base de dados. Veremos que isso se fará sem impacto nas camadas [ui] e [metier].