7. Interfaces gráficas con C# y VS.NET
7.1. Conceptos básicos de las interfaces gráficas
7.1.1. Un primer proyecto
Creemos un primer proyecto de tipo «Aplicación de Windows»:
![]() |
- [1]: crear un nuevo proyecto
- [2]: de tipo «Aplicación de Windows»
- [3]: el nombre del proyecto no importa por ahora
- [4]: el proyecto creado
![]() |
- [5]: guardamos la solución actual
- [6]: nombre del proyecto
- [7]: carpeta de la solución
- [8]: nombre de la solución
- [9]: se creará una carpeta para la solución [Chap5]. Los proyectos de esta se encontrarán en subcarpetas.
![]() |
- [10]: el proyecto [01] dentro de la solución [Chap5]:
- [Program.cs] es la clase principal del proyecto
- [Form1.cs] es el archivo fuente que gestionará el comportamiento de la ventana [11]
- [Form1.Designer.cs] es el archivo fuente que encapsulará la información sobre los componentes de la ventana [11]
- [11]: el archivo [Form1.cs] en modo «diseño» (design)
- [12]: la aplicación generada se puede ejecutar pulsando (Ctrl-F5). Se muestra la ventana [Form1]. Se puede mover, cambiar su tamaño y cerrarla. Así pues, disponemos de los elementos básicos de una ventana gráfica.
La clase principal [Program.cs] es la siguiente:
using System;
using System.Windows.Forms;
namespace Chap5 {
static class Program {
/// <summary>
/// El punto de entrada principal de la aplicación.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
- línea 2: las aplicaciones con formularios utilizan el espacio de nombres System.Windows.Forms.
- línea 4: el espacio de nombres inicial se ha renombrado como Chap5.
- línea 10: al ejecutar el proyecto (Ctrl-F5), se ejecuta el método [Main].
- Líneas 11-13: la clase Application pertenece al espacio de nombres System.Windows.Forms. Contiene métodos estáticos para iniciar y cerrar aplicaciones gráficas de Windows.
- línea 11: opcional; permite aplicar diferentes estilos visuales a los controles colocados en un formulario
- línea 12: opcional; establece el motor de renderizado del texto de los controles: GDI+ (verdadero), GDI (falso)
- línea 13: la única línea imprescindible del método [Main]: instancia la clase [Form1], que es la clase del formulario, y le pide que se ejecute.
El archivo fuente [Form1.cs] es el siguiente:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
}
}
- línea 5: la clase Form1 deriva de la clase [System.Windows.Forms.Form], que es la clase padre de todas las ventanas. La palabra clave «partial» indica que la clase es parcial y que puede completarse con otros archivos fuente. Este es el caso aquí, donde la clase Form1 se distribuye en dos archivos:
- [Form1.cs]: en el que se encuentra el comportamiento del formulario, en particular sus gestores de eventos
- [Form1.Designer.cs]: en el que se encuentran los componentes del formulario y sus propiedades. Este archivo tiene la particularidad de que se regenera cada vez que el usuario modifica la ventana en modo [conception].
- líneas 6-8: el constructor de la clase Form1
- línea 7: invoca el método InitializeComponent. Se observa que este método no está presente en [Form1.cs]. Se encuentra en [Form1.Designer.cs].
El archivo fuente [Form1.Designer.cs] es el siguiente:
namespace Chap5 {
partial class Form1 {
/// <summary>
/// Variable de diseño obligatoria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Limpia cualquier recurso que se esté utilizando.
/// </summary>
/// <param name="disposing">true si se deben eliminar los recursos gestionados; en caso contrario, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#Código generado por el Diseñador de formularios de Windows
/// <summary>
/// Método obligatorio para la compatibilidad con el Diseñador: no modificar
/// el contenido de este método con el 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);
}
#fin de región
}
}
- línea 2: sigue tratándose de la clase Form1. Cabe señalar que ya no es necesario repetir que deriva de la clase Form.
- líneas 25-37: el método InitializeComponent, invocado por el constructor de la clase [Form1]. Este método creará e inicializará todos los componentes del formulario. Se regenera cada vez que se produce un cambio en el mismo en modo [conception]. Se crea una sección, denominada région, para delimitarla (líneas 19-39). El desarrollador no debe añadir código en esta región: se sobrescribirá en la siguiente regeneración.
En un primer momento, es más sencillo no prestar atención al código de [Form1.Designer.cs]. Se genera automáticamente y es la traducción al lenguaje C# de las opciones que el desarrollador elige en el modo [conception]. Veamos un primer ejemplo:
![]() |
- [1]: selecciona el modo [conception] haciendo doble clic en el archivo [Form1.cs]
- [2]: hacer clic con el botón derecho del ratón sobre el formulario y seleccionar [Properties]
- [3]: la ventana de propiedades de [Form1]
- [4]: la propiedad [Text] representa el título de la ventana
- [5]: el cambio en la propiedad [Text] se tiene en cuenta tanto en el modo [conception] como en el código fuente [Form1.Designer.cs]:
private void InitializeComponent() {
this.SuspendLayout();
...
this.Text = "Mon 1er formulaire";
...
}
7.1.2. Un segundo proyecto
7.1.2.1. El formulario
Comenzamos un nuevo proyecto denominado 02. Para ello, seguimos el procedimiento explicado anteriormente para crear un proyecto. La ventana que hay que crear es la siguiente:
![]() |
Los componentes del formulario son los siguientes:
n.º | nombre | tipo | función |
1 | labelSaisie | Etiqueta | una descripción |
2 | textBoxSaisie | TextBox | un campo de entrada |
3 | buttonAfficher | Botón | para mostrar en un cuadro de diálogo el contenido del campo de entrada textBoxSaisie |
Para crear esta ventana, se puede proceder de la siguiente manera:
![]() |
- [1]: hacer clic con el botón derecho del ratón en el formulario, fuera de cualquier componente, y seleccionar la opción [Properties]
- [2]: la hoja de propiedades de la ventana aparece en la esquina inferior derecha de Visual Studio
Entre las propiedades del formulario, cabe destacar:
para establecer el color de fondo de la ventana | |
para establecer el color de los elementos gráficos o del texto de la ventana | |
para asociar un menú a la ventana | |
para asignar un título a la ventana | |
para establecer el tipo de ventana | |
para establecer el tipo de letra de los textos de la ventana | |
para establecer el nombre de la ventana |
Aquí configuramos las propiedades Text y Name:
Campos de entrada y botones - 1 | |
frmSaisiesBoutons |
![]() |
- [1]: selecciona el kit de herramientas [Common Controls] de entre los kits de herramientas que ofrece Visual Studio
- [2, 3, 4]: hacer doble clic sucesivamente en los componentes [Label], [Button] y [TextBox]
- [5]: los tres componentes ya están en el formulario
Para alinear y ajustar correctamente el tamaño de los componentes, se pueden utilizar los elementos de la barra de herramientas:
![]() | |||||||||
El principio del formateo es el siguiente:
- selecciona los distintos componentes que deseas formatear juntos (mantén pulsada la tecla Ctrl mientras haces clic para seleccionar los componentes)
- seleccione el tipo de formato deseado:
- (continuación)
- las opciones Align permiten alinear los componentes por la parte superior, inferior, izquierda, derecha o central
- las opciones «Make Same Size» permiten que los componentes tengan la misma altura o la misma anchura
- La opción «Horizontal Spacing» permite alinear horizontalmente los componentes con espacios entre ellos de la misma anchura. Lo mismo ocurre con la opción «Vertical Spacing» para alinear verticalmente.
- La opción Center permite centrar un componente horizontalmente (Horizontally) o verticalmente (Vertically) en la ventana
Una vez colocados los componentes, configuramos sus propiedades. Para ello, haz clic con el botón derecho del ratón sobre el componente y selecciona la opción Properties:
![]() |
- [1]: selecciona el componente para abrir su ventana de propiedades. En ella, modifica las siguientes propiedades: nombre: labelSaisie, texto: Saisie
- [2]: haz lo mismo: nombre: textBoxSaisie, texto: no introduzcas nada
- [3]: nombre: buttonAfficher, texto: Afficher
- [4]: la propia ventana: nombre: frmSaisiesBoutons, texto: Campos de entrada y botones - 1
- [5]: ejecuta (Ctrl-F5) el proyecto para obtener una primera vista previa de la ventana en funcionamiento.
Lo que se ha hecho en el modo [conception] se ha traducido al 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;
}
}
- líneas 53-55: los tres componentes han dado lugar a tres campos privados de la clase [Form1]. Cabe señalar que los nombres de estos campos son los nombres asignados a los componentes en el modo [conception]. Lo mismo ocurre con el formulario de la línea 2, que es la propia clase.
- líneas 7-9: se crean los tres objetos de tipo [Label], [TextBox] y [Button]. A través de ellos se gestionan los componentes visuales.
- líneas 14-19: configuración de la etiqueta labelSaisie
- líneas 23-29: configuración del botón buttonAfficher
- líneas 33-36: configuración del campo de entrada textBoxSaisie
- líneas 40-47: configuración del formulario frmSaisiesBoutons. Cabe destacar, en las líneas 43-45, la forma de añadir componentes al formulario.
Este código es fácil de entender. De este modo, es posible crear formularios mediante código sin necesidad de utilizar el modo [conception]. En la documentación MSDN de Visual Studio se ofrecen numerosos ejemplos al respecto. Dominar este código permite crear formularios en tiempo de ejecución: por ejemplo, crear sobre la marcha un formulario que permita actualizar una tabla de una base de datos, cuya estructura no se conoce hasta el momento de la ejecución.
Ahora nos queda escribir el procedimiento para gestionar un clic en el botón Afficher. Selecciona el botón para acceder a su ventana de propiedades. Esta cuenta con varias pestañas:
![]() |
- [1]: lista de propiedades por orden alfabético
- [2]: eventos relacionados con el control
Se puede acceder a las propiedades y eventos de un control por categorías o por orden alfabético:
- [3]: Propiedades o eventos por categoría
- [4]: Propiedades o eventos por orden alfabético
La pestaña Events en el modo Catégories para el botón buttonAfficher es la siguiente:
![]() |
- [1]: la columna de la izquierda de la ventana muestra los posibles eventos del botón. Al hacer clic en un botón se produce el evento Click.
- [2]: la columna de la derecha contiene el nombre del procedimiento que se invoca cuando se produce el evento correspondiente.
- [3]: si se hace doble clic en la celda del evento Click, se pasa automáticamente a la ventana de código para escribir el controlador del evento Click en el botón 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) {
}
}
}
- líneas 10-12: el esqueleto del controlador del evento Click del botón denominado buttonAfficher. Cabe destacar los siguientes puntos:
- el método se denomina según el esquema nomDuComposant_NomEvénement
- el método es privado. Recibe dos parámetros:
- sender: es el objeto que ha provocado el evento. Si el procedimiento se ejecuta tras hacer clic en el botón buttonAfficher, sender será igual a buttonAfficher. Es posible que el procedimiento buttonAfficher_Click se ejecute desde otro procedimiento. En ese caso, este último tendría total libertad para establecer como primer parámetro el objeto sender que elija.
- EventArgs: un objeto que contiene información sobre el evento. Para un evento Click, no contiene nada. Para un evento relacionado con los movimientos del ratón, en él se encontrarán las coordenadas (X, Y) del ratón.
- Aquí no utilizaremos ninguno de estos parámetros.
Escribir un controlador de eventos consiste en completar el esqueleto de código anterior. En este caso, queremos mostrar un cuadro de diálogo que contenga, si el campo textBoxSaisie no está vacío, el contenido de dicho campo ([1]); de lo contrario, un mensaje de error ([2]):
![]() |
El código para llevar esto a cabo podría ser el siguiente:
private void buttonAfficher_Click(object sender, EventArgs e) {
// se muestra el texto que se ha introducido en el 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);
}
La clase MessageBox sirve para mostrar mensajes en una ventana. Aquí hemos utilizado el siguiente método Show:
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
con
el mensaje que se va a mostrar | |
el título de la ventana | |
los botones que aparecen en la ventana | |
El icono que aparece en la ventana |
El parámetro buttons puede adoptar valores entre las siguientes constantes (precedidas por MessageBoxButtons, tal y como se muestra en la línea 7) anterior:
botones | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
El parámetro icon puede adoptar valores de entre las siguientes constantes (precedidas por MessageBoxIcon, tal y como se muestra en la línea 10) anteriores:
![]() | ídem Stop | ||
ídem Advertencia | ![]() | ||
Lo mismo que Asterisk | ![]() | ||
![]() | ídem Hand | ||
![]() |
El método Show es un método estático que devuelve un resultado de tipo [System.Windows.Forms.DialogResult], que es una enumeración:

