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 “Aplicativo Windows”:

  • [1]: criar um novo projeto
  • [2]: do tipo “Aplicativo Windows”
  • [3]: o nome do projeto não importa neste momento
  • [4]: o projeto foi criado
  • [5]: salvando 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 dessa solução ficarão em subpastas.
  • [10]: o projeto [01] na solução [Chap5]:
  • [Program.cs] é a classe principal do projeto
  • [Form1.cs] é o arquivo-fonte que irá gerenciar o comportamento da janela [11]
  • [Form1.Designer.cs] é o arquivo-fonte que irá encapsular as informações sobre os componentes da janela [11]
  • [11]: o arquivo [Form1.cs] no modo “design”
  • [12]: o aplicativo gerado pode ser executado pressionando (Ctrl-F5). A janela [Form1] é exibida. É possível movê-la, redimensioná-la e fechá-la. Assim, temos 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 principal ponto de entrada do aplicativo.
        /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
  • linha 2: os aplicativos com formulários utilizam o namespace System.Windows.Forms.
  • linha 4: o namespace 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. Ela contém métodos estáticos para iniciar/encerrar aplicativos gráficos do Windows.
  • linha 11: opcional — permite atribuir diferentes estilos visuais aos controles inseridos em um formulário
  • linha 12: opcional — define o mecanismo de renderização dos textos dos controles: 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 que ela seja executada.

O arquivo-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 complementada por outros arquivos-fonte. É o caso aqui, onde a classe Form1 está distribuída em dois arquivos:
  • [Form1.cs]: no qual se encontra o comportamento do formulário, notadamente seus manipuladores de eventos
  • [Form1.Designer.cs]: no qual se encontram os componentes do formulário e suas propriedades. Esse arquivo tem a particularidade de ser regenerado sempre que o usuário modifica a janela no modo [conception].
  • linhas 6-8: o construtor da classe Form1
  • linha 7: chama o método InitializeComponent. Percebe-se que esse método não está presente em [Form1.cs]. Ele se encontra em [Form1.Designer.cs].

O arquivo-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>
        /// Limpe todos os recursos que estão sendo usados.
        /// </summary>
        /// <param name="disposing">true se os recursos gerenciados devem ser descartados; 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 ao Designer — não modifique
        /// 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);

        }

         #endregion

    }
}
  • linha 2: trata-se, mais uma vez, da classe Form1. Observe-se que não é mais necessário repetir que ela deriva da classe Form.
  • linhas 25-37: o método InitializeComponent chamado pelo construtor da classe [Form1]. Esse método criará e inicializará todos os componentes do formulário. Ele é regenerado a cada alteração do formulário no modo [conception]. Uma seção, chamada région, é criada para delimitá-la nas linhas 19-39. O desenvolvedor não deve adicionar código nessa região: ele será sobrescrito na próxima regeneração.

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

  • [1]: selecione o modo [conception] clicando duas vezes no arquivo [Form1.cs]
  • [2]: clicar com o botão direito do mouse 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

Iniciamos um novo projeto chamado 02. Para isso, seguimos o procedimento explicado anteriormente para criar um projeto. A janela a ser criada é 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 preenchimento
3
buttonAfficher
Botão
para exibir o conteúdo do campo de entrada em uma caixa de diálogo textBoxSaisie

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

  • [1]: clicar com o botão direito do mouse 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, destaque para:

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 a fonte das letras na janela
Name
para definir o nome da janela

Aqui, definimos as propriedades Text e Name:

Text
Campos de entrada e botões - 1
Name
frmSaisiesBoutons
  • [1]: selecione a caixa de ferramentas [Common Controls] entre as caixas de ferramentas oferecidas 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, é possível usar os elementos da barra de ferramentas:

 
  
   

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

  1. selecione os diferentes componentes a serem formatados em conjunto (mantenha a tecla Ctrl pressionada enquanto clica para selecionar os componentes)
  2. selecione o tipo de formatação desejado:
  • (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 componentes horizontalmente com intervalos entre eles de mesma largura. O mesmo vale para a opção “Vertical Spacing”, que permite o alinhamento vertical.
    • A opção Center permite centralizar um componente horizontalmente (Horizontally) ou verticalmente (Vertically) na janela

Depois de posicionados os componentes, definimos suas propriedades. Para isso, clique com o botão direito do mouse no componente e selecione a opção Properties:

  • [1]: selecione o componente para abrir sua janela de propriedades. Nela, altere as seguintes propriedades: name: labelSaisie, text: Saisie
  • [2]: proceda da mesma forma: nome: textBoxSaisie, texto: não preencha nada
  • [3]: name: buttonAfficher, text: Afficher
  • [4]: a própria janela: nome: frmSaisiesBoutons, texto: Campos de entrada e botões - 1
  • [5]: execute (Ctrl-F5) o projeto para ter uma primeira prévia 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]. Observe-se que os nomes desses 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: os três objetos do tipo [Label], [TextBox] e [Button] são criados. É por meio deles que os componentes visuais são gerenciados.
  • 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 entrada textBoxSaisie
  • linhas 40-47: configuração do formulário frmSaisiesBoutons. Observe, nas linhas 43-45, como adicionar componentes ao formulário.

Esse código é compreensível. Assim, é possível criar formulários por meio de código sem utilizar o modo [conception]. Muitos exemplos disso são apresentados na documentação MSDN do Visual Studio. Dominar esse 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 banco de dados, sendo que a estrutura dessa tabela só é identificada na hora da execução.

