Hasta ahora, BiblioTech ha vivido enteramente en la consola: cada préstamo, cada alta de socio, cada consulta al catálogo se ha mostrado con Console.WriteLine y se ha recibido con Console.ReadLine. Esta lección da el primer paso hacia una interfaz real, con Windows Forms (a menudo abreviado WinForms), el framework de interfaz gráfica más antiguo de .NET que sigue en uso. Aunque hoy existen alternativas más modernas (WPF, Blazor, MAUI, que verás en las próximas lecciones), Windows Forms sigue siendo una opción legítima y muy productiva para un tipo concreto de aplicación: herramientas internas de escritorio en Windows, sin pretensiones visuales sofisticadas, donde la velocidad de desarrollo importa más que la estética. Vas a construir un formulario que muestra el catálogo de BiblioTech y permite prestar un libro con un clic, reutilizando exactamente la misma lógica de dominio de los módulos anteriores.

Contenido

  1. Qué es Windows Forms y cuándo tiene sentido usarlo hoy
  2. Crear un proyecto con dotnet new winforms
  3. El diseñador de formularios y los controles básicos
  4. El modelo de eventos de los controles: Click y los delegados del Módulo 4
  5. Ejemplo completo: un formulario de préstamos para BiblioTech
  6. Buenas prácticas al conectar la interfaz con el dominio

  1. Qué es Windows Forms y cuándo tiene sentido usarlo hoy

Windows Forms apareció en el año 2002, junto con la primera versión de .NET, y fue durante años la forma estándar de construir aplicaciones de escritorio en C#. Su modelo es sencillo: un formulario (Form) es una ventana, sobre la que se colocan controles (botones, cajas de texto, listas...) mediante un diseñador visual de arrastrar y soltar. Cada control expone propiedades (posición, texto, color) y eventos (Click, TextChanged) a los que el programador engancha código.

¿Sigue teniendo sentido en 2026? Sí, para un perfil de aplicación muy concreto:

Característica Windows Forms
Plataforma Solo Windows (no es multiplataforma)
Curva de aprendizaje Muy baja: el diseñador visual genera casi todo el código de la interfaz
Aspecto visual Funcional, con el estilo nativo de Windows; no está pensado para diseños sofisticados
Separación UI/lógica Débil por defecto (el código de eventos suele mezclarse con la lógica)
Caso de uso típico Herramientas internas de empresa, utilidades administrativas, aplicaciones de gestión sencillas
Alternativa moderna recomendada para UI más cuidada WPF (siguiente lección)

Para BiblioTech, Windows Forms encaja perfectamente en un escenario: una aplicación interna que usa el personal de la biblioteca en los ordenadores de mostrador, sin necesidad de instalar nada más que .NET, y sin que el aspecto visual sea una prioridad frente a la rapidez de tener algo funcionando.

  1. Crear un proyecto con dotnet new winforms

Igual que en módulos anteriores creaste proyectos de consola con dotnet new console, Windows Forms tiene su propia plantilla:

dotnet new winforms -n BiblioTech.Escritorio
cd BiblioTech.Escritorio

Esto genera una estructura mínima:

BiblioTech.Escritorio/
├── Program.cs
├── Form1.cs
├── Form1.Designer.cs
└── Form1.resx
  • Program.cs: el punto de entrada, que arranca la aplicación y muestra el primer formulario.
  • Form1.cs: el código del formulario que tú escribes (eventos, lógica).
  • Form1.Designer.cs: código generado automáticamente por el diseñador visual; no se edita a mano en circunstancias normales.
  • Form1.resx: recursos del formulario (iconos, textos incrustados).
// Program.cs generado por la plantilla
ApplicationConfiguration.Initialize();
Application.Run(new Form1());