Para saber qué botón ha pulsado el usuario para cerrar la ventana de tipo MessageBox, escribiremos:
7.1.2.2. El código relacionado con la gestión de eventos
Además de la función buttonAfficher_Click que hemos escrito, Visual Studio ha generado en el método InitializeComponents de [Form1.Designer.cs] —que crea e inicializa los componentes del formulario— la siguiente línea:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
«Click» es un evento de la clase Button [1, 2, 3]:
![]() |
- [5]: la declaración del evento [Control.Click] [4]. Así pues, vemos que el evento Click no es propio de la clase [Button]. Pertenece a la clase [Control], clase padre de la clase [Button].
- EventHandler es un prototipo (un modelo) de método denominado «delegado». Volveremos sobre ello más adelante.
- event es una palabra clave que restringe las funcionalidades de delegate y EventHandler: un objeto delegate tiene más funcionalidades que un objeto event.
El delegate EventHandler se define de la siguiente manera:
![]() |
El delegate EventHandler designa un modelo de método:
- cuyo primer parámetro es un tipo Object
- que tiene como segundo parámetro un tipo EventArgs
- que no devuelve ningún resultado
Este es el caso del método de gestión del clic en el botón buttonAfficher, generado por Visual Studio:
private void buttonAfficher_Click(object sender, EventArgs e);
Así pues, el método buttonAfficher_Click se corresponde con el prototipo definido por el tipo EventHandler. Para crear un objeto de tipo EventHandler, se procede de la siguiente manera:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
Dado que el método buttonAfficher_Click se corresponde con el prototipo definido por el tipo EventHandler, se podrá escribir:
Una variable de tipo delegate es, en realidad, una lista de referencias a métodos del tipo delegate. Para añadir un nuevo método M a la variable evtHandler anterior, se utilizará la sintaxis:
La notación += se puede utilizar incluso si evtHandler es una lista vacía.
Volvamos a la línea de [InitializeComponent], que añade un controlador de eventos al evento Click del objeto buttonAfficher:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Esta instrucción añade un método de tipo EventHandler a la lista de métodos del campo buttonAfficher.Click. Estos métodos se invocarán cada vez que se detecte el evento Click en el componente buttonAfficher. A menudo solo hay uno. Se le denomina «gestor del evento».
Volvamos a la firma de EventHandler:
private delegate void EventHandler(object sender, EventArgs e);
El segundo parámetro de delegate es un objeto de tipo EventArgs o de una clase derivada. El tipo EventArgs es muy general y, de hecho, no aporta ninguna información sobre el evento que se ha producido. Para un clic en un botón, esto es suficiente. Para el desplazamiento del ratón sobre un formulario, tendríamos un evento MouseMove de la clase [Form] definido por:
El delegate MouseEventHandler se define como:
![]() |
Se trata de una función delegada (delegate) de firma void f (object, MouseEventArgs). ¿La clase MouseEventArgs está definida por:
![]() |
La clase MouseEventArgs es más completa que la clase EventArgs. Por ejemplo, permite conocer las coordenadas X e Y del ratón en el momento en que se produce el evento.
7.1.2.3. Conclusion
A partir de los dos proyectos analizados, podemos concluir que, una vez creada la interfaz gráfica con Visual Studio, el trabajo del desarrollador consiste principalmente en escribir los controladores de eventos que desea gestionar para dicha interfaz gráfica. Visual Studio genera código automáticamente. Este código, que puede resultar complejo, puede ignorarse en un primer momento. Posteriormente, su estudio puede permitir una mejor comprensión de la creación y la gestión de formularios.
7.2. Los componentes básicos
A continuación, presentamos diversas aplicaciones que utilizan los componentes más habituales con el fin de descubrir sus principales métodos y propiedades. Para cada aplicación, mostramos la interfaz gráfica y el código relevante, principalmente el de los controladores de eventos.
7.2.1. Formulario Form
Comenzamos presentando el componente indispensable: el formulario sobre el que se colocan los componentes. Ya hemos presentado algunas de sus propiedades básicas. Aquí nos centramos en algunos eventos importantes de un formulario.
El formulario se está cargando | |
El formulario se está cerrando | |
El formulario está cerrado |
El evento Load se produce incluso antes de que se muestre el formulario. El evento Closing se produce cuando el formulario se está cerrando. Este cierre aún se puede detener mediante programación.
Creamos un formulario con el nombre Form1 sin ningún componente:
![]() |
- [1]: el formulario
- [2]: los tres eventos procesados
El código de [Form1.cs] es el siguiente:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// carga inicial del formulario
MessageBox.Show("Evt Load", "Load");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
// el formulario se está cerrando
MessageBox.Show("Evt FormClosing", "FormClosing");
// se solicita confirmación
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) {
// el formulario se va a cerrar
MessageBox.Show("Evt FormClosed", "FormClosed");
}
}
}
Utilizamos la función MessageBox para recibir notificaciones sobre los distintos eventos.
línea 10: El evento Load se producirá al iniciar la aplicación, incluso antes de que se muestre el formulario: | ![]() |
línea 15: El evento FormClosing se producirá cuando el usuario cierre la ventana. | ![]() |
línea 19: A continuación, le preguntamos si realmente desea salir la aplicación: | ![]() |
línea 20: Si responde «No», establecemos la propiedad Cancel del evento CancelEventArgs y que el método ha recibido como parámetro. Si establecemos esta propiedad en False, se cancela el cierre de la ventana se cancela; de lo contrario, continúa. El evento FormClosed se producirá entonces: | ![]() |
7.2.2. Etiquetas y cuadros de entrada TextBox
Ya hemos visto estos dos componentes. Label es un componente de texto y TextBox, un componente de campo de entrada. Su propiedad principal es Text, que designa bien el contenido del campo de entrada, bien el texto de la etiqueta. Esta propiedad es de lectura y escritura.
El evento que se suele utilizar para TextBox es TextChanged, que indica que el usuario ha modificado el campo de entrada. A continuación se muestra un ejemplo que utiliza el evento TextChanged para realizar un seguimiento de los cambios en un campo de entrada:
![]() |
n.º | tipo | nombre | función |
1 | TextBox | textBoxSaisie | campo de entrada |
2 | Etiqueta | labelControle | muestra el texto de 1 en tiempo real AutoSize=False, Text=(nada) |
3 | Botón | buttonEffacer | para borrar los campos 1 y 2 |
4 | Botón | buttonQuitter | para salir de la aplicación |
El código de esta aplicación es el siguiente:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// el contenido de TextBox ha cambiado; se copia en la etiqueta labelControle
labelControle.Text = textBoxSaisie.Text;
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// se borra el contenido del campo de entrada
textBoxSaisie.Text = "";
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
// Se hace clic en el botón «Salir»: se sale de la aplicación
Application.Exit();
}
private void Form1_Shown(object sender, System.EventArgs e) {
// se coloca el foco en el campo de entrada
textBoxSaisie.Focus();
}
}
}
- línea 24: el evento [Form].Shown se produce cuando se muestra el formulario
- línea 26: a continuación, se coloca el foco (para la introducción de datos) en el componente textBoxSaisie.
- línea 9: el evento [TextBox].TextChanged se produce cada vez que cambia el contenido de un componente TextBox
- línea 11: se copia el contenido del componente [TextBox] al componente [Label]
- línea 14: gestiona el clic en el botón [Effacer]
- línea 16: se introduce la cadena vacía en el componente [TextBox]
- línea 19: gestiona el clic en el botón [Quitter]
- línea 21: para detener la aplicación que se está ejecutando. Recordemos que el objeto Application sirve para iniciar la aplicación en el método [Main] de [Form1.cs]:
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
El siguiente ejemplo utiliza un TextBox de varias líneas:
![]() |
La lista de controles es la siguiente:
n.º | tipo | nombre | función |
1 | TextBox | textBoxLignes | campo de entrada de varias líneas Multiline=true, ScrollBars=Both, AcceptReturn=True, AcceptTab=True |
2 | TextBox | textBoxLigne | campo de entrada de una sola línea |
3 | Botón | buttonAjouter | Añade el contenido de 2 a 1 |
Para que un TextBox se convierta en un campo de varias líneas, se deben configurar las siguientes propiedades del control:
para que admita varias líneas de texto | |
para indicar si el control debe tener barras de desplazamiento (Horizontal, Vertical, Both) o no (None) | |
si es «true», la tecla Intro provocará un salto de línea | |
si es «true», la tecla Tab generará un salto de tabulación en el texto |
La aplicación permite escribir líneas directamente en [1] o añadirlas mediante [2] y [3].
El código de la aplicación es el siguiente:
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) {
// se añade el contenido de textBoxLigne al de textBoxLignes
textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
textBoxLigne.Text = "";
}
private void Form1_Shown(object sender, EventArgs e) {
// se establece el foco en el campo de entrada
textBoxLigne.Focus();
}
}
}
- línea 18: cuando se muestra el formulario (posiblemente Shown), se coloca el foco en el campo de entrada textBoxLigne
- línea 10: gestiona el clic en el botón [Ajouter]
- línea 12: el texto del campo de entrada textBoxLigne se añade al texto del campo de entrada textBoxLignes, seguido de un salto de línea.
- línea 13: se borra el campo de entrada textBoxLigne
7.2.3. Listas desplegables ComboBox
Creamos el siguiente formulario:
![]() |
n.º | tipo | nombre | función |
1 | ComboBox | comboNombres | contiene cadenas de caracteres DropDownStyle=DropDownList |
Un componente ComboBox es una lista desplegable acompañada de un campo de entrada: el usuario puede elegir un elemento en (2) o escribir texto en (1). Existen tres tipos de ComboBox determinados por la propiedad DropDownStyle:
lista no desplegable con campo de edición | |
lista desplegable con campo de edición | |
Lista desplegable sin campo de edición |
Por defecto, el tipo de un ComboBox es DropDown.
La clase ComboBox tiene un único constructor:
crea un menú desplegable vacío |
Los elementos de ComboBox están disponibles en la propiedad Items:
Se trata de una propiedad indexada, donde Items[i] designa el elemento i del cuadro combinado. Es de solo lectura.
Supongamos que C es un combo y C.Items su lista de elementos. Tenemos las siguientes propiedades:
número de elementos del combo | |
elemento i del cuadro combinado | |
añade el objeto o como último elemento del combo | |
añade una matriz de objetos al final del combo | |
añade el objeto o en la posición i del combo | |
elimina el elemento i del combo | |
elimina el objeto o del combo | |
elimina todos los elementos del combo | |
devuelve la posición i del objeto o en el cuadro combinado | |
índice del elemento seleccionado | |
elemento seleccionado | |
Texto mostrado del elemento seleccionado | |
texto que se muestra del elemento seleccionado |
Puede resultar sorprendente que un combo pueda contener objetos cuando, visualmente, muestra cadenas de caracteres. Si un ComboBox contiene un objeto obj, muestra la cadena obj.ToString(). Recordemos que todo objeto tiene un método ToString heredado de la clase object, que devuelve una cadena de caracteres «representativa» del objeto.
El elemento Item seleccionado en el menú desplegable C es C.SelectedItem o C.Items[C.SelectedIndex], donde C.SelectedIndex es el n.º del elemento seleccionado, que comienza en cero para el primer elemento. El texto seleccionado se puede obtener de diversas formas: C.SelectedItem.Text, C.Text
Al seleccionar un elemento de la lista desplegable se produce el evento SelectedIndexChanged, que puede utilizarse para recibir una notificación del cambio de selección en el cuadro combinado. En la siguiente aplicación, utilizamos este evento para mostrar el elemento que se ha seleccionado en la lista.
![]() |
El código de la aplicación es el siguiente:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
private int previousSelectedIndex=0;
public Form1() {
InitializeComponent();
// Rellenar el menú desplegable
comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
// selección del elemento n.º 0
comboBoxNombres.SelectedIndex = 0;
}
private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
int newSelectedIndex = comboBoxNombres.SelectedIndex;
if (newSelectedIndex != previousSelectedIndex) {
// el elemento seleccionado ha cambiado; se muestra
MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
// se anota el nuevo índice
previousSelectedIndex = newSelectedIndex;
}
}
}
}
- línea 5: previousSelectedIndex almacena el último índice seleccionado en el menú desplegable
- línea 10: se rellena el cuadro combinado con una matriz de cadenas de caracteres
- línea 12: se selecciona el primer elemento
- línea 15: el método que se ejecuta cada vez que el usuario selecciona un elemento del cuadro combinado. Al contrario de lo que podría sugerir el nombre del evento, este se produce incluso si el elemento seleccionado es el mismo que el anterior.
- línea 16: se anota el índice del elemento seleccionado
- línea 17: si es diferente del anterior
- línea 19: se muestran el número y el texto del elemento seleccionado
- línea 21: se anota el nuevo índice
7.2.4. Componente ListBox
Nos proponemos crear la siguiente interfaz:
![]() |
Los componentes de esta ventana son los siguientes:
n.º | tipo | nombre | función/propiedades |
0 | Form | Form1 | formulario FormBorderStyle=FixedSingle (marco no redimensionable) |
1 | TextBox | textBoxSaisie | campo de entrada |
2 | Botón | buttonAjouter | Botón que permite añadir el contenido del campo de entrada [1] a la lista [3] |
3 | ListBox | listBox1 | lista 1 SelectionMode=MultiExtended: |
4 | ListBox | listBox2 | Lista 2 SelectionMode=MultiSimple: |
5 | Botón | botón1a2 | transfiere los elementos seleccionados de la lista 1 a la lista 2 |
6 | Botón | botón2a1 | hace lo contrario |
7 | Botón | buttonEffacer1 | vacía la lista 1 |
8 | Botón | buttonEffacer2 | vacía la lista 2 |
Los componentes ListBox tienen un modo de selección de sus elementos que viene definido por su propiedad SelectionMode:
solo se puede seleccionar un elemento | |
es posible la selección múltiple: al mantener pulsada la tecla SHIFT y hacer clic en un elemento, la selección del elemento seleccionado anteriormente se amplía al elemento actual. | |
Es posible la selección múltiple: un elemento se selecciona o deselecciona haciendo clic con el ratón o pulsando la barra espaciadora. |
- El usuario escribe texto en el campo 1. Lo añade a la lista 1 con el botón Ajouter (2). A continuación, el campo de introducción (1) se vacía y el usuario puede añadir un nuevo elemento.
- Puede transferir elementos de una lista a otra seleccionando el elemento que desea transferir en una de las listas y eligiendo el botón de transferencia correspondiente, el 5 o el 6. El elemento transferido se añade al final de la lista de destino y se elimina de la lista de origen.
- Puede hacer doble clic en un elemento de la lista 1. Este elemento se transfiere entonces al cuadro de entrada para su modificación y se elimina de la lista 1.
Los botones están activados o desactivados según las siguientes reglas:
- el botón Ajouter solo está activado si hay texto no vacío en el campo de entrada
- el botón [5] de transferencia de la lista 1 a la lista 2 solo está activado si hay un elemento seleccionado en la lista 1
- el botón [6], para transferir la lista 2 a la lista 1, solo se ilumina si hay un elemento seleccionado en la lista 2
- Los botones [7] y [8], que sirven para borrar las listas 1 y 2, solo se iluminan si la lista que se va a borrar contiene elementos.
En las condiciones anteriores, todos los botones deben estar desactivados al iniciar la aplicación. Para ello, hay que establecer la propiedad Enabled de los botones en false. Esto se puede hacer en la fase de diseño, lo que generará el código correspondiente en el método InitializeComponent, o bien hacerlo nosotros mismos en el constructor, tal y como se muestra a continuación:
public Form1() {
InitializeComponent();
// --- inicializaciones adicionales ---
// se desactivan varios botones
buttonAjouter.Enabled = false;
button1vers2.Enabled = false;
button2vers1.Enabled = false;
buttonEffacer1.Enabled = false;
buttonEffacer2.Enabled = false;
}
El estado del botón Ajouter viene determinado por el contenido del campo de entrada. El evento TextChanged nos permite seguir los cambios en dicho contenido:
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// El contenido de textBoxSaisie ha cambiado
// el botón «Añadir» solo está activo si el campo no está vacío
buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
}
El estado de los botones de transferencia depende de si se ha seleccionado o no un elemento de la lista que controlan:
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
// Se ha seleccionado un elemento
// se activa el botón de transferencia de 1 a 2
button1vers2.Enabled = true;
}
private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
// Se ha seleccionado un elemento
// Se activa el botón de transferencia de 2 a 1
button2vers1.Enabled = true;
}
El código asociado al clic en el botón Ajouter es el siguiente:
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// se añade un nuevo elemento a la lista 1
listBox1.Items.Add(textBoxSaisie.Text.Trim());
// Se borra la entrada
textBoxSaisie.Text = "";
// La lista 1 no está vacía
buttonEffacer1.Enabled = true;
// el foco vuelve al campo de entrada
textBoxSaisie.Focus();
}
Cabe destacar el método Focus, que permite situar el «foco» en un control del formulario. El código asociado al clic en los botones Effacer:
private void buttonEffacer1_Click(object sender, System.EventArgs e) {
// Se borra la lista 1
listBox1.Items.Clear();
// Botón «Borrar»
buttonEffacer1.Enabled = false;
}
private void buttonEffacer2_Click(object sender, System.EventArgs e) {
// Se borra la lista 2
listBox2.Items.Clear();
// botón «Borrar»
buttonEffacer2.Enabled = false;
}
El código para transferir los elementos seleccionados de una lista a otra:
private void button1vers2_Click(object sender, System.EventArgs e) {
// traslado del elemento seleccionado de la Lista 1 a la Lista 2
transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
}
private void button2vers1_Click(object sender, System.EventArgs e) {
// traslado del elemento seleccionado de la Lista 2 a la Lista 1
transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
}
Los dos métodos anteriores delegan la transferencia de los elementos seleccionados de una lista a otra a un mismo método privado denominado «transferencia»:
// transferencia
private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
// transferencia a la lista l2 de los elementos seleccionados de la lista l1
for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
// índice del elemento seleccionado
int index = l1.SelectedIndices[i];
// Añadir a l2
l2.Items.Add(l1.Items[index]);
// eliminación de l1
l1.Items.RemoveAt(index);
}
// Botones «Borrar»
buttonEffacer2.Enabled = l2.Items.Count != 0;
buttonEffacer1.Enabled = l1.Items.Count != 0;
// Botones de transferencia
button1vers2.Enabled = false;
}
- línea b: el método «transfert» recibe seis parámetros:
- una referencia a la lista que contiene los elementos seleccionados, denominada aquí l1. Durante la ejecución de la aplicación, l1 es o bien listBox1 o bien listBox2. Se pueden ver ejemplos de llamada en las líneas 3 y 8 de los procedimientos de transferencia buttonXversY_Click.
- una referencia al botón de transferencia vinculado a la lista l1. Por ejemplo, si l1 es listBox2, será button2vers1 (véase la llamada en la línea 8)
- una referencia al botón de borrado de la lista l1. Por ejemplo, si l1 es listBox1, será buttonEffacer1 (véase la llamada en la línea 3)
- Las otras tres referencias son análogas, pero hacen referencia a la lista l2.
- línea d: la colección [ListBox].SelectedIndices representa los índices de los elementos seleccionados en el componente [ListBox]. Se trata de una colección:
- [ListBox].SelectedIndices.Count es el número de elementos de esta colección
- [ListBox].SelectedIndices[i] es el elemento n.º i de esta colección
Recorremos la colección en sentido inverso: empezamos por el final de la colección y terminamos por el principio. Explicaremos por qué.
- línea f: índice de un elemento seleccionado de la lista l1
- línea h: este elemento se añade a la lista l2
- línea j: y se elimina de la lista l1. Al eliminarse, deja de estar seleccionado. La colección l1.SelectedIndices de la línea d se va a recalcular. Perderá el elemento que acaba de eliminarse. A todos los elementos que se encuentren después de este se les reducirá su número de n a n-1.
- Si el bucle de la línea (d) es ascendente y acaba de procesar el elemento n.º 0, a continuación procesará el elemento n.º 1. Sin embargo, el elemento que tenía el n.º 1 antes de la eliminación del elemento n.º 0 pasará a tener el n.º 0. Por lo tanto, el bucle lo omitirá.
- Si el bucle de la línea (d) es descendente y acaba de procesar el elemento n.º n, a continuación procesará el elemento n.º n-1. Tras la eliminación del elemento n.º n, el elemento n.º n-1 no cambia de número. Por lo tanto, se procesa en la siguiente iteración del bucle.
- líneas m-n: el estado de los botones [Effacer] depende de si hay o no elementos en las listas asociadas
- línea p: la lista l2 ya no tiene elementos seleccionados: se desactiva su botón de transferencia.
7.2.5. Casillas de selección CheckBox, botones de opción ButtonRadio
Nos proponemos escribir la siguiente aplicación:
![]() |
Los componentes de la ventana son los siguientes:
n.º | tipo | nombre | función |
1 | GroupBox véase [6] | groupBox1 | Un contenedor de componentes. En él se pueden colocar otros componentes. Texto=Botones de opción |
2 | RadioButton | radioButton1 radioButton2 radioButton3 | 3 botones de opción: radioButton1 tiene la propiedad Checked=True y la propiedad Text=1 - radioButton2 tiene la propiedad Text=2 - radioButton3 tiene la propiedad Text=3 Los botones de opción que se encuentran en un mismo contenedor, en este caso el GroupBox, son mutuamente excluyentes: solo uno de ellos está activado. |
3 | GroupBox | groupBox2 | |
4 | CheckBox | checkBox1 checkBox2 checkBox3 | 3 casillas de selección. chechBox1 tiene la propiedad Checked=True y la propiedad Text=A - chechBox2 tiene la propiedad Text=B - chechBox3 tiene la propiedad Text=C |
5 | ListBox | listBoxValeurs | una lista que muestra los valores de los botones de opción y las casillas de selección en cuanto se produce un cambio. |
6 | muestra dónde se encuentra el contenedor GroupBox |
El evento que nos interesa para estos seis controles es el evento CheckChanged, que indica que el estado de la casilla de selección o del botón de opción ha cambiado. Este estado se representa en ambos casos mediante la propiedad booleana Checked, que, si es verdadera, significa que el control está marcado. Aquí utilizaremos un único método para gestionar los seis eventos CheckChanged: el método affiche. Para que los seis eventos CheckChanged sean gestionados por el mismo método affiche, podemos proceder de la siguiente manera:
Seleccionemos el componente radioButton1 y hagamos clic con el botón derecho del ratón sobre él para acceder a sus propiedades:
![]() |
En la pestaña événements [1], se asocia el método affiche [2] al evento CheckChanged. Esto significa que se desea que al hacer clic en la opción A1 se ejecute un método denominado affiche. Visual Studio genera automáticamente el método affiche en la ventana de código:
private void affiche(object sender, EventArgs e) {
}
El método affiche es un método de tipo EventHandler.
Para los otros cinco componentes, se procede de la misma manera. Seleccionemos, por ejemplo, la opción CheckBox1 y sus eventos [3]. Junto al evento Click, aparece una lista desplegable [4] en la que figuran los métodos existentes capaces de gestionar este evento. En este caso, solo aparece el método affiche.. Lo seleccionamos. Repetimos este proceso para todos los demás componentes.
En el método InitializeComponent se ha generado el código. El método affiche se ha declarado como gestor de los seis eventos CheckedChanged de la siguiente manera:
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);
El método affiche se completa de la siguiente manera:
private void affiche(object sender, System.EventArgs e) {
// muestra el estado del botón de opción o de la casilla de selección
// ¿Es una casilla de selección?
if (sender is CheckBox) {
CheckBox chk = (CheckBox)sender;
listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
}
// ¿Es un botón de opción?
if (sender is RadioButton) {
RadioButton rdb = (RadioButton)sender;
listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
}
}
La sintaxis
if (sender is CheckBox) {
permite comprobar que el objeto sender es de tipo CheckBox. Esto nos permite, a continuación, realizar una conversión de tipo al tipo exacto de sender. El método affiche escribe en la lista listBoxValeurs el nombre del componente que ha originado el evento y el valor de su propiedad Checked. Al ejecutar [7], se observa que al hacer clic en un botón de opción se producen dos eventos CheckChanged: uno en el antiguo botón marcado, que pasa a «desmarcado», y otro en el nuevo botón, que pasa a «marcado».
7.2.6. Variadores ScrollBar
Existen varios tipos de reguladores: el regulador horizontal (HscrollBar), el variador vertical (VscrollBar), el incrementador (NumericUpDown). |
Vamos a crear la siguiente aplicación:
![]() |
n.º | tipo | nombre | función |
1 | hScrollBar | hScrollBar1 | un variador horizontal |
2 | hScrollBar | hScrollBar2 | un variador horizontal que sigue las variaciones del variador 1 |
3 | Etiqueta | labelValeurHS1 | muestra el valor del variador horizontal |
4 | NumericUpDown | numericUpDown2 | permite fijar el valor del regulador 2 |
Un control deslizante ScrollBar permite al usuario seleccionar un valor dentro de un rango de valores enteros, representado por la «banda» del control deslizante sobre la que se desplaza un cursor. El valor del control deslizante está disponible en su propiedad Value.
- En un control deslizante horizontal, el extremo izquierdo representa el valor mínimo del rango, el extremo derecho el valor máximo y el cursor el valor actual seleccionado. En un control deslizante vertical, el mínimo está representado por el extremo superior y el máximo por el extremo inferior. Estos valores se representan mediante las propiedades Minimum y Maximum y tienen por defecto los valores 0 y 100.
- Al hacer clic en los extremos del regulador, el valor varía en un incremento (positivo o negativo) según el extremo en el que se haya hecho clic, denominado SmallChange, cuyo valor por defecto es 1.
- Al hacer clic a ambos lados del cursor, el valor varía en un incremento (positivo o negativo) según el extremo en el que se haya hecho clic, denominado LargeChange, cuyo valor por defecto es 10.
- Al hacer clic en el extremo superior de un control deslizante vertical, su valor disminuye. Esto puede sorprender al usuario medio, que normalmente espera que el valor «aumente». Este problema se soluciona asignando un valor negativo a las propiedades SmallChange y LargeChange
- Se puede leer y escribir en estas cinco propiedades (Value, Minimum, Maximum, SmallChange, LargeChange).
- El evento principal del variador es el que señala un cambio de valor: el evento Scroll.
Hay un componente NumericUpDown cerca del variador: este también tiene las propiedades Minimum, Maximum y Value, cuyos valores por defecto son 0, 100 y 0, respectivamente. Sin embargo, en este caso, la propiedad Value se muestra en un cuadro de entrada que forma parte integrante del control. El usuario puede modificar este valor por sí mismo, salvo que se haya establecido la propiedad ReadOnly del control en «verdadero». El valor del incremento viene determinado por la propiedad Increment, cuyo valor por defecto es 1. El evento principal del componente NumericUpDown es el que señala un cambio de valor: el evento ValueChanged
El código de la aplicación es el siguiente:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
// se configuran las características del variador 1
hScrollBar1.Value = 7;
hScrollBar1.Minimum = 1;
hScrollBar1.Maximum = 130;
hScrollBar1.LargeChange = 11;
hScrollBar1.SmallChange = 1;
// se asignan al variador 2 las mismas características que al variador 1
hScrollBar2.Value = hScrollBar1.Value;
hScrollBar2.Minimum = hScrollBar1.Minimum;
hScrollBar2.Maximum = hScrollBar1.Maximum;
hScrollBar2.LargeChange = hScrollBar1.LargeChange;
hScrollBar2.SmallChange = hScrollBar1.SmallChange;
// Lo mismo para el incrementador
numericUpDown2.Value = hScrollBar1.Value;
numericUpDown2.Minimum = hScrollBar1.Minimum;
numericUpDown2.Maximum = hScrollBar1.Maximum;
numericUpDown2.Increment = hScrollBar1.SmallChange;
// se asigna a la etiqueta el valor del variador 1
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
// cambio de valor del variador 1
// se transfiere su valor al variador 2 y a la etiqueta
hScrollBar2.Value = hScrollBar1.Value;
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
// el incrementador ha cambiado de valor
// se fija el valor del variador 2
hScrollBar2.Value = (int)numericUpDown2.Value;
}
}
}
7.3. Eventos del ratón
Al dibujar en un contenedor, es importante conocer la posición del ratón para, por ejemplo, mostrar un punto al hacer clic. Los movimientos del ratón provocan eventos en el contenedor por el que se desplaza.
![]() |
- [1]: los eventos que se producen al mover el ratón sobre el formulario o sobre un control
- [2]: eventos que se producen al arrastrar y soltar (Drag'nDrop)
el ratón acaba de entrar en el área del control | |
el ratón acaba de salir del área del control | |
El ratón se mueve dentro del área de control | |
Se ha pulsado el botón izquierdo del ratón | |
Suelta el botón izquierdo del ratón | |
El usuario suelta un objeto sobre el control | |
El usuario entra en el área del control arrastrando un objeto | |
El usuario sale del área del control arrastrando un objeto | |
El usuario pasa por encima del área del control al arrastrar un objeto |
Aquí tienes una aplicación que te ayudará a comprender mejor en qué momentos se producen los distintos eventos del ratón:
![]() |
n.º | tipo | nombre | función |
1 | Etiqueta | lblPositionSouris | para mostrar la posición del ratón en el formulario 1, la lista 2 o el botón 3 |
2 | ListBox | listBoxEvts | para mostrar los eventos del ratón distintos de MouseMove |
3 | Botón | buttonEffacer | para borrar el contenido de 2 |
Para seguir los movimientos del ratón en los tres controles, solo hay que escribir un único controlador, el controlador affiche:
![]() |
El código del procedimiento affiche es el siguiente:
private void affiche(object sender, MouseEventArgs e) {
// movimiento del ratón: se muestran las coordenadas (X, Y) del mismo
labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}
Cada vez que el ratón entra en el ámbito de un control, su sistema de coordenadas cambia. Su origen (0,0) es la esquina superior izquierda del control en el que se encuentra. Así, durante la ejecución, al pasar el ratón del formulario al botón, se aprecia claramente el cambio de coordenadas. Para ver mejor estos cambios en el ámbito del ratón, se puede utilizar la propiedad Cursor [1] de los controles:
![]() |
Esta propiedad permite establecer la forma del cursor del ratón cuando este entra en el ámbito del control. Así, en nuestro ejemplo, hemos establecido el cursor en «Default» para el propio formulario [2], en «Hand» para la lista 2 [3] y en «Cross» para el botón 3 [4].
Por otra parte, para detectar las entradas y salidas del ratón en la lista 2, procesamos los eventos MouseEnter y MouseLeave de esta misma lista:
private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
// se notifica el evento
listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
}
private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
// se notifica el evento
listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}
Para gestionar los clics en el formulario, procesamos los eventos MouseDown y MouseUp:
private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
// se notifica el evento
listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
}
private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
// se notifica el evento
listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
- líneas 3 y 8: los mensajes se colocan en primera posición en el ListBox para que los eventos más recientes sean los primeros de la lista.
![]() |
Por último, el código del gestor de clics del botón Effacer:
private void buttonEffacer_Click(object sender, EventArgs e) {
listBoxEvts.Items.Clear();
}
7.4. Crear una ventana con menú
Veamos ahora cómo crear una ventana con menú. Vamos a crear la siguiente ventana:
![]() |
Para crear un menú, seleccionamos el componente «MenuStrip» en la barra «Menús y barras de herramientas»:
![]() |
- [1]: selección del componente [MenuStrip]
- [2]: de este modo, se crea un menú que se integra en el formulario con casillas vacías tituladas «Type Here». Solo hay que introducir en ellas las diferentes opciones del menú.
- [3]: se ha introducido el texto «Opciones A». Pasamos al texto [4].
- [5]: se han introducido los textos de las opciones A. Pasamos al texto [6]
![]() |
- [6]: las primeras opciones B
- [7]: debajo de B1, se inserta un separador. Este está disponible en un menú desplegable asociado al texto «Type Here»
- [8]: para crear un submenú, utiliza la flecha [8] y escribe el submenú en [9]
Ahora solo queda nombrar los distintos componentes del formulario:
![]() |
n.º | tipo | nombre(s) | función |
1 | Etiqueta | labelStatut | para mostrar el texto de la opción del menú seleccionada |
2 | toolStripMenuItem | toolStripMenuItemOptionsA toolStripMenuItemA1 toolStripMenuItemA2 toolStripMenuItemA3 | opciones de menú bajo la opción principal «Opciones A» |
3 | toolStripMenuItem | toolStripMenuItemOptionsB toolStripMenuItemB1 toolStripMenuItemB2 toolStripMenuItemB3 | Opciones del menú bajo la opción principal «Opciones B» |
4 | toolStripMenuItem | toolStripMenuItemB31 toolStripMenuItemB32 | Opciones de menú bajo la opción principal «B3» |
Las opciones de menú son controles como el resto de componentes visuales y tienen propiedades y eventos. Por ejemplo, las propiedades de la opción de menú A1 son las siguientes:
![]() |
En nuestro ejemplo se utilizan dos propiedades:
el nombre del control de menú | |
El texto de la opción del menú |
En la estructura del menú, seleccionemos la opción A1 y hagamos clic con el botón derecho para acceder a las propiedades del control:
![]() |
En la pestaña événements [1], se asocia el método affiche [2] al evento Click. Esto significa que se desea que al hacer clic en la opción A1 se ejecute un método denominado affiche. Visual Studio genera automáticamente el método affiche en la ventana de código:
private void affiche(object sender, EventArgs e) {
}
En este método, nos limitaremos a mostrar en la etiqueta labelStatut la propiedad Text de la opción de menú en la que se ha hecho clic:
private void affiche(object sender, EventArgs e) {
// muestra en el TextBox el nombre del submenú seleccionado
labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}
El origen del evento sender es de tipo object. Las opciones del menú son de tipo ToolStripMenuItem, por lo que es necesario realizar una conversión de tipo de object a ToolStripMenuItem.
Para todas las opciones de menú, se establece el gestor de clics en el método affiche [3,4].
Ejecutemos la aplicación y seleccionemos un elemento del menú:
![]() | ![]() |
7.5. Componentes no visuales
Ahora nos centraremos en una serie de componentes no visuales: se utilizan durante el diseño, pero no se ven durante la ejecución.
7.5.1. Cuadros de diálogo , OpenFileDialog y SaveFileDialog
Vamos a crear la siguiente aplicación:
![]() |
Los controles son los siguientes:
N.º | tipo | nombre | función |
1 | TextBox | TextBoxLignes | texto introducido por el usuario o cargado desde un archivo MultiLine=True, ScrollBars=Both, AccepReturn=True, AcceptTab=True |
2 | Botón | buttonSauvegarder | permite guardar el texto de [1] en un archivo de texto |
3 | Botón | buttonCharger | permite cargar el contenido de un archivo de texto en [1] |
4 | Botón | buttonEffacer | borra el contenido de [1] |
5 | SaveFileDialog | saveFileDialog1 | componente que permite elegir el nombre y la ubicación del archivo de copia de seguridad de [1]. Este componente se selecciona en la barra de herramientas [7] y simplemente se coloca en el formulario. A continuación, se guarda, pero no ocupa espacio en el formulario. Se trata de un componente no visual. |
6 | OpenFileDialog | openFileDialog1 | Componente que permite seleccionar el archivo que se va a cargar en [1]. |
El código asociado al botón Effacer es sencillo:
private void buttonEffacer_Click(object sender, EventArgs e) {
// se introduce la cadena vacía en el TexBox
textBoxLignes.Text = "";
}
Utilizaremos las siguientes propiedades y métodos de la clase SaveFileDialog:
Campo | Tipo | Función |
Propriété | los tipos de archivo que aparecen en la lista desplegable de tipos de archivo del cuadro de diálogo | |
Propriété | El número del tipo de archivo que se propone por defecto en la lista anterior. Empieza por 0. | |
Propriété | la carpeta indicada inicialmente para guardar el archivo | |
Propriété | el nombre del archivo de copia de seguridad indicado por el usuario | |
Méthode | Método que muestra el cuadro de diálogo de copia de seguridad. Devuelve un resultado de tipo DialogResult. |
El método ShowDialog muestra un cuadro de diálogo similar al siguiente:
![]() |
Lista desplegable generada a partir de la propiedad Filter. El tipo de archivo propuesto por defecto viene determinado por FilterIndex | |
carpeta actual, determinada por InitialDirectory si se ha rellenado esta propiedad | |
el nombre del archivo elegido o introducido directamente por el usuario. Estará disponible en la propiedad FileName | |
Botones «Guardar»/«Cancelar». Si se utiliza el botón Enregistrer, la función ShowDialog devuelve el resultado DialogResult.OK |
El procedimiento de guardado se puede escribir así:
private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
// se guarda el campo de entrada en un archivo de texto
// se configura el cuadro de diálogo savefileDialog1
saveFileDialog1.InitialDirectory = Application.ExecutablePath;
saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
saveFileDialog1.FilterIndex = 0;
// se muestra el cuadro de diálogo y se recupera su resultado
if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
// se recupera el nombre del archivo
string nomFichier = saveFileDialog1.FileName;
StreamWriter fichier = null;
try {
// se abre el archivo en modo de escritura
fichier = new StreamWriter(nomFichier);
// se escribe el texto en él
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 {
// se cierra el archivo
if (fichier != null) {
fichier.Dispose();
}
}
}
}
- línea 4: se establece la carpeta inicial (InitialDirectory) en la carpeta (Application.ExecutablePath) que contiene el ejecutable de la aplicación.
- línea 5: se especifican los tipos de archivos que se van a mostrar. Cabe destacar la sintaxis de los filtros: filtre1|filtre2|..|filtren con filtro i = Texto|plantilla de archivo. En este caso, el usuario podrá elegir entre los archivos *.txt y *.*.
- línea 6: se establece el tipo de archivo que se mostrará en primer lugar al usuario. Aquí, el índice 0 hace referencia a los archivos *.txt.
- línea 8: se muestra el cuadro de diálogo y se recoge su resultado. Mientras se muestra el cuadro de diálogo, el usuario ya no tiene acceso al formulario principal (cuadro de diálogo denominado «modal»). El usuario establece el nombre del archivo que desea guardar y sale del cuadro de diálogo, ya sea mediante el botón Enregistrer, mediante el botón Annuler, o cerrando el cuadro de diálogo. El resultado del método ShowDialog es DialogResult.OK únicamente si el usuario ha utilizado el botón Enregistrer para salir del cuadro de diálogo.
- Una vez hecho esto, el nombre del archivo que se va a crear se encuentra ahora en la propiedad FileName del objeto saveFileDialog1. A continuación, se vuelve al proceso clásico de creación de un archivo de texto. En él se escribe el contenido de TextBox: textBoxLignes.Text, gestionando al mismo tiempo las excepciones que puedan producirse.
La clase OpenFileDialog es muy similar a la clase SaveFileDialog. Se utilizarán los mismos métodos y propiedades que anteriormente. El método ShowDialog muestra un cuadro de diálogo similar al siguiente:
![]() |
lista desplegable generada a partir de la propiedad Filter. El tipo de archivo propuesto por defecto viene determinado por FilterIndex | |
carpeta actual, determinada por InitialDirectory si se ha rellenado esta propiedad | |
el nombre del archivo elegido o introducido directamente por el usuario. Estará disponible en la propiedad FileName | |
Botones «Abrir»/«Cancelar». Si se utiliza el botón Ouvrir, la función ShowDialog devuelve el resultado DialogResult.OK |
El procedimiento para cargar el archivo de texto se puede escribir así:
private void buttonCharger_Click(object sender, EventArgs e) {
// se carga un archivo de texto en el cuadro de entrada
// se configura el cuadro de diálogo openfileDialog1
openFileDialog1.InitialDirectory = Application.ExecutablePath;
openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
openFileDialog1.FilterIndex = 0;
// se muestra el cuadro de diálogo y se recupera su resultado
if (openFileDialog1.ShowDialog() == DialogResult.OK) {
// se recupera el nombre del archivo
string nomFichier = openFileDialog1.FileName;
StreamReader fichier = null;
try {
// se abre el archivo en modo de lectura
fichier = new StreamReader(nomFichier);
// se lee todo el archivo y se guarda en el 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 {
// se cierra el archivo
if (fichier != null) {
fichier.Dispose();
}
}//por fin
}//si
}
- línea 4: se establece la carpeta inicial (InitialDirectory) en la carpeta (Application.ExecutablePath) que contiene el ejecutable de la aplicación.
- línea 5: se especifican los tipos de archivos que se van a mostrar. Cabe destacar la sintaxis de los filtros: filtre1|filtre2|..|filtren con filtro i = Texto|plantilla de archivo. En este caso, el usuario podrá elegir entre los archivos *.txt y *.*.
- línea 6: se establece el tipo de archivo que se mostrará en primer lugar al usuario. Aquí, el índice 0 hace referencia a los archivos *.txt.
- línea 8: se muestra el cuadro de diálogo y se recoge su resultado. Mientras se muestra el cuadro de diálogo, el usuario ya no tiene acceso al formulario principal (cuadro de diálogo denominado «modal»). El usuario establece el nombre del archivo que desea guardar y sale del cuadro de diálogo, ya sea mediante el botón Ouvrir, mediante el botón Annuler, o cerrando el cuadro de diálogo. El resultado del método ShowDialog es DialogResult.OK únicamente si el usuario ha utilizado el botón Enregistrer para salir del cuadro de diálogo.
- Una vez hecho esto, el nombre del archivo que se va a crear se encuentra ahora en la propiedad FileName del objeto openFileDialog1. A continuación, se vuelve a la lectura clásica de un archivo de texto. Cabe destacar, en la línea 16, el método que permite leer la totalidad de un archivo.
7.5.2. Cuadros de diálogo FontColor y ColorDialog
Continuamos con el ejemplo anterior añadiendo dos nuevos botones y dos nuevos controles no visuales:
![]() |
6
7
N.º | tipo | nombre | función |
1 | Botón | buttonCouleur | para establecer el color de los caracteres de TextBox |
2 | Botón | buttonPolice | para establecer la fuente de TextBox |
3 | ColorDialog | colorDialog1 | el componente que permite seleccionar un color, incluido en la caja de herramientas [5]. |
4 | FontDialog | colorDialog1 | El componente que permite seleccionar una fuente, incluido en el kit de herramientas [5]. |
Las clases FontDialog y ColorDialog tienen un método ShowDialog análogo al método ShowDialog de las clases OpenFileDialog y SaveFileDialog.
![]() |
El método ShowDialog de la clase ColorDialog permite seleccionar un color [1]. El de la clase FontDialog permite seleccionar una fuente [2]:
- [1]: si el usuario sale del cuadro de diálogo con el botón OK, el resultado del método ShowDialog es DialogResult.OK y el color seleccionado se encuentra en la propiedad Color del objeto ColorDialog utilizado.
- [2]: si el usuario sale del cuadro de diálogo pulsando el botón OK, el resultado del método ShowDialog es DialogResult.OK y la fuente seleccionada se encuentra en la propiedad Font del objeto FontDialog utilizado.
Ahora disponemos de los elementos necesarios para gestionar los clics en los botones Couleur y Police:
private void buttonCouleur_Click(object sender, EventArgs e) {// selección de un color de texto
if (colorDialog1.ShowDialog() == DialogResult.OK) {
// se cambia la propiedad Forecolor de TextBox
textBoxLignes.ForeColor = colorDialog1.Color;
}//si
}
private void buttonPolice_Click(object sender, EventArgs e) {
// selección de una fuente
if (fontDialog1.ShowDialog() == DialogResult.OK) {
// se cambia la propiedad Font de TextBox
textBoxLignes.Font = fontDialog1.Font;
}
- línea [4]: la propiedad [ForeColor] de un componente TextBox designa el color de tipo [Color] de los caracteres del TextBox. En este caso, este color es el elegido por el usuario en el cuadro de diálogo de tipo [ColorDialog].
- línea [12]: la propiedad [Font] de un componente TextBox designa la fuente de tipo [Font] de los caracteres del TextBox. En este caso, esta fuente es la elegida por el usuario en el cuadro de diálogo de tipo [FontDialog].
7.5.3. Temporizador
Nos proponemos aquí escribir la siguiente aplicación:
![]() |
n.º | Tipo | Nombre | Función |
1 | Etiqueta | labelChrono | muestra un cronómetro |
2 | Botón | buttonArretMarche | botón de inicio/parada del cronómetro |
3 | Temporizador | timer1 | componente que emite aquí un evento cada segundo |
En [4] vemos el cronómetro en marcha, y en [5], el cronómetro parado.
Para cambiar cada segundo el contenido de la etiqueta LabelChrono, necesitamos un componente que genere un evento cada segundo, evento que podremos interceptar para actualizar la visualización del cronómetro. Este componente es el Timer [1], disponible en el kit de herramientas Components [2]:
![]() |
Las propiedades del componente «Timer» que se utilizan aquí serán las siguientes:
número de milisegundos tras los cuales se emite un evento Tick. | |
el evento que se produce al cabo de Interval milisegundos | |
activa (true) o desactiva (false) el temporizador |
En nuestro ejemplo, el temporizador se llama timer1 y timer1.Interval se ha establecido en 1000 ms (1 s). Por lo tanto, el evento Tick se producirá cada segundo. Al hacer clic en el botón «Parar/Iniciar», se ejecuta el siguiente procedimiento buttonArretMarche_Click:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
// variable de instancia
private DateTime début = DateTime.Now;
...
private void buttonArretMarche_Click(object sender, EventArgs e) {
// ¿parado o en marcha?
if (buttonArretMarche.Text == "Marche") {
// se anota la hora de inicio
début = DateTime.Now;
// se muestra
labelChrono.Text = "00:00:00";
// se inicia el temporizador
timer1.Enabled = true;
// se cambia el texto del botón
buttonArretMarche.Text = "Arrêt";
// fin
return;
}//
if (buttonArretMarche.Text == "Arrêt") {
// se detiene el temporizador
timer1.Enabled = false;
// se cambia el texto del botón
buttonArretMarche.Text = "Marche";
// fin
return;
}
}
}
}
- línea 13: el procedimiento que gestiona el clic en el botón «Parar/Marcha».
- línea 15: el texto del botón «Parar/Marcha» es «Parar» o «Marcha». Por lo tanto, es necesario realizar una comprobación de este texto para saber qué acción realizar.
- línea 17: en el caso de «Marcha», se anota la hora de inicio en una variable début, que es una variable global (línea 11) del objeto formulario
- línea 19: se inicializa el contenido de la etiqueta LabelChrono
- línea 21: se inicia el temporizador (Enabled=true)
- línea 23: el texto del botón cambia a «Parar».
- línea 27: en el caso de «Detener»
- línea 29: se detiene el temporizador (Enabled=false)
- línea 31: se cambia el texto del botón a «Marcha».
Ahora nos queda gestionar el evento Tick en el objeto timer1, evento que se produce cada segundo:
private void timer1_Tick(object sender, EventArgs e) {
// Ha transcurrido un segundo
DateTime maintenant = DateTime.Now;
TimeSpan durée = maintenant - début;
// se actualiza el cronómetro
labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
}
- línea 3: anotamos la hora actual
- línea 4: se calcula el tiempo transcurrido desde el momento en que se puso en marcha el cronómetro. Se obtiene un objeto de tipo TimeSpan que representa una duración en el tiempo.
- línea 6: esta debe mostrarse en el cronómetro con el formato hh:mm:ss. Para ello, utilizamos las propiedades Hours, Minutes, Seconds del objeto TimeSPan, que representan, respectivamente, las horas, los minutos y los segundos de la duración que mostramos en el formato ToString("d2") para obtener una visualización de dos dígitos.
7.6. Aplicación de ejemplo: , versión 6
Retomamos la aplicación de ejemplo IMPOTS. La última versión se analizó en el apartado 6.4. Se trataba de la siguiente aplicación de tres capas:
![]() |
- las capas [metier] y [dao] estaban encapsuladas en DLL
- la capa [ui] era una capa [console]
- La instanciación de las capas y su integración en la aplicación corría a cargo de Spring.
En esta nueva versión, la capa [ui] estará a cargo de la siguiente interfaz gráfica:
![]() |
7.6.1. La solución de Visual Studio
La solución de Visual Studio se compone de los siguientes elementos:
![]() |
- [1]: el proyecto consta de los siguientes elementos:
- [Program.cs]: la clase que inicia la aplicación
- [Form1.cs]: la clase de un primer formulario
- [Form2]: la clase de un segundo formulario
- [lib], detallado en [2]: aquí se han incluido todas las DLL necesarias para el proyecto:
- [ImpotsV5-dao.dll]: el DLL de la capa [dao] generada en el apartado 6.4.3;
- [ImpotsV5-metier.dll]: el DLL de la capa [dao] generada en el apartado 6.4.4;
- [Spring.Core.dll], [Common.Logging.dll], [antlr.runtime.dll]: los DLL de Spring ya utilizados en la versión anterior (véase el apartado 6.4.6).
- [references], detallado en [3]: las referencias del proyecto. Se ha añadido una referencia para cada uno de los archivos DLL de la carpeta [lib]
- [App.config]: el archivo de configuración del proyecto. Es idéntico al de la versión anterior descrito en el apartado 6.4.6;
- [DataImpot.txt]: el archivo de tramos impositivos configurado para copiarse automáticamente en la carpeta de ejecución del proyecto [4]
El formulario [Form1] es el formulario de introducción de los parámetros para el cálculo del impuesto [A], ya presentado anteriormente. El formulario [Form2] [B] sirve para mostrar un mensaje de error:
![]() |
7.6.2. La clase [Program.cs]
La clase [Program.cs] inicia la aplicación. Su código es el siguiente:
using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;
namespace Chap5 {
static class Program {
/// <summary>
/// El punto de entrada principal de la aplicación.
/// </summary>
[STAThread]
static void Main() {
// código generado por Vs
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// --------------- Código del desarrollador
// instancias de las capas [metier] y [dao]
IApplicationContext ctx = null;
Exception ex = null;
IImpotMetier metier = null;
try {
// contexto de Spring
ctx = ContextRegistry.GetContext();
// se solicita una referencia en la capa [metier]
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
// almacenamiento de la excepción
ex = e1;
}
// formulario que se va a mostrar
Form form = null;
// ¿Se ha producido alguna excepción?
if (ex != null) {
// sí: se crea el mensaje de error que se va a mostrar
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;
}
// creación de la ventana de error a la que se pasa el mensaje de error que se va a mostrar
Form2 form2 = new Form2();
form2.MsgErreur = msgErreur.ToString();
// esta será la ventana que se mostrará
form = form2;
} else {
// todo ha salido bien
// creación de la interfaz gráfica [Form1] a la que se pasa la referencia en la capa [metier]
Form1 form1 = new Form1();
form1.Metier = metier;
// esta será la ventana que se mostrará
form = form1;
}
// Visualización de la ventana
Application.Run(form);
}
}
}
El código generado por Visual Studio se ha completado a partir de la línea 19. La aplicación utiliza el siguiente archivo [App.config] :
<?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>
- líneas 24-32: uso del archivo [App.config] anterior para instanciar las capas [metier] y [dao]
- línea 26: procesamiento del archivo [App.config]
- línea 28: recuperación de una referencia en la capa [metier]
- línea 31: registro de la posible excepción
- línea 34: la referencia form indicará el formulario que se debe mostrar (form1 o form2)
- líneas 36-50: si se ha producido una excepción, se prepara la visualización de un formulario de tipo [Form2]
- líneas 38-44: se genera el mensaje de error que se va a mostrar. Se compone de la concatenación de los mensajes de error de las distintas excepciones presentes en la cadena de excepciones.
- línea 46: se crea un formulario de tipo [Form2].
- línea 47: como veremos más adelante, este formulario tiene una propiedad pública MsgErreur que es el mensaje de error que se va a mostrar:
public string MsgErreur { private get; set; }
Se rellena esta propiedad.
- Línea 49: se inicializa la referencia form, que designa la ventana que se va a mostrar. Cabe destacar el polimorfismo que se está aplicando. form2 no es de tipo [Form], sino de tipo [Form2], un tipo derivado de [Form].
- Líneas 50-57: no se ha producido ninguna excepción. Nos preparamos para mostrar un formulario de tipo [Form1].
- línea 53: se crea un formulario de tipo [Form1].
- línea 54: como veremos más adelante, este formulario tiene una propiedad pública Metier que es una referencia a la capa [metier]:
public IImpotMetier Metier { private get; set; }
Se rellena esta propiedad.
- Línea 56: se inicializa la referencia form, que designa la ventana que se va a mostrar. Cabe destacar de nuevo el polimorfismo en acción. form1 no es de tipo [Form], sino de tipo [Form1], un tipo derivado de [Form].
- Línea 59: se muestra la ventana a la que hace referencia form.
7.6.3. El formulario [Form1]
En el modo [conception], el formulario [Form1] es el siguiente:
![]() |
Los controles son los siguientes
n.º | tipo | nombre | función |
0 | GroupBox | groupBox1 | Text=¿Está casado/a? |
1 | RadioButton | radioButtonOui | marcar si está casado |
2 | RadioButton | radioButtonNon | marcar si no está casado Checked=True |
3 | NumericUpDown | numericUpDownEnfants | Número de hijos del contribuyente Mínimo=0, Máximo=20, Incremento=1 |
4 | TextBox | textSalaire | salario anual del contribuyente en euros |
5 | Etiqueta | labelImpot | Importe del impuesto a pagar BorderStyle=Fixed3D |
6 | Botón | buttonCalculer | inicia el cálculo del impuesto |
7 | Botón | buttonEffacer | restablece el formulario al estado en el que se encontraba al cargarlo |
8 | Botón | buttonQuitter | para salir de la aplicación |
Reglas de funcionamiento del formulario
- el botón Calculer permanece desactivado mientras no haya nada en el campo del salario
- si, al iniciar el cálculo, resulta que el salario es incorrecto, se señala el error [9]
El código de la clase es el siguiente:
using System.Windows.Forms;
using Metier;
using System;
namespace Chap5 {
public partial class Form1 : Form {
// capa [métier]
public IImpotMetier Metier { private get; set; }
public Form1() {
InitializeComponent();
}
private void buttonCalculer_Click(object sender, System.EventArgs e) {
// ¿Es correcto el salario?
int salaire;
bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
if (! ok || salaire < 0) {
// mensaje de error
MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
// Volver al campo con error
textSalaire.Focus();
// Selección del texto del campo de introducción
textSalaire.SelectAll();
// Volver a la interfaz de introducción de datos
return;
}
// el salario es correcto; se puede calcular el impuesto
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) {
// borrar formulario
labelImpot.Text = "";
numericUpDownEnfants.Value = 0;
textSalaire.Text = "";
radioButtonNon.Checked = true;
}
private void textSalaire_TextChanged(object sender, EventArgs e) {
// estado del botón [Calculer]
buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
}
}
}
Solo comentamos las partes importantes:
- línea [8]: la propiedad pública Metier, que permite a la clase de ejecución [Program.cs] insertar en [Form1] una referencia a la capa [metier].
- línea [14]: el procedimiento de cálculo del impuesto
- líneas 15-27: comprobación de la validez del salario (un número entero >=0).
- línea 29: cálculo del impuesto mediante el método [CalculerImpot] de la capa [metier]. Cabe destacar la simplicidad de esta operación, conseguida gracias a la encapsulación de la capa [metier] en una DLL.
7.6.4. El formulario [Form2]
En el modo [conception], el formulario [Form2] es el siguiente:
![]() |
Los controles son los siguientes
n.º | tipo | nombre | función |
1 | TextBox | textBoxErreur | Multiline=True, Scrollbars=Both |
El código de la clase es el siguiente:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form2 : Form {
// mensaje de error
public string MsgErreur { private get; set; }
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, System.EventArgs e) {
// se muestra el mensaje de error
textBoxErreur.Text = MsgErreur;
// se deselecciona todo el texto
textBoxErreur.Select(0, 0);
}
}
}
- línea 6: la propiedad pública MsgErreur, que permite a la clase de lanzamiento [Program.cs] insertar en [Form2] el mensaje de error que se va a mostrar. Este mensaje se muestra durante el procesamiento del evento Load, líneas 12-16.
- línea 14: el mensaje de error se introduce en TextBox
- línea 16: se elimina la selección realizada en la operación anterior. [TextBox].Select(inicio, longitud) selecciona (resalta) longueur caracteres a partir del carácter n.º début. [TextBox].Select(0,0) equivale a deseleccionar todo el texto.
7.6.5. Conclusión
Volvamos a la arquitectura de tres capas utilizada:
![]() |
Esta arquitectura nos ha permitido sustituir la implementación de consola de la capa [ui] existente por una implementación gráfica, sin modificar en absoluto las capas [metier] y [dao]. Hemos podido centrarnos en la capa [ui] sin preocuparnos por los posibles impactos en las demás capas. Ahí radica el principal interés de las arquitecturas de tres capas. Veremos otro ejemplo más adelante, cuando la capa [dao], que actualmente procesa los datos de un archivo de texto, sea sustituida por una capa [dao] que procese los datos de una base de datos. Veremos que esto se llevará a cabo sin que afecte a las capas [ui] y [metier].


































