Resta-nos escrever o procedimento para gerenciar um clique no botão Afficher. Selecione o botão para acessar sua janela de propriedades. Ela possui várias abas:

  • [1]: lista de propriedades em ordem alfabética
  • [2]: eventos relacionados ao controle

As propriedades e eventos de um controle podem ser acessados por categorias ou em ordem alfabética:

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

A aba Events no modo Catégories para o botão buttonAfficher é a seguinte:

  • [1]: a coluna à esquerda da janela lista os eventos possíveis no botão. Um clique no 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, o sistema passa automaticamente para a janela de código para escrever o manipulador 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: a estrutura básica do manipulador do evento Click para o botão denominado buttonAfficher. Observe os seguintes pontos:
    • o método é nomeado de acordo com o esquema nomDuComposant_NomEvénement
    • o método é privado. Ele recebe dois parâmetros:
    • sender: é o objeto que provocou o evento. Se o procedimento for executado após 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, então, teria toda a liberdade para definir como primeiro parâmetro o objeto sender de sua escolha.
    • EventArgs: um objeto que contém informações sobre o evento. Para um evento Click, ele não contém nada. Para um evento relacionado aos movimentos do mouse, encontraremos nele as coordenadas (X, Y) do mouse.
    • Não utilizaremos nenhum desses parâmetros aqui.

Escrever um manipulador de evento consiste em preencher o esboço de código anterior. Aqui, queremos exibir uma caixa de diálogo contendo, se o campo textBoxSaisie não estiver vazio, o conteúdo desse campo ([1]); caso contrário, uma mensagem de erro ([2]):

O código para fazer isso poderia ser o seguinte:


        private void buttonAfficher_Click(object sender, EventArgs e) {
            // é exibido o texto que foi digitado 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 exibir mensagens em uma janela. Utilizamos aqui o seguinte método Show:


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

com

text
a mensagem a ser exibida
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 Parar
Exclamation
idem Aviso
Hand
Information
idem Asterisk
None
Question
Stop
idem Hand
Warning

 

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

Image

Para saber em qual botão o usuário clicou para fechar a janela do tipo MessageBox, escreveremos:

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

7.1.2.2. O código relacionado ao gerenciamento de eventos

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, vemos que o evento Click não é exclusivo da classe [Button]. Ele pertence à classe [Control], classe pai da classe [Button].
    • EventHandler é um protótipo (um modelo) de método chamado delegado. Voltaremos a esse assunto mais adiante.
    • 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 retorna nenhum resultado

Esse é o caso do método de gerenciamento 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, proceda da seguinte forma:

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

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

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 usada mesmo que evtHandler seja uma lista vazia.

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


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

Essa instrução adiciona um método do tipo EventHandler à lista de métodos do campo buttonAfficher.Click. Esses métodos serão chamados sempre que o evento Click no componente buttonAfficher for detectado. Geralmente, há apenas um. Ele é chamado de “gerenciador 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 nenhuma informação sobre o evento que ocorreu. Para um clique em um botão, isso é suficiente. Para o movimento do mouse sobre um 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) de 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 mouse no momento em que o evento ocorre.

7.1.2.3. Conclusion

A partir dos dois projetos analisados, podemos concluir que, uma vez criada a interface gráfica com o Visual Studio, o trabalho do desenvolvedor consiste principalmente em escrever os manipuladores de eventos que deseja gerenciar para essa interface gráfica. O código é gerado automaticamente pelo Visual Studio. Esse código, que pode ser complexo, pode ser ignorado em uma primeira análise. Posteriormente, seu estudo pode permitir uma melhor compreensão da criação e do gerenciamento de formulários.

7.2. Os componentes básicos

Apresentamos agora diversas aplicações que utilizam os componentes mais comuns, a fim de explorar seus principais métodos e propriedades. Para cada aplicação, apresentamos a interface gráfica e o código relevante, principalmente o dos manipuladores de eventos.

7.2.1. Formulário Form

Começamos apresentando o componente indispensável: o formulário, no qual os componentes são inseridos. Já apresentamos algumas de suas propriedades básicas. Aqui, vamos nos concentrar em alguns eventos importantes de um formulário.

Load
o formulário está sendo carregado
Closing
o formulário está sendo fechado
Closed
o formulário está fechado

O evento Load ocorre antes mesmo de o formulário ser exibido. O evento Closing ocorre quando o formulário está sendo fechado. Ainda é possível interromper esse fechamento por meio de programação.

Criamos um formulário com o nome Form1 sem nenhum componente:

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

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á sendo fechado
            MessageBox.Show("Evt FormClosing", "FormClosing");
            // solicitando 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 será fechado
            MessageBox.Show("Evt FormClosed", "FormClosed");
        }
    }
}

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

linha 10: O evento Load ocorrerá ao iniciar
aplicativo, antes mesmo que o formulário seja exibido:
  
linha 15: O evento FormClosing ocorrerá quando
o usuário fechar a janela.
linha 19: Perguntamos então se ele realmente deseja sair
o aplicativo:
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 essa propriedade como False, o fechamento
da janela é cancelada; caso contrário, ela prossegue. O evento
FormClosed ocorrerá então:

7.2.2. Etiquetas e campos de entrada TextBox

Já nos deparamos com esses dois componentes. Label é um componente de texto e TextBox, um componente de campo de entrada. Sua principal propriedade é Text, que designa tanto o conteúdo do campo de entrada quanto o texto do rótulo. Essa propriedade é de leitura/gravação.