Application.Run(new Form1()) crea una instancia del formulario principal y entra en el bucle de mensajes de Windows: un bucle interno, gestionado por el framework, que espera eventos del sistema operativo (clics de ratón, pulsaciones de teclado, repintados de pantalla) y los despacha al formulario correspondiente. Este bucle es exactamente lo que hace que un clic en un botón acabe ejecutando tu código: no es magia, es el mismo modelo de eventos que ya conoces del Módulo 4, aplicado ahora a la interacción del usuario con el ratón y el teclado.

  1. El diseñador de formularios y los controles básicos

El diseñador de formularios (disponible en Visual Studio en Windows) permite arrastrar controles desde una caja de herramientas hasta el formulario, y ajustar sus propiedades desde un panel lateral, sin escribir código a mano. Los controles más habituales:

Control Para qué sirve Propiedad principal
Label Mostrar texto no editable (títulos, etiquetas) Text
TextBox Entrada de texto de una línea Text
Button Disparar una acción al hacer clic Text (el texto del botón)
ListBox Lista simple de elementos seleccionables Items, SelectedItem
DataGridView Tabla con filas y columnas, ideal para colecciones de objetos DataSource

Cuando arrastras un Button al formulario y cambias su propiedad Text a "Prestar", el diseñador escribe automáticamente algo así en Form1.Designer.cs (código generado, no manual):

// Form1.Designer.cs (fragmento generado por el diseñador)
private Button botonPrestar;

private void InitializeComponent()
{
    botonPrestar = new Button();
    botonPrestar.Location = new Point(120, 200);
    botonPrestar.Size = new Size(100, 30);
    botonPrestar.Text = "Prestar";
    botonPrestar.Click += botonPrestar_Click; // conexion del evento, ver apartado 4
    // ...
    Controls.Add(botonPrestar);
}

Controls.Add(botonPrestar) añade el botón a la colección Controls del formulario —el conjunto de controles hijos que se dibujan sobre él—; sin esa línea, el botón existiría como objeto en memoria pero nunca aparecería en pantalla.

  1. El modelo de eventos de los controles: Click y los delegados del Módulo 4

La lección de Delegados y Eventos (Módulo 4) explicó que un evento es, en esencia, una lista de métodos (delegados) que se ejecutan cuando algo ocurre —allí el ejemplo fue Biblioteca.PrestamoRegistrado—. Los controles de Windows Forms usan exactamente el mismo mecanismo: Button.Click es un evento de tipo EventHandler, al que te suscribes con +=, igual que hiciste con PrestamoRegistrado:

public partial class FormularioPrincipal : Form
{
    public FormularioPrincipal()
    {
        InitializeComponent(); // generado por el diseñador: crea y coloca los controles

        botonPrestar.Click += BotonPrestar_Click; // suscripcion al evento, igual que en el Modulo 4
    }

    private void BotonPrestar_Click(object? remitente, EventArgs argumentos)
    {
        // codigo que se ejecuta cada vez que el usuario hace clic en "Prestar"
    }
}

La firma (object? remitente, EventArgs argumentos) es el patrón estándar de eventos de .NET: remitente es el objeto que disparó el evento (aquí, botonPrestar) y argumentos transporta información adicional (vacía en Click, pero no en otros eventos como KeyPress, que incluye la tecla pulsada). Reconocer esta firma es clave: es la misma estructura object, EventArgs que usan prácticamente todos los eventos de la biblioteca estándar de .NET, no solo los de Windows Forms.

  1. Ejemplo completo: un formulario de préstamos para BiblioTech

Se reutiliza el dominio ya construido —Biblioteca, MaterialBibliotecario, Libro, Socio— sin modificar ni una sola clase. El formulario solo añade una capa visual encima:

// FormularioPrincipal.cs
public partial class FormularioPrincipal : Form
{
    private readonly Biblioteca _biblioteca;

    public FormularioPrincipal(Biblioteca biblioteca)
    {
        InitializeComponent();
        _biblioteca = biblioteca;

        CargarCatalogoEnListBox();

        botonPrestar.Click += BotonPrestar_Click;
    }

