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 controlará 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 con (Ctrl-F5). Se muestra la ventana [Form1]. Se puede mover, cambiar su tamaño y cerrarla. Así, contamos con 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 detener aplicaciones gráficas de Windows.
- línea 11: opcional; permite asignar diferentes estilos visuales a los controles colocados en un formulario
- línea 12: opcional; establece el motor de renderización 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 solicita 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 controladores de eventos
- [Form1.Designer.cs]: en el que se encuentran los componentes del formulario y sus propiedades. Este archivo tiene la particularidad de regenerarse 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ñador requerida.
/// </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 administrados; de lo 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 requerido para la compatibilidad con el Diseñador; no lo modifique
/// 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 siendo 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 es 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 modifica el formulario en modo [conception]. Se crea una sección, llamada région, para delimitarla (líneas 19-39). El desarrollador no debe agregar 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 decisiones que toma el desarrollador en el modo [conception]. Veamos un primer ejemplo:
![]() |
- [1]: selecciona el modo [conception] haciendo doble clic en el archivo [Form1.cs]
- [2]: haz clic con el botón derecho del ratón en el formulario y elige [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 refleja 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 llamado 02. Para ello, seguimos el procedimiento explicado anteriormente para crear un proyecto. La ventana que se 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]: haz clic con el botón derecho del ratón en el formulario, fuera de cualquier componente, y selecciona 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 en 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 en la ventana | |
para establecer el nombre de la ventana |
Aquí configuramos las propiedades Text y Name:
Campos de entrada y botones - 1 | |
frmSaisiesBoutons |
![]() |
- [1]: seleccione el kit de herramientas [Common Controls] de entre los kits de herramientas que ofrece Visual Studio
- [2, 3, 4]: haz 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:
- seleccione los distintos componentes que desea formatear juntos (mantenga presionada la tecla Ctrl mientras hace 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 en el centro
- 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 pongas nada
- [3]: nombre: buttonAfficher, texto: Afficher
- [4]: la ventana en sí: 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 acción.
Lo que se hizo en el modo [conception] se tradujo 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]. Esto también se aplica al formulario de la línea 2, que es la clase misma.
- líneas 7-9: se crean los tres objetos de tipo [Label], [TextBox] y [Button]. A través de ellos se administran 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 agregar componentes al formulario.
Este código es fácil de entender. De esta manera, 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 base de datos, cuya estructura solo se descubre en el momento de la ejecución.
Ahora nos queda escribir el procedimiento para manejar 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 en orden alfabético
- [2]: eventos relacionados con el control
Se puede acceder a las propiedades y eventos de un control por categorías o en orden alfabético:
- [3]: Propiedades o eventos por categoría
- [4]: Propiedades o eventos en 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 enumera los eventos posibles 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 ocurre 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 para el botón denominado buttonAfficher. Cabe destacar los siguientes puntos:
- el método se nombra según el esquema nomDuComposant_NomEvénement
- el método es privado. Recibe dos parámetros:
- sender: es el objeto que provocó 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 de su elección.
- 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, allí se encontrarán las coordenadas (X, Y) del ratón.
- Aquí no utilizaremos ninguno de estos parámetros.
Escribir un manejador de eventos consiste en completar el esqueleto de código anterior. Aquí queremos mostrar un cuadro de diálogo que contenga, si el campo textBoxSaisie no está vacío, su contenido; de lo contrario, un mensaje de error:
![]() |
El código para lograrlo podría ser el siguiente:
private void buttonAfficher_Click(object sender, EventArgs e) {
// se muestra el texto que se ingresó 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 mostrará | |
el título de la ventana | |
los botones que aparecen en la ventana | |
el ícono que aparece en la ventana |
El parámetro buttons puede tomar valores de entre las siguientes constantes (precedidas por MessageBoxButtons, como se muestra en la línea 7) arriba:
botones | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
El parámetro icon puede tomar sus valores de entre las siguientes constantes (precedidas por MessageBoxIcon, como se muestra en la línea 10) arriba:
![]() | ídem Stop | ||
ídem Advertencia | ![]() | ||
Lo mismo que Asterisk | ![]() | ||
![]() | ídem Mano | ||
![]() |
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 presionó el usuario para cerrar la ventana de tipo MessageBox, escribiremos:
7.1.2.2. El código relacionado con el manejo 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í, 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 llamado delegado. Volveremos sobre esto 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 se refiere a un modelo de método:
- cuyo primer parámetro es un tipo Object
- cuyo segundo parámetro es de tipo EventArgs
- que no devuelve ningún resultado
Este es el caso del método que gestiona el clic en el botón buttonAfficher, generado por Visual Studio:
private void buttonAfficher_Click(object sender, EventArgs e);
Así, el método buttonAfficher_Click corresponde al 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 corresponde al prototipo definido por el tipo EventHandler, se puede escribir:
Una variable de tipo delegate es, de hecho, una lista de referencias a métodos del tipo delegate. Para agregar 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 agrega un controlador de eventos al evento Click del objeto buttonAfficher:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Esta instrucción agrega 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. Por lo general, solo hay uno. Se le conoce como el «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 ocurrió. Para un clic en un botón, esto es suficiente. Para el movimiento 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 ocurre el evento.
7.1.2.3. Conclusion
A partir de los dos proyectos analizados, podemos concluir que, una vez construida 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. Visual Studio genera código automáticamente. Este código, que puede ser 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
Ahora presentamos diversas aplicaciones que utilizan los componentes más comunes con el fin de descubrir sus principales métodos y propiedades. Para cada aplicación, presentamos 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 detendremos en algunos eventos importantes de un formulario.
El formulario se está cargando | |
El formulario se está cerrando | |
El formulario está cerrado |
El evento Load ocurre incluso antes de que se muestre el formulario. El evento Closing ocurre cuando el formulario se está cerrando. Este cierre aún se puede detener mediante programación.
Creamos un formulario con el nombre Form1 sin componentes:
![]() |
- [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: Entonces le preguntamos si realmente quiere salir la aplicación: | ![]() |
línea 20: Si responde «No», establecemos la propiedad Cancel del evento CancelEventArgs y que el método recibió 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 ya sea el contenido del campo de entrada o 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 dar seguimiento a 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 activa cuando se muestra el formulario
- línea 26: a continuación, se coloca el foco (para la entrada 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 coloca 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 | Agrega el contenido de 2 a 1 |
Para que un TextBox se convierta en multilínea, se configuran las siguientes propiedades del control:
para aceptar varias líneas de texto | |
para indicar si el control debe tener barras de desplazamiento (Horizontal, Vertical, Both) o no (None) | |
si es igual a true, la tecla Enter pasará a la línea siguiente | |
si es igual a «true», la tecla Tab generará un salto de tabulación en el texto |
La aplicación permite escribir líneas directamente en [1] o agregarlas 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 agrega el contenido de textBoxLigne al de textBoxLignes
textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
textBoxLigne.Text = "";
}
private void Form1_Shown(object sender, EventArgs e) {
// se coloca 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 agrega 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 definidos por la propiedad DropDownStyle:
lista no desplegable con campo de edición | |
lista desplegable con área 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 del ComboBox están disponibles en la propiedad Items:
Es una propiedad indexada, donde Items[i] designa al elemento i del combo. Es de solo lectura.
Supongamos que C es un combo y C.Items es su lista de elementos. Tenemos las siguientes propiedades:
número de elementos del combo | |
elemento i del menú desplegable | |
agrega el objeto o como último elemento del combo | |
agrega una tabla de objetos al final del combo | |
agrega 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 combo | |
índice del elemento seleccionado | |
elemento seleccionado | |
texto que se muestra 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 cuenta con un método ToString heredado de la clase object, el cual 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, y este número 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 en la lista desplegable, se produce el evento SelectedIndexChanged, el cual puede utilizarse para recibir una notificación sobre el cambio de selección en el combo. 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();
// Relleno del 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; lo mostramos
MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
// se registra el nuevo índice
previousSelectedIndex = newSelectedIndex;
}
}
}
}
- línea 5: previousSelectedIndex almacena el último índice seleccionado en el menú desplegable
- línea 10: se llena el combo con una tabla de cadenas de caracteres
- línea 12: se selecciona el primer elemento
- línea 15: el método se ejecuta cada vez que el usuario selecciona un elemento del menú desplegable. A diferencia 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 registra 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 guarda el nuevo índice
7.2.4. Componente ListBox
Nos proponemos construir 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 agregar 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 se define mediante su propiedad SelectionMode:
solo se puede seleccionar un elemento | |
es posible la selección múltiple: al mantener presionada la tecla SHIFT y hacer clic en un elemento, la selección del elemento previamente seleccionado se extiende al elemento actual. | |
Se puede realizar una selección múltiple: un elemento se selecciona o deselecciona haciendo clic con el ratón o presionando la barra espaciadora. |
- El usuario escribe texto en el campo 1. Lo agrega a la lista 1 con el botón Ajouter (2). El campo de entrada (1) se vacía y el usuario puede agregar 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, 5 o 6. El elemento transferido se agrega 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] para transferir 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 de la lista 2 a la lista 1 solo está iluminado si hay un elemento seleccionado en la lista 2
- Los botones [7] y [8] para borrar las listas 1 y 2, respectivamente, solo están activados 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 configurar la propiedad Enabled de los botones en false. Esto se puede hacer en el momento del diseño, lo que generará el código correspondiente en el método InitializeComponent, o bien hacerlo nosotros mismos en el constructor como se muestra a continuación:
public Form1() {
InitializeComponent();
// --- inicializaciones adicionales ---
// se desactivan algunos botones
buttonAjouter.Enabled = false;
button1vers2.Enabled = false;
button2vers1.Enabled = false;
buttonEffacer1.Enabled = false;
buttonEffacer2.Enabled = false;
}
El estado del botón Ajouter se controla mediante el contenido del campo de entrada. El evento TextChanged nos permite rastrear los cambios en dicho contenido:
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// el contenido de textBoxSaisie ha cambiado
// el botón «Agregar» solo aparece activado 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 en 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 agrega 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 cuadro de entrada
textBoxSaisie.Focus();
}
Cabe destacar el método Focus, que permite colocar 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 llamado «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];
// agregar 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 «transferencia» recibe seis parámetros:
- una referencia a la lista que contiene los elementos seleccionados, denominada aquí l1. Al ejecutar la aplicación, l1 es listBox1 o bien listBox2. Se pueden ver ejemplos de llamadas 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 en el 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 similares, pero se refieren 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: comenzamos por el final de la colección para terminar por el principio. Explicaremos por qué.
- línea f: índice de un elemento seleccionado de la lista l1
- línea h: este elemento se agrega a la lista l2
- línea j: y se elimina de la lista l1. Como se elimina, ya no está seleccionado. La colección l1.SelectedIndices de la línea d se volverá a calcular. Perderá el elemento que acaba de eliminarse. A todos los elementos que se encuentran después de este se les cambiará 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, procesará a continuación 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, procesará a continuación 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 ronda 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 radio 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 tan pronto como se produce un cambio. |
6 | muestra dónde encontrar 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, cuando es verdadera, significa que el control está marcado. Aquí utilizaremos un solo método para manejar los seis eventos CheckChanged: el método affiche. Para asegurarnos de que los seis eventos CheckChanged sean manejados por el mismo método affiche, podemos proceder de la siguiente manera:
Seleccionemos el componente radioButton1 y hagamos clic con el botón derecho sobre él para acceder a sus propiedades:
![]() |
En la pestaña événements [1], asociamos el método affiche [2] al evento CheckChanged. Esto significa que queremos que al hacer clic en la opción A1 se procese mediante un método llamado 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 del tipo EventHandler.
Para los otros cinco componentes, se procede de la misma manera. Seleccionemos, por ejemplo, la opción CheckBox1 y sus eventos [3]. Al lado del evento Click, hay una lista desplegable [4] en la que aparecen los métodos existentes que pueden manejar este evento. Aquí solo tenemos el método affiche.. Lo seleccionamos. Repetimos este proceso para todos los demás componentes.
En el método InitializeComponent se generó el código. El método affiche se declaró como manejador 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 verificar que el objeto sender es de tipo CheckBox. Esto nos permite luego 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 originó 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 botón que estaba marcado, el cual pasa a «desmarcado», y otro en el nuevo botón, el cual pasa a «marcado».
7.2.6. Controladores ScrollBar
Existen varios tipos de controladores: el regulador horizontal (HscrollBar), el variador vertical (VscrollBar), el incrementador (NumericUpDown). |
Realicemos 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 controlador 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 están representados por las propiedades Minimum y Maximum y tienen valores predeterminados de 0 y 100.
- Al hacer clic en los extremos del control deslizante, el valor varía en un incremento (positivo o negativo) según el extremo en el que se haga clic, denominado SmallChange, cuyo valor predeterminado 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 predeterminado es 10.
- Al hacer clic en el extremo superior de un control deslizante vertical, su valor disminuye. Esto puede sorprender al usuario promedio, quien normalmente espera que el valor «aumente». Este problema se resuelve asignando un valor negativo a las propiedades SmallChange y LargeChange
- Estas cinco propiedades (Value, Minimum, Maximum, SmallChange, LargeChange) son accesibles tanto para lectura como para escritura.
- El evento principal del variador es aquel que indica un cambio de valor: el evento Scroll.
Un componente NumericUpDown se encuentra cerca del variador: también cuenta con 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 integral del control. El usuario puede modificar este valor por sí mismo, a menos que se haya establecido la propiedad ReadOnly del control en «verdadero». El valor del incremento se establece mediante la propiedad Increment, cuyo valor predeterminado es 1. El evento principal del componente NumericUpDown es el que notifica 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 le 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 le asigna a la etiqueta el valor del variador 1
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
// cambio en el 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 ocurren al mover el ratón sobre el formulario o sobre un control
- [2]: los eventos que ocurren al arrastrar y soltar (Drag'nDrop)
el ratón acaba de ingresar al área del control | |
el ratón acaba de salir del área del control | |
el ratón se mueve dentro del área de control | |
Se presiona el botón izquierdo del ratón | |
Se suelta el botón izquierdo del ratón | |
El usuario suelta un objeto sobre el control | |
El usuario ingresa al á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 |
A continuación se presenta una aplicación que permite comprender mejor en qué momentos ocurren los diferentes 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 se escribe 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 cursor entra en el área 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 mover el cursor del formulario al botón, se observa claramente el cambio de coordenadas. Para ver mejor estos cambios en el área 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 área del control. Así, en nuestro ejemplo, hemos establecido el cursor en «Default» para el formulario mismo ([2]), en «Hand» para la lista 2 ([3]) y en «Cross» para el botón 3 ([4]).
Además, para detectar las entradas y salidas del ratón en la lista 2, procesamos los eventos MouseEnter y MouseLeave de esa 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 manejar 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 la primera posición en el ListBox para que los eventos más recientes aparezcan primero en la lista.
![]() |
Por último, el código del controlador de clic del botón Effacer:
private void buttonEffacer_Click(object sender, EventArgs e) {
listBoxEvts.Items.Clear();
}
7.4. Crear una ventana con menú
Ahora veamos 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]: ahora tenemos un menú que se instala en el formulario con casillas vacías tituladas «Type Here». Solo hay que indicar allí las diferentes opciones del menú.
- [3]: se ha escrito el texto «Opciones A». Pasamos al texto [4].
- [5]: se han ingresado los nombres de las opciones A. Pasamos al nombre [6]
![]() |
- [6]: las primeras opciones B
- [7]: debajo de B1, se coloca un separador. Este está disponible en un menú desplegable asociado al texto «Type Here»
- [8]: para crear un submenú, usa la flecha [8] y escribe el submenú en [9]
Queda por nombrar los diferentes 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 de 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 cualquier otro componente visual 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 procese mediante un método llamado 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, simplemente mostraremos en la etiqueta labelStatut la propiedad Text de la opción de menú en la que se hizo 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 controlador de clic en el método affiche [3,4].
Ejecutemos la aplicación y seleccionemos un elemento del menú:
![]() | ![]() |
7.5. Componentes no visuales
Ahora nos enfocaremos en varios 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 desarrollar la siguiente aplicación:
![]() |
Los controles son los siguientes:
N.º | tipo | nombre | función |
1 | TextBox | TextBoxLignes | texto escrito 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 respaldo de [1]. Este componente se toma de la barra de herramientas [7] y simplemente se coloca en el formulario. Una vez colocado, se guarda, pero no ocupa espacio en el formulario. Es un componente no visual. |
6 | OpenFileDialog | openFileDialog1 | Componente que permite seleccionar el archivo que se cargará en [1]. |
El código asociado al botón Effacer es sencillo:
private void buttonEffacer_Click(object sender, EventArgs e) {
// se coloca 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 archivos que aparecen en la lista desplegable de tipos de archivos del cuadro de diálogo | |
Propriété | el número del tipo de archivo que se propone por defecto en la lista anterior. Comienza en 0. | |
Propriété | la carpeta seleccionada 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 guardado. 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 se establece mediante FilterIndex | |
carpeta actual, definida por InitialDirectory si se ha completado esta propiedad | |
el nombre del archivo seleccionado o ingresado 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 cuadro 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 mostrarán. Cabe destacar la sintaxis de los filtros: filtre1|filtre2|..|filtren con filtro i = Texto|plantilla de archivo. Aquí el usuario podrá elegir entre los archivos *.txt y *.*.
- línea 6: se establece el tipo de archivo que se mostrará primero al usuario. Aquí, el índice 0 se refiere a los archivos *.txt.
- línea 8: se muestra el cuadro de diálogo y se recupera 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 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, al tiempo que se manejan las excepciones que puedan surgir.
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 lo establece FilterIndex | |
carpeta actual, definida por InitialDirectory si se ha completado esta propiedad | |
el nombre del archivo elegido o ingresado 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();
}
}//finalmente
}//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 mostrarán. Cabe destacar la sintaxis de los filtros: filtre1|filtre2|..|filtren con filtro i = Texto|plantilla de archivo. Aquí el usuario podrá elegir entre los archivos *.txt y *.*.
- línea 6: se establece el tipo de archivo que se mostrará primero al usuario. Aquí, el índice 0 se refiere a los archivos *.txt.
- línea 8: se muestra el cuadro de diálogo y se recupera 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 creará se encuentra ahora en la propiedad FileName del objeto openFileDialog1. A partir de ahí, se vuelve a la lectura convencional 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 agregando 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 el tipo de letra de TextBox |
3 | ColorDialog | colorDialog1 | el componente que permite seleccionar un color —tomado de 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 elegir un color [1]. El de la clase FontDialog permite elegir 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 con 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 contamos con los elementos necesarios para manejar 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 que eligió 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 que el usuario seleccionó en el cuadro de diálogo de tipo [FontDialog].
7.5.3. Temporizador
Aquí nos proponemos 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; en [5], el cronómetro detenido.
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 conjunto de herramientas Components [2]:
![]() |
Las propiedades del componente Timer que se utilizarán aquí serán las siguientes:
número de milisegundos tras los cuales se emite un evento Tick. | |
el evento generado al final de Interval milisegundos | |
activa (true) o desactiva (false) el temporizador |
En nuestro ejemplo, el temporizador se llama timer1 y timer1.Interval se establece en 1000 ms (1 s). Por lo tanto, el evento Tick se producirá cada segundo. Al hacer clic en el botón «Detener/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) {
// ¿apagar o encender?
if (buttonArretMarche.Text == "Marche") {
// se registra 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 procesa el clic en el botón «Apagar/Encender».
- línea 15: el texto del botón «On/Off» es «Off» o «On». Por lo tanto, es necesario realizar una comprobación sobre este texto para saber qué acción realizar.
- línea 17: en el caso de «Marcha», se registra la hora de inicio en una variable début, que es una variable global (línea 11) del objeto formulario
- línea 19: 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 «Detener».
- 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 «Iniciar».
Nos queda por procesar el evento Tick en el objeto timer1, evento que ocurre 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: registramos la hora actual
- línea 4: se calcula el tiempo transcurrido desde el momento en que se inició 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 en 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 párrafo 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 estaban 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 está compuesta por los siguientes elementos:
![]() |
- [1]: el proyecto está formado por 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 todos los DLL necesarios para el proyecto:
- [ImpotsV5-dao.dll]: el DLL de la capa [dao] generada en el párrafo 6.4.3;
- [ImpotsV5-metier.dll]: el DLL de la capa [dao] generada en el párrafo 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 párrafo 6.4.6).
- [references], detallado en [3]: las referencias del proyecto. Se ha agregado 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 párrafo 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 para ingresar los parámetros del 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 Visual Studio
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 para mostrar
Form form = null;
// ¿Hubo alguna excepción?
if (ex != null) {
// sí: se crea el mensaje de error que se 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 debe mostrar
Form2 form2 = new Form2();
form2.MsgErreur = msgErreur.ToString();
// esta será la ventana que se mostrará
form = form2;
} else {
// todo salió 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 cualquier 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, nos preparamos para mostrar un formulario de tipo [Form2]
- líneas 38-44: se genera el mensaje de error que se mostrará. Este consiste en 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 mostrará:
public string MsgErreur { private get; set; }
Se asigna un valor a esta propiedad.
- línea 49: se inicializa la referencia form, que designa la ventana que se mostrará. Cabe destacar el polimorfismo en acción. form2 no es de tipo [Form], sino de tipo [Form2], un tipo derivado de [Form].
- líneas 50-57: no se produjo 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 completa esta propiedad.
- línea 56: se inicializa la referencia form, que designa la ventana que se debe mostrar. Cabe destacar nuevamente 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 | marque 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 | monto 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 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 reporta 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);
// regreso al campo con error
textSalaire.Focus();
// Selección del texto del campo de entrada
textSalaire.SelectAll();
// Regreso a la interfaz de entrada
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] inyectar en [Form1] una referencia a la capa [metier].
- línea [14]: el procedimiento de cálculo del impuesto
- líneas 15-27: verificació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, lograda 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 debe mostrar. Este mensaje se muestra durante el procesamiento del evento Load, líneas 12-16.
- línea 14: el mensaje de error se coloca 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 permitió sustituir la implementación de consola de la capa [ui] existente por una implementación gráfica, sin modificar las capas [metier] y [dao]. Pudimos concentrarnos 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 reemplazada por una capa [dao] que procese los datos de una base de datos. Veremos que esto se llevará a cabo sin afectar a las capas [ui] y [metier].


































