O evento normalmente utilizado para TextBox é TextChanged, que sinaliza que o usuário alterou o campo de entrada. Aqui está um exemplo que utiliza o evento TextChanged para acompanhar as alterações em um campo de entrada:

n.º
tipo
nome
função
1
TextBox
textBoxSaisie
campo de entrada
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 do aplicativo

O código deste aplicativo é 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á sendo copiado para o rótulo labelControle
            labelControle.Text = textBoxSaisie.Text;
        }

        private void buttonEffacer_Click(object sender, System.EventArgs e) {
            // o conteúdo do campo de entrada é apagado
            textBoxSaisie.Text = "";
        }

        private void buttonQuitter_Click(object sender, System.EventArgs e) {
            // clique no botão Sair — saímos do aplicativo
            Application.Exit();
        }

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

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

O exemplo a seguir utiliza um TextBox com várias linhas:

A lista de controles é a seguinte:

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

Para que um TextBox se torne multilinha, defina as seguintes propriedades do controle:

Multiline=true
para aceitar várias linhas de texto
ScrollBars=( None, Horizontal, Vertical, Both)
para definir se o controle terá barras de rolagem (Horizontal, Vertical, Both) ou não (None)
AcceptReturn=(True, False)
se for igual a true, a tecla Enter irá avançar para a próxima linha
AcceptTab=(True, False)
se for igual a true, a tecla Tab gerará uma tabulação no texto

O aplicativo permite digitar linhas diretamente em [1] ou adicioná-las por meio de [2] e [3].

O código do aplicativo é 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 entrada
            textBoxLigne.Focus();
        }
    }
}
  • linha 18: quando o formulário é exibido (possivelmente Shown), coloca-se o foco no campo de entrada textBoxLigne
  • linha 10: gerencia 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 entrada textBoxLigne é apagado

7.2.3. Listas suspensas ComboBox

Criamos o seguinte formulário:

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

Um componente ComboBox é uma lista suspensa acompanhada de um campo de entrada: o usuário pode escolher um item em (2) ou digitar 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 padrão, o tipo de um ComboBox é DropDown.

A classe ComboBox possui 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. Ela é somente para leitura.

Seja C um combo e C.Items 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()
exclui 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, embora visualmente exiba cadeias de caracteres. Se um ComboBox contiver um objeto obj, ele exibirá a cadeia obj.ToString(). Vale lembrar que todo objeto possui um método ToString herdado da classe object, que retorna uma sequência de caracteres “representativa” do objeto.

O elemento Item selecionado na lista suspensa C é C.SelectedItem ou C.Items[C.SelectedIndex], onde C.SelectedIndex é o número do elemento selecionado, sendo que esse número começa em zero para o primeiro elemento. O texto selecionado pode ser obtido de várias maneiras: C.SelectedItem.Text, C.Text

Ao selecionar um elemento na lista suspensa, ocorre o evento SelectedIndexChanged, que pode ser utilizado para ser notificado sobre a mudança na seleção da lista suspensa. No aplicativo a seguir, usamos esse evento para exibir o elemento que foi selecionado na lista.

 

O código do aplicativo é 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 — exibimos
                MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
                // registramos o novo índice
                previousSelectedIndex = newSelectedIndex;
            }
        }
    }
}
  • linha 5: previousSelectedIndex armazena o último índice selecionado no menu suspenso
  • 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 usuário seleciona um elemento da lista suspensa. Ao contrário do que o nome do evento possa sugerir, ele ocorre mesmo que o elemento selecionado seja o mesmo que o anterior.
  • linha 16: registra-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: registra-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 preenchimento
2
Botão
buttonAjouter
botão que permite adicionar o conteúdo do campo de entrada [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 contrário
7
Botão
buttonEffacer1
esvazia a lista 1
8
Botão
buttonEffacer2
esvazia a lista 2

Os componentes ListBox possuem um modo de seleção de seus elementos definido pela propriedade SelectionMode:

One
apenas um elemento pode ser selecionado
MultiExtended
é possível a seleção múltipla: manter pressionada a tecla SHIFT e clicar em um elemento amplia a seleção do elemento selecionado anteriormente para o elemento atual.
MultiSimple
É possível selecionar vários itens: um item é selecionado/desmarcado com um clique do mouse ou pressionando a barra de espaço.
  • O usuário digita o texto no campo 1. Ele o adiciona à lista 1 com o botão Ajouter (2). O campo de entrada (1) é então esvaziado e o usuário pode adicionar um novo elemento.
  • É possível transferir elementos de uma lista para outra selecionando o elemento a ser transferido em uma 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.
  • O usuário pode clicar duas vezes em um item da lista 1. Esse item é então transferido para a caixa de entrada para edição e removido da lista 1.

Os botões ficam ativados ou desativados de acordo com as seguintes regras:

  • o botão Ajouter só fica aceso se houver um texto não vazio no campo de entrada
  • o botão [5] para transferência da lista 1 para a lista 2 fica ativado apenas 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 ser apagada contiver itens.

Nas condições acima, todos os botões devem estar desativados ao iniciar o aplicativo. Nesse caso, a propriedade Enabled dos botões deve ser definida como false. Isso pode ser feito na fase de projeto, o que gerará o código correspondente no método InitializeComponent, ou pode ser feito manualmente no construtor, conforme mostrado abaixo:


        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 entrada. É o evento TextChanged que nos permite acompanhar as alterações nesse conteúdo:


        private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
            // o conteúdo de textBoxSaisie foi alterado
            // o botão “Adicionar” só fica ativado se o campo estiver preenchido
            buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
        }

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


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

        private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
            // um elemento foi selecionado
            // o botão de transferência de 2 para 1 fica ativado
            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());
            // limpeza do campo de entrada
            textBoxSaisie.Text = "";
            // A Lista 1 não está vazia
            buttonEffacer1.Enabled = true;
            // o foco volta para o campo de entrada
            textBoxSaisie.Focus();
}