    private void CargarCatalogoEnListBox()
    {
        listaCatalogo.Items.Clear();

        foreach (MaterialBibliotecario material in _biblioteca.Catalogo)
        {
            // ToString() no esta sobrescrito en MaterialBibliotecario, asi que se compone el texto aqui
            listaCatalogo.Items.Add($"{material.Titulo} ({material.Autor})");
        }
    }

    private void BotonPrestar_Click(object? remitente, EventArgs argumentos)
    {
        int indiceSeleccionado = listaCatalogo.SelectedIndex;

        if (indiceSeleccionado < 0)
        {
            MessageBox.Show("Selecciona un libro de la lista antes de prestar.", "Aviso");
            return;
        }

        MaterialBibliotecario materialSeleccionado = _biblioteca.Catalogo[indiceSeleccionado];

        if (!materialSeleccionado.Disponible)
        {
            MessageBox.Show($"'{materialSeleccionado.Titulo}' ya esta prestado.", "Aviso");
            return;
        }

        materialSeleccionado.Prestar(); // logica de dominio ya existente, Modulo 2

        MessageBox.Show($"Prestamo registrado: {materialSeleccionado.Titulo}", "Prestamo realizado");

        CargarCatalogoEnListBox(); // refresca la lista para reflejar el nuevo estado
    }
}
// Program.cs
Biblioteca biblioteca = new Biblioteca();
biblioteca.AgregarMaterial(new Libro("Rayuela", "Julio Cortazar", "978-84-376-0495-4"));
biblioteca.AgregarMaterial(new Libro("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1"));

ApplicationConfiguration.Initialize();
Application.Run(new FormularioPrincipal(biblioteca));

Ningún concepto de Biblioteca, MaterialBibliotecario o Libro ha cambiado: el formulario se limita a leer _biblioteca.Catalogo para pintar la ListBox, y a llamar a materialSeleccionado.Prestar() cuando el usuario hace clic —exactamente el mismo método que ya usabas desde la consola en el Módulo 2. MessageBox.Show(...) muestra un cuadro de diálogo emergente, la forma más simple de comunicar un mensaje al usuario en Windows Forms, equivalente visual del Console.WriteLine de los módulos anteriores.

Para un catálogo más grande, un DataGridView con listaCatalogo.DataSource = _biblioteca.Catalogo (en vez de una ListBox rellenada manualmente) mostraría automáticamente una tabla con una columna por propiedad pública, aunque requiere ajustar qué columnas mostrar para no exponer Disponible como texto plano sin formato.

  1. Buenas prácticas al conectar la interfaz con el dominio

  • El formulario no debe contener lógica de negocio: BotonPrestar_Click llama a materialSeleccionado.Prestar(), no reimplementa la comprobación de disponibilidad dentro del formulario —esa lógica ya vive, correctamente, en MaterialBibliotecario desde el Módulo 2.
  • Pasar Biblioteca al constructor del formulario, como se hace aquí, en vez de crearla dentro del propio formulario: mantiene el formulario desacoplado de cómo se construye o configura Biblioteca (con persistencia en texto, JSON, SQLite... según lo visto en el Módulo 5), un principio que se retomará con más profundidad en el Módulo 8 al hablar de inyección de dependencias.
  • Refrescar la interfaz tras cada cambio de estado: como en CargarCatalogoEnListBox() después de Prestar(), para que lo que ve el usuario coincida siempre con el estado real de _biblioteca.

