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:
para definir a cor de fundo da janela | |
para definir a cor dos desenhos ou do texto na janela | |
para associar um menu à janela | |
para atribuir um título à janela | |
para definir o tipo de janela | |
para definir o tipo de letra das entradas na janela | |
para definir o nome da janela |
Aqui, definimos as propriedades Text e Name:
Campos de introdução e botões - 1 | |
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:
- selecione os diferentes componentes a formatar em conjunto (mantenha a tecla Ctrl premida enquanto clica para selecionar os componentes)
- 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
a mensagem a apresentar | |
o título da janela | |
os botões presentes na janela | |
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:
botões | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
O parâmetro icon pode assumir valores entre as seguintes constantes (prefixadas por MessageBoxIcon, conforme mostrado na linha 10) acima:
![]() | idem Stop | ||
idem Aviso | ![]() | ||
idem Asterisk | ![]() | ||
![]() | idem Hand | ||
![]() |
O método Show é um método estático que devolve um resultado do tipo [System.Windows.Forms.DialogResult], que é uma enumeração:

Para saber em que botão o utilizador clicou para fechar a janela do tipo MessageBox, escrever-se-á:
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:
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:
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:
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.
o formulário está a ser carregado | |
o formulário está a ser fechado | |
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:
para aceitar várias linhas de texto | |
para definir se o controlo deve ter barras de deslocamento (Horizontal, Vertical, Both) ou não (None) | |
se for igual a «true», a tecla Enter irá passar para a linha seguinte | |
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:
lista não suspensa com campo de edição | |
lista suspensa com área de edição | |
lista suspensa sem campo de edição |
Por predefinição, o tipo de um ComboBox é DropDown.
A classe ComboBox tem um único construtor:
cria uma lista suspensa vazia |
Os elementos do ComboBox estão disponíveis na propriedade Items:
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:
número de elementos do combo | |
elemento i da lista suspensa | |
adiciona o objeto o como último elemento da lista suspensa | |
adiciona uma matriz de objetos ao final da lista suspensa | |
adiciona o objeto o na posição i da lista suspensa | |
remove o elemento i da lista suspensa | |
remove o objeto o da lista suspensa | |
elimina todos os elementos da lista suspensa | |
retorna a posição i do objeto o na lista suspensa | |
índice do elemento selecionado | |
elemento selecionado | |
texto exibido do elemento selecionado | |
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:
só é possível selecionar um único elemento | |
é 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. | |
É 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)
o rato acabou de entrar na área do controlo | |
o rato acabou de sair da área do controlo | |
o rato está a mover-se na área de controlo | |
Clicou no botão esquerdo do rato | |
Soltar o botão esquerdo do rato | |
O utilizador solta um objeto no controlo | |
O utilizador entra na área do controlo ao arrastar um objeto | |
o utilizador sai da área do controlo arrastando um objeto | |
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:
o nome do controlo de menu | |
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 |
Propriété | os tipos de ficheiros disponíveis na lista suspensa de tipos de ficheiros da caixa de diálogo | |
Propriété | o número do tipo de ficheiro proposto por predefinição na lista acima. Começa em 0. | |
Propriété | a pasta inicialmente apresentada para guardar o ficheiro | |
Propriété | o nome do ficheiro de cópia de segurança indicado pelo utilizador | |
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:
![]() |
lista suspensa criada a partir da propriedade Filter. O tipo de ficheiro proposto por predefinição é definido por FilterIndex | |
pasta atual, definida por InitialDirectory, caso esta propriedade tenha sido preenchida | |
nome do ficheiro selecionado ou introduzido diretamente pelo utilizador. Estará disponível na propriedade FileName | |
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:
![]() |
lista suspensa criada a partir da propriedade Filter. O tipo de ficheiro proposto por predefinição é definido por FilterIndex | |
pasta atual, definida por InitialDirectory, caso esta propriedade tenha sido preenchida | |
nome do ficheiro selecionado ou introduzido diretamente pelo utilizador. Estará disponível na propriedade FileName | |
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:
número de milissegundos após os quais é emitido um evento Tick. | |
o evento gerado ao fim de Interval milissegundos | |
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].


































