Destaque para o método Focus, que permite colocar o “foco” em um controle 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) {
            // limpa 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 item 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 item 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 mesmo método privado chamado “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]);
                // exclusã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, denominada aqui l1. Durante a execução do aplicativo, l1 é listBox1 ou listBox2. Veja exemplos de chamadas nas linhas 3 e 8 dos procedimentos de transferência buttonXversY_Click.
  • uma referência ao botão de transferência vinculado à lista l1. Por exemplo, se l1 for listBox2, será button2vers1 (ver chamada na linha 8)
  • uma referência no botão de apagamento 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 se referem à 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 dessa coleção
  • [ListBox].SelectedIndices[i] é o elemento nº i dessa coleção

Percorremos a coleção na ordem inversa: começamos pelo fim da coleção e terminamos pelo início. Explicaremos o motivo.

  • linha f: índice de um elemento selecionado da lista l1
  • linha h: esse elemento é adicionado à lista l2
  • linha j: e removido da lista l1. Como foi removido, ele não está mais selecionado. A coleção l1.SelectedIndices da linha d será recalculada. Ela perderá o elemento que acabou de ser removido. Todos os elementos que vêm depois dele terão seus números alterados de n para n-1.
  • Se o ciclo da linha (d) for crescente e tiver acabado de processar o elemento nº 0, ele passará a processar o elemento nº 1. No entanto, o elemento que tinha o nº 1 antes da exclusão do elemento nº 0 passará a ter o nº 0. Ele será, então, ignorado pelo ciclo.
  • Se o loop da linha (d) for decrescente e tiver acabado de processar o elemento nº n, ele passará a processar o elemento nº n-1. Após a exclusão do elemento nº n, o elemento nº n-1 não muda de número. Portanto, ele é processado na próxima iteração do loop.
  • linhas m-n: o estado dos botões [Effacer] depende da presença ou não de elementos nas listas associadas
  • linha p: a lista l2 não tem mais elementos selecionados: desativa-se seu botão de transferência.

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

Propomos escrever o seguinte aplicativo:

Os componentes da janela são os seguintes:

n.º
tipo
nome
função
1
GroupBox
ver [6]
groupBox1
um contêiner de componentes. É possível adicionar outros componentes a ele.
Texto=Botões de opção
2
RadioButton
radioButton1
radioButton2
radioButton3
3 botões de opção — radioButton1 possui a propriedade Checked=True e a propriedade Text=1 - radioButton2 possui a propriedade Text=2 - radioButton3 possui a propriedade Text=3
Os botões de opção presentes no mesmo contêiner, neste caso o GroupBox, são mutuamente exclusivos: apenas um deles fica selecionado.
3
GroupBox
groupBox2
 
4
CheckBox
checkBox1
checkBox2
checkBox3
3 caixas de seleção. chechBox1 possui a propriedade Checked=True e a propriedade Text=A - chechBox2 possui a propriedade Text=B - chechBox3 possui a propriedade Text=C
5
ListBox
listBoxValeurs
uma lista que exibe 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 contêiner GroupBox

O evento que nos interessa para esses seis controles é o evento CheckChanged, que indica que o estado da caixa de seleção ou do botão de opção mudou. Esse estado é representado, em ambos os casos, pela propriedade booleana Checked, que, quando verdadeira, significa que o controle 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 tratados pelo mesmo método affiche, podemos proceder da seguinte forma:

Selecione o componente radioButton1 e clique com o botão direito do mouse sobre ele para acessar suas propriedades:

Na guia événements [1], associamos o método affiche [2] ao evento CheckChanged. Isso significa que se deseja que o clique na opção A1 seja processado por um método chamado 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 seus eventos [3]. Ao lado do evento Click, há uma lista suspensa [4] na qual estão listados os métodos existentes capazes de processar esse evento. Aqui, há apenas o método affiche.. Selecionamos esse método. Repetimos esse processo para todos os demais componentes.

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


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) {
            // exibe o status do botão de opção ou da caixa de seleção
            // isso é 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. Isso nos permite, em seguida, realizar uma conversão de tipo para o tipo exato de sender. O método affiche grava na lista listBoxValeurs o nome do componente que originou o evento e o valor de sua propriedade Checked. Na execução de [7], observa-se que um clique em um botão de opção provoca dois eventos CheckChanged: um no botão anteriormente 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 controle deslizante ScrollBar permite que o usuário escolha um valor dentro de um intervalo de valores inteiros, representado pela “faixa” do controle deslizante sobre a qual um cursor se move. O valor do controle deslizante está disponível na propriedade Value.

  • Para um controle deslizante 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. Para um controle deslizante vertical, o mínimo é representado pela extremidade superior e o máximo pela extremidade inferior. Esses valores são representados pelas propriedades Minimum e Maximum e têm valores padrão de 0 e 100.
  • Um clique nas extremidades do controle deslizante altera o valor em um incremento (positivo ou negativo), dependendo da extremidade clicada, denominada SmallChange, cujo valor padrão é 1.
  • Um clique em qualquer lado do cursor altera o valor em um incremento (positivo ou negativo), dependendo da extremidade clicada, denominada LargeChange, cujo valor padrão é 10.
  • Ao clicar na extremidade superior de um controle deslizante vertical, seu valor diminui. Isso pode surpreender o usuário comum, que normalmente espera ver o valor “aumentar”. Esse problema é resolvido atribuindo-se um valor negativo às propriedades SmallChange e LargeChange
  • Essas cinco propriedades (Value, Minimum, Maximum, SmallChange, LargeChange) estão acessíveis para leitura e gravação.
  • O principal evento do inversor é aquele que sinaliza uma alteração de valor: o evento Scroll.

Um componente NumericUpDown está próximo ao variador: ele também possui as propriedades Minimum, Maximum e Value, com valores padrão 0, 100, 0. Mas, neste caso, a propriedade Value é exibida em um campo de entrada que faz parte integrante do controle. O usuário pode alterar esse valor por conta própria, a menos que a propriedade ReadOnly do controle tenha sido definida como verdadeira. O valor do incremento é definido pela propriedade Increment, cujo valor padrão é 1. O principal evento do componente NumericUpDown é aquele que sinaliza uma alteração de valor: o evento ValueChanged

O código do aplicativo é o seguinte:


using System.Windows.Forms;

namespace Chap5 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
            // definimos as características do variador 1
            hScrollBar1.Value = 7;
            hScrollBar1.Minimum = 1;
            hScrollBar1.Maximum = 130;
            hScrollBar1.LargeChange = 11;
            hScrollBar1.SmallChange = 1;
            // atribuímos ao variador 2 as mesmas características do variador 1
            hScrollBar2.Value = hScrollBar1.Value;
            hScrollBar2.Minimum = hScrollBar1.Minimum;
            hScrollBar2.Maximum = hScrollBar1.Maximum;
            hScrollBar2.LargeChange = hScrollBar1.LargeChange;
            hScrollBar2.SmallChange = hScrollBar1.SmallChange;
            // o mesmo vale para o 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
            // seu valor é repassado para o variador 2 e para o Label
            hScrollBar2.Value = hScrollBar1.Value;
            labelValeurHS1.Text = hScrollBar1.Value.ToString();
        }

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