Errores Comunes y Consejos

  • Editar Form1.Designer.cs a mano: es código generado por el diseñador visual; cualquier cambio hecho a mano puede perderse la próxima vez que se edite el formulario desde el diseñador. Los cambios de comportamiento van en el fichero .cs "normal" (Form1.cs), no en el .Designer.cs.
  • Olvidar comprobar SelectedIndex < 0: si el usuario hace clic en "Prestar" sin haber seleccionado nada en la ListBox, SelectedIndex vale -1; acceder al catálogo con ese índice lanzaría una excepción en tiempo de ejecución.
  • Mezclar lógica de negocio dentro del método de evento: duplicar aquí la comprobación de Disponible en vez de delegarla en Prestar() (que ya la hace, Módulo 2) generaría dos fuentes de verdad que podrían desincronizarse con el tiempo.
  • Consejo: Windows Forms solo compila y ejecuta en Windows; si tu equipo de desarrollo usa distintos sistemas operativos, ten esto en cuenta antes de elegirlo para un proyecto nuevo.

Ejercicios

  1. Añade a FormularioPrincipal un TextBox llamado cuadroTitulo y un segundo Button llamado botonBuscar. En el evento Click de botonBuscar, filtra _biblioteca.Catalogo por título (usando Contains, ya visto con cadenas en el Módulo 1) y muestra solo los resultados en listaCatalogo.

  2. Modifica BotonPrestar_Click para que, en vez de MessageBox.Show, muestre el mensaje de confirmación en un Label llamado etiquetaEstado situado en la parte inferior del formulario.

Soluciones

private void BotonBuscar_Click(object? remitente, EventArgs argumentos)
{
    string textoBusqueda = cuadroTitulo.Text;

    listaCatalogo.Items.Clear();

    IEnumerable<MaterialBibliotecario> coincidencias = _biblioteca.Catalogo
        .Where(material => material.Titulo.Contains(textoBusqueda, StringComparison.OrdinalIgnoreCase));

    foreach (MaterialBibliotecario material in coincidencias)
    {
        listaCatalogo.Items.Add($"{material.Titulo} ({material.Autor})");
    }
}
private void BotonPrestar_Click(object? remitente, EventArgs argumentos)
{
    int indiceSeleccionado = listaCatalogo.SelectedIndex;

    if (indiceSeleccionado < 0)
    {
        etiquetaEstado.Text = "Selecciona un libro de la lista antes de prestar.";
        return;
    }

    MaterialBibliotecario materialSeleccionado = _biblioteca.Catalogo[indiceSeleccionado];

    if (!materialSeleccionado.Disponible)
    {
        etiquetaEstado.Text = $"'{materialSeleccionado.Titulo}' ya esta prestado.";
        return;
    }

    materialSeleccionado.Prestar();
    etiquetaEstado.Text = $"Prestamo registrado: {materialSeleccionado.Titulo}";

    CargarCatalogoEnListBox();
}

Conclusión

En esta lección has construido la primera interfaz gráfica real de BiblioTech con Windows Forms: un proyecto dotnet new winforms, controles básicos (ListBox, Button, Label) colocados con el diseñador visual, y el modelo de eventos de controles (Click), que resulta ser exactamente el mismo mecanismo de delegados y eventos del Módulo 4 aplicado a la interacción del usuario. Todo ello sin tocar una sola línea de Biblioteca, MaterialBibliotecario o Libro: la interfaz es una capa nueva encima del dominio ya construido.

La siguiente lección presenta WPF, una alternativa más moderna a Windows Forms, con una diferencia de fondo importante: en vez de colocar controles imperativamente desde el diseñador, WPF describe la interfaz de forma declarativa con XAML, y separa con más disciplina la interfaz de la lógica mediante el patrón MVVM y el data binding — verás el mismo catálogo de BiblioTech, pero conectado a la interfaz de una forma bastante distinta.

Curso de Programación en C#

Módulo 1: Introducción a C#

Módulo 2: Estructuras de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Conceptos Avanzados de C#

Módulo 5: Trabajando con Datos

Módulo 6: Temas Avanzados

Módulo 7: Construcción de Aplicaciones

Módulo 8: Mejores Prácticas y Patrones de Diseño

Módulo 9: Proyecto Final

© Copyright 2026. Todos los derechos reservados