7.3. Eventos do mouse

Ao desenhar em um contêiner, é importante saber a posição do mouse para, por exemplo, exibir um ponto ao clicar. Os movimentos do mouse geram eventos no contêiner em que ele se move.

  • [1]: eventos que ocorrem durante o deslocamento do mouse sobre o formulário ou um controle
  • [2]: eventos que ocorrem durante um arrastar e soltar (Drag'nDrop)
MouseEnter
o mouse acaba de entrar na área do controle
MouseLeave
o mouse acaba de sair da área do controle
MouseMove
o mouse está se movendo na área de controle
MouseDown
Pressão no botão esquerdo do mouse
MouseUp
Solte o botão esquerdo do mouse
DragDrop
o usuário solta um objeto no controle
DragEnter
o usuário entra na área do controle arrastando um objeto
DragLeave
o usuário sai da área do controle arrastando um objeto
DragOver
o usuário passa por cima da área do controle ao arrastar um objeto

Aqui está um aplicativo que ajuda a entender melhor em quais momentos ocorrem os diferentes eventos do mouse:

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

Para acompanhar os movimentos do mouse nos três controles, escrevemos apenas um único manipulador, o manipulador affiche:

O código do procedimento affiche é o seguinte:


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

Sempre que o mouse entra no domínio de um controle, seu sistema de coordenadas muda. Sua origem (0,0) é o canto superior esquerdo do controle sobre o qual ele se encontra. Assim, durante a execução, ao passar o mouse do formulário para o botão, percebe-se claramente a mudança nas coordenadas. Para visualizar melhor essas mudanças de área do mouse, pode-se utilizar a propriedade Cursor [1] dos controles:

Essa propriedade permite definir a forma do cursor do mouse quando ele entra na área do controle. Assim, em 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 detectar as entradas e saídas do mouse na lista 2, processamos os eventos MouseEnter e MouseLeave dessa mesma lista:


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

        private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
            // se sinaliza 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) {
            // notifica-se o evento
            listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
        }

        private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
            // notifica 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 manipulador de clique no botão Effacer:


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

7.4. Criar uma janela com menu

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

Para criar um menu, selecione o componente “MenuStrip” na barra “Menus & Tollbars”:

  • [1]: seleção do componente [MenuStrip]
  • [2]: surge então um menu que é inserido no formulário com campos em branco intitulados “Digite aqui”. Basta inserir as diferentes opções do menu nesses campos.
  • [3]: o texto “Opções A” foi digitado. Passamos para o texto [4].
  • [5]: os nomes das opções A foram inseridos. Passamos para o nome [6]
  • [6]: as primeiras opções B
  • [7]: abaixo de B1, insere-se um separador. Ele está disponível em uma lista suspensa associada ao texto “Type Here”
  • [8]: para criar um submenu, use a seta [8] e digite o submenu em [9]

Resta nomear os diferentes componentes do formulário:

n.º
tipo
nome(s)
função
1
Etiqueta
labelStatut
para exibir 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 de 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 controles, assim como os demais componentes visuais, e possuem propriedades e eventos. Por exemplo, as propriedades da opção de menu A1 são as seguintes:

 

Duas propriedades são utilizadas em nosso exemplo:

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

Na estrutura do menu, selecione a opção A1 e clique com o botão direito do mouse para acessar as propriedades do controle:

Na guia événements [1], associamos o método affiche [2] ao evento Click. Isso significa que se deseja que o clique na opção A1 seja processado por um método chamado affiche. O Visual Studio gera automaticamente o método affiche na janela de código:


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

Nesse método, nos limitaremos a exibir no rótulo labelStatut a propriedade Text da opção de menu que foi clicada:


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 de menu são do tipo ToolStripMenuItem; portanto, é necessário realizar uma conversão de tipo de object para ToolStripMenuItem.

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

Vamos executar o aplicativo e selecionar um item do menu:

 

7.5. Componentes não visuais

Vamos nos concentrar agora em alguns componentes não visuais: eles são utilizados durante o projeto, mas não são visíveis durante a execução.

7.5.1. Caixas de diálogo , OpenFileDialog e SaveFileDialog

Vamos criar o seguinte aplicativo:

Os controles são os seguintes:

tipo
nome
função
1
TextBox
TextBoxLignes
texto digitado pelo usuário ou carregado de um arquivo
MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True
2
Botão
buttonSauvegarder
permite salvar o texto de [1] em um arquivo de texto
3
Botão
buttonCharger
permite carregar o conteúdo de um arquivo de texto no [1]
4
Botão
buttonEffacer
apaga o conteúdo de [1]
5
SaveFileDialog
saveFileDialog1
componente que permite escolher o nome e o local do arquivo de backup de [1]. Esse componente é selecionado na barra de ferramentas [7] e simplesmente arrastado para o formulário. Ele é então registrado, 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 arquivo a ser carregado no [1].

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


        private void buttonEffacer_Click(object sender, EventArgs e) {
            // insere a string vazia no TexBox
            textBoxLignes.Text = "";
}

Utilizaremos as seguintes propriedades e métodos da classe SaveFileDialog:

Campo
Tipo
Função
string Filter
Propriété
os tipos de arquivo disponíveis na lista suspensa de tipos de arquivo da caixa de diálogo
int FilterIndex
Propriété
o número do tipo de arquivo sugerido por padrão na lista acima. Começa em 0.
string InitialDirectory
Propriété
a pasta indicada inicialmente para salvar o arquivo
string FileName
Propriété
o nome do arquivo de backup indicado pelo usuário
DialogResult.ShowDialog()
Méthode
método que exibe a caixa de diálogo de salvamento. Retorna um resultado do tipo DialogResult.

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

1
lista suspensa criada a partir da propriedade Filter. O tipo de arquivo proposto por padrão é definido por FilterIndex
2
pasta atual, definida por InitialDirectory caso essa propriedade tenha sido preenchida
3
nome do arquivo escolhido ou digitado diretamente pelo usuário. Estará disponível na propriedade FileName
4
botões Salvar/Cancelar. Se o botão Enregistrer for utilizado, a função ShowDialog retorna o resultado DialogResult.OK

O procedimento de salvamento pode ser escrito da seguinte forma:


private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
            // salva-se o campo de entrada em um arquivo 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 seu resultado
            if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
                // recupera-se o nome do arquivo
                string nomFichier = saveFileDialog1.FileName;
                StreamWriter fichier = null;
                try {
                    // abre-se o arquivo para gravação
                    fichier = new StreamWriter(nomFichier);
                    // escreve-se o texto nele
                    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 arquivo
                    if (fichier != null) {
                        fichier.Dispose();
                    }
                }
            }
        }
  • linha 4: define-se a pasta inicial (InitialDirectory) como a pasta (Application.ExecutablePath) que contém o executável do aplicativo.
  • linha 5: definem-se os tipos de arquivos a serem apresentados. Observe-se a sintaxe dos filtros: filtre1|filtre2|..|filtren com filtro i = Texto|modelo de arquivo. Aqui, o usuário poderá escolher entre os arquivos *.txt e *.*.
  • linha 6: define-se o tipo de arquivo a ser apresentado primeiro ao usuário. Aqui, o índice 0 refere-se aos arquivos *.txt.
  • linha 8: a caixa de diálogo é exibida e seu resultado é recuperado. Enquanto a caixa de diálogo estiver exibida, o usuário não terá mais acesso ao formulário principal (caixa de diálogo chamada modal). O usuário define o nome do arquivo a ser salvo e sai da caixa de diálogo clicando no botão Enregistrer, no botão Annuler, ou fechando a caixa de diálogo. O resultado do método ShowDialog é DialogResult.OK apenas se o usuário tiver usado o botão Enregistrer para sair da caixa de diálogo.
  • Feito isso, o nome do arquivo a ser criado está agora na propriedade FileName do objeto saveFileDialog1. Chega-se então à criação clássica de um arquivo de texto. Nele, escreve-se o conteúdo de TextBox: textBoxLignes.Text, ao mesmo tempo em que se gerencia as exceções que possam ocorrer.

A classe OpenFileDialog é muito semelhante à classe SaveFileDialog. Utilizaremos os mesmos métodos e propriedades de antes. O método ShowDialog exibe uma caixa de diálogo semelhante à seguinte:

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

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


private void buttonCharger_Click(object sender, EventArgs e) {
            // carrega-se um arquivo 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 seu resultado
            if (openFileDialog1.ShowDialog() == DialogResult.OK) {
                // recupera-se o nome do arquivo
                string nomFichier = openFileDialog1.FileName;
                StreamReader fichier = null;
                try {
                    // abre-se o arquivo para leitura
                    fichier = new StreamReader(nomFichier);
                    // lê-se todo o arquivo e ele é colocado 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 arquivo
                    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 do aplicativo.
  • linha 5: definem-se os tipos de arquivos a serem apresentados. Observe-se a sintaxe dos filtros: filtre1|filtre2|..|filtren com filtro i = Texto|modelo de arquivo. Aqui, o usuário poderá escolher entre os arquivos *.txt e *.*.
  • linha 6: define-se o tipo de arquivo a ser apresentado primeiro ao usuário. Aqui, o índice 0 refere-se aos arquivos *.txt.
  • linha 8: a caixa de diálogo é exibida e seu resultado é recuperado. Enquanto a caixa de diálogo estiver exibida, o usuário não terá mais acesso ao formulário principal (caixa de diálogo chamada modal). O usuário define o nome do arquivo a ser salvo e sai da caixa de diálogo clicando no botão Ouvrir, no botão Annuler, ou fechando a caixa de diálogo. O resultado do método ShowDialog é DialogResult.OK apenas se o usuário tiver usado o botão Enregistrer para sair da caixa de diálogo.
  • Feito isso, o nome do arquivo a ser criado está agora na propriedade FileName do objeto openFileDialog1. Passa-se então para a leitura clássica de um arquivo de texto. Observe, na linha 16, o método que permite ler um arquivo na íntegra.

7.5.2. Caixas de diálogo FontColor e ColorDialog

Continuamos o exemplo anterior, adicionando dois novos botões e dois novos controles 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 a fonte do TextBox
3
ColorDialog
colorDialog1
o componente que permite a seleção de uma cor — extraído da 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 possuem 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 uma fonte [2]:

  • [1]: se o usuário fechar a caixa de diálogo com o botão OK, o resultado do método ShowDialog é DialogResult.OK e a cor selecionada está na propriedade Color do objeto ColorDialog utilizado.
  • [2]: se o usuário fechar a caixa de diálogo usando o botão OK, o resultado do método ShowDialog é DialogResult.OK e a fonte selecionada está na propriedade Font do objeto FontDialog utilizado.

Agora temos os elementos necessários para processar 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) {
                // alterando a propriedade Forecolor do TextBox
                textBoxLignes.ForeColor = colorDialog1.Color;
            }//se
        }

        private void buttonPolice_Click(object sender, EventArgs e) {
            // escolha de uma fonte
            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. Nesse caso, essa cor é a escolhida pelo usuário na caixa de diálogo do tipo [ColorDialog].
  • linha [12]: a propriedade [Font] de um componente TextBox designa a fonte do tipo [Font] dos caracteres do TextBox. Nesse caso, essa fonte é a escolhida pelo usuário na caixa de diálogo do tipo [FontDialog].

7.5.3. Temporizador

Propomos aqui escrever o seguinte aplicativo:

n.º
Tipo
Nome
Função
1
Marca
labelChrono
exibe um cronômetro
2
Botão
buttonArretMarche
botão de parar/iniciar o cronômetro
3
Temporizador
timer1
componente que emite 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 o qual um evento Tick é emitido.
Tick
o evento gerado ao final de Interval milissegundos
Enabled
torna o temporizador ativo (true) ou inativo (false)

No nosso exemplo, o temporizador se chama timer1 e 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") {
                // registra-se a hora de início
                début = DateTime.Now;
                // exibimos
                labelChrono.Text = "00:00:00";
                // inicia-se o cronômetro
                timer1.Enabled = true;
                // alterar o texto do botão
                buttonArretMarche.Text = "Arrêt";
                // fim
                return;
            }//
            if (buttonArretMarche.Text == "Arrêt") {
                // parada do cronômetro
                timer1.Enabled = false;
                // alterar o texto do botão
                buttonArretMarche.Text = "Marche";
                // fim
                return;
            }
        }

    }
}
  • linha 13: o procedimento que processa o clique no botão Parar/Iniciar.
  • linha 15: o texto do botão Parar/Iniciar é “Parar” ou “Iniciar”. Portanto, é necessário verificar esse texto para saber o que fazer.
  • linha 17: no caso de “Ligar”, registra-se a hora de início em uma 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 muda para “Parar”.
  • linha 27: no caso de “Parar”
  • linha 29: o temporizador é parado (Enabled=false)
  • linha 31: o texto do botão passa para “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: registramos 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 exibida no cronômetro na forma hh:mm:ss. Para isso, utilizamos as propriedades Hours, Minutes, Seconds do objeto TimeSPan, que representam, respectivamente, as horas, minutos e segundos da duração que exibimos no formato ToString("d2") para obter uma exibição com 2 dígitos.

7.6. Aplicativo de exemplo — versão 6

Retomamos a aplicação de exemplo IMPOTS. A versão mais recente 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 sua integração na aplicação eram garantidas pelo Spring.

Nesta nova versão, a camada [ui] será implementada 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 é composto pelos seguintes elementos:
  • [Program.cs]: a classe que inicia o aplicativo
  • [Form1.cs]: a classe de um primeiro formulário
  • [Form2]: a classe de um segundo formulário
  • [lib], detalhado em [2]: nele foram incluídos todos os DLL necessários para o projeto:
  • [ImpotsV5-dao.dll]: o DLL da camada [dao] gerado no parágrafo 6.4.3;
  • [ImpotsV5-metier.dll]: o DLL da camada [dao] gerada no parágrafo 6.4.4;
  • [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: os DLL do Spring já utilizados 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 arquivos DLL da pasta [lib]
  • [App.config]: o arquivo de configuração do projeto. É idêntico ao da versão anterior, descrito no parágrafo 6.4.6;
  • [DataImpot.txt]: o arquivo 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 para inserção dos parâmetros do cálculo do imposto [A], já apresentado anteriormente. O formulário [Form2] [B] serve para exibir uma mensagem de erro:

7.6.2. A classe [Program.cs]

A classe [Program.cs] inicia o aplicativo. 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 do aplicativo.
        /// </summary>
        [STAThread]
        static void Main() {
            // código gerado pelo Visual Studio
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            // --------------- Código do desenvolvedor
            // 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) {
                // armazenamento de exceção
                ex = e1;
            }
            // formulário a ser exibido
            Form form = null;
            // houve alguma exceção?
            if (ex != null) {
                // sim — cria-se a mensagem de erro a ser exibida
                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 é passada a mensagem de erro a ser exibida
                Form2 form2 = new Form2();
                form2.MsgErreur = msgErreur.ToString();
                // essa será a janela a ser exibida
                form = form2;
            } else {
                // tudo correu bem
                // criação da interface gráfica [Form1], para a qual é passada a referência na camada [metier]
                Form1 form1 = new Form1();
                form1.Metier = metier;
                // essa será a janela a ser exibida
                form = form1;
            }
            // exibição da janela
            Application.Run(form);
        }
    }
}

O código gerado pelo Visual Studio foi complementado a partir da linha 19. O aplicativo utiliza o arquivo [App.config] e a seguir:


<?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 arquivo [App.config] anterior para instanciar as camadas [metier] e [dao]
  • linha 26: processamento do arquivo [App.config]
  • linha 28: recuperação de uma referência na camada [metier]
  • linha 31: registro de uma eventual exceção
  • linha 34: a referência form indicará o formulário a ser exibido (form1 ou form2)
  • linhas 36-50: se houver 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 ser exibida. Ela é formada 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, esse formulário possui uma propriedade pública MsgErreur, que é a mensagem de erro a ser exibida:

        public string MsgErreur { private get; set; }

Preenche-se essa propriedade.

  • linha 49: a referência form, que designa a janela a ser exibida, é inicializada. Observe-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 houve exceção. Preparamos a exibição de um formulário do tipo [Form1].
  • linha 53: é criado um formulário do tipo [Form1].
  • linha 54: como veremos mais adiante, esse formulário possui uma propriedade pública Metier, que é uma referência à camada [metier]:

                public IImpotMetier Metier { private get; set; }

Preenche-se essa propriedade.

  • linha 56: a referência form, que designa a janela a ser exibida, é inicializada. Observe-se novamente o polimorfismo em ação. form1 não é do tipo [Form], mas do tipo [Form1], um tipo derivado de [Form].
  • linha 59: a janela referenciada por form é exibida.

7.6.3. O formulário [Form1]

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

Os controles são os seguintes

n.º
tipo
nome
função
0
GroupBox
groupBox1
Text=Você é casado(a)?
1
RadioButton
radioButtonOui
marque se for casado(a)
2
RadioButton
radioButtonNon
marcado se solteiro
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
valor do imposto a pagar
BorderStyle=Fixed3D
6
Botão
buttonCalculer
inicia o cálculo do imposto
7
Botão
buttonEffacer
recupera o formulário no estado em que se encontrava no momento do carregamento
8
Botão
buttonQuitter
para sair do aplicativo

Regras de funcionamento do formulário

  • o botão Calculer permanece desativado enquanto não houver nada no campo do salário
  • se, ao iniciar o cálculo, for constatado 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);
                // retorno ao campo com erro
                textSalaire.Focus();
                // seleção do texto do campo de preenchimento
                textSalaire.SelectAll();
                // retorno à interface de preenchimento
                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()!="";
        }

    }
}

Comentamos apenas as partes importantes:

  • linha [8]: a propriedade pública Metier, que permite que a classe de execução [Program.cs] insira 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]. Vale destacar a simplicidade dessa operação, obtida graças ao encapsulamento da camada [metier] na camada DLL.

7.6.4. O formulário [Form2]

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

As verificações são as 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 a mensagem de erro
            textBoxErreur.Text = MsgErreur;
            // desmarca todo o texto
            textBoxErreur.Select(0, 0);
        }
    }
}
  • linha 6: a propriedade pública MsgErreur, que permite que a classe de lançamento [Program.cs] insira na [Form2] a mensagem de erro a ser exibida. Essa mensagem é exibida durante o processamento do evento Load, linhas 12 a 16.
  • linha 14: a mensagem de erro é inserida no TextBox
  • linha 16: remove-se a seleção realizada na operação anterior. [TextBox].Select(início,comprimento) seleciona (destaca) longueur caracteres a partir do caractere nº début. [TextBox].Select(0,0) desmarca todo o texto.

7.6.5. Conclusão

Voltemos à arquitetura de três camadas utilizada:

Essa arquitetura nos permitiu substituir a implementação de console da camada [ui] existente por uma implementação gráfica, sem alterar nada nas camadas [metier] e [dao]. Pudemos nos concentrar na camada [ui] sem nos preocuparmos com possíveis impactos nas outras camadas. Esse é o principal benefício das arquiteturas de três camadas. Veremos outro exemplo mais adiante, quando a camada [dao], que atualmente utiliza os dados de um arquivo de texto, for substituída por uma camada [dao] que utiliza os dados de um banco de dados. Veremos que isso ocorrerá sem impacto nas camadas [ui] e [metier].