Fins ara, BiblioTech ha viscut sencer a la consola: cada préstec, cada alta de soci, cada consulta
al catàleg s'ha mostrat amb Console.WriteLine i s'ha rebut amb Console.ReadLine. Aquesta
lliçó fa el primer pas cap a una interfície real, amb Windows Forms (sovint abreujat
WinForms), el framework d'interfície gràfica més antic de .NET que encara es fa servir. Tot i
que avui existeixen alternatives més modernes (WPF, Blazor, MAUI, que veuràs a les properes
lliçons), Windows Forms continua sent una opció legítima i molt productiva per a un tipus concret
d'aplicació: eines internes d'escriptori a Windows, sense pretensions visuals sofisticades, on la
velocitat de desenvolupament importa més que l'estètica. Construiràs un formulari que mostra el
catàleg de BiblioTech i permet prestar un llibre amb un clic, reutilitzant exactament la mateixa
lògica de domini dels mòduls anteriors.
Contingut
- Què és Windows Forms i quan té sentit fer-lo servir avui
- Crear un projecte amb
dotnet new winforms - El dissenyador de formularis i els controls bàsics
- El model d'esdeveniments dels controls:
Clicki els delegats del Mòdul 4 - Exemple complet: un formulari de préstecs per a BiblioTech
- Bones pràctiques en connectar la interfície amb el domini
- Què és Windows Forms i quan té sentit fer-lo servir avui
Windows Forms va aparèixer l'any 2002, juntament amb la primera versió de .NET, i va ser durant
anys la forma estàndard de construir aplicacions d'escriptori en C#. El seu model és senzill: un
formulari (Form) és una finestra, sobre la qual es col·loquen controls (botons, caixes
de text, llistes...) mitjançant un dissenyador visual d'arrossegar i deixar anar. Cada control
exposa propietats (posició, text, color) i esdeveniments (Click, TextChanged) als quals el
programador hi enganxa codi.
Continua tenint sentit el 2026? Sí, per a un perfil d'aplicació molt concret:
| Característica | Windows Forms |
|---|---|
| Plataforma | Només Windows (no és multiplataforma) |
| Corba d'aprenentatge | Molt baixa: el dissenyador visual genera gairebé tot el codi de la interfície |
| Aspecte visual | Funcional, amb l'estil natiu de Windows; no està pensat per a dissenys sofisticats |
| Separació UI/lògica | Feble per defecte (el codi d'esdeveniments sol barrejar-se amb la lògica) |
| Cas d'ús típic | Eines internes d'empresa, utilitats administratives, aplicacions de gestió senzilles |
| Alternativa moderna recomanada per a una UI més acurada | WPF (lliçó següent) |
Per a BiblioTech, Windows Forms encaixa perfectament en un escenari: una aplicació interna que fa servir el personal de la biblioteca als ordinadors de taulell, sense necessitat d'instal·lar res més que .NET, i sense que l'aspecte visual sigui una prioritat davant la rapidesa de tenir alguna cosa funcionant.
- Crear un projecte amb
dotnet new winforms
dotnet new winformsIgual que en mòduls anteriors vas crear projectes de consola amb dotnet new console, Windows
Forms té la seva pròpia plantilla:
Això genera una estructura mínima:
Program.cs: el punt d'entrada, que arrenca l'aplicació i mostra el primer formulari.Form1.cs: el codi del formulari que tu escrius (esdeveniments, lògica).Form1.Designer.cs: codi generat automàticament pel dissenyador visual; no s'edita a mà en circumstàncies normals.Form1.resx: recursos del formulari (icones, textos incrustats).
// Program.cs generat per la plantilla
ApplicationConfiguration.Initialize();
Application.Run(new Form1());Application.Run(new Form1()) crea una instància del formulari principal i entra en el bucle
de missatges de Windows: un bucle intern, gestionat pel framework, que espera esdeveniments del
sistema operatiu (clics de ratolí, pulsacions de teclat, repintats de pantalla) i els envia al
formulari corresponent. Aquest bucle és exactament el que fa que un clic en un botó acabi
executant el teu codi: no és màgia, és el mateix model d'esdeveniments que ja coneixes del Mòdul
4, aplicat ara a la interacció de l'usuari amb el ratolí i el teclat.
- El dissenyador de formularis i els controls bàsics
El dissenyador de formularis (disponible a Visual Studio a Windows) permet arrossegar controls des d'una caixa d'eines fins al formulari, i ajustar-ne les propietats des d'un panell lateral, sense escriure codi a mà. Els controls més habituals:
| Control | Per a què serveix | Propietat principal |
|---|---|---|
Label |
Mostrar text no editable (títols, etiquetes) | Text |
TextBox |
Entrada de text d'una línia | Text |
Button |
Disparar una acció en fer clic | Text (el text del botó) |
ListBox |
Llista simple d'elements seleccionables | Items, SelectedItem |
DataGridView |
Taula amb files i columnes, ideal per a col·leccions d'objectes | DataSource |
Quan arrossegues un Button al formulari i li canvies la propietat Text a "Prestar", el
dissenyador escriu automàticament alguna cosa així a Form1.Designer.cs (codi generat, no
manual):
// Form1.Designer.cs (fragment generat pel dissenyador)
private Button botoPrestar;
private void InitializeComponent()
{
botoPrestar = new Button();
botoPrestar.Location = new Point(120, 200);
botoPrestar.Size = new Size(100, 30);
botoPrestar.Text = "Prestar";
botoPrestar.Click += botoPrestar_Click; // connexio de l'esdeveniment, veure apartat 4
// ...
Controls.Add(botoPrestar);
}Controls.Add(botoPrestar) afegeix el botó a la col·lecció Controls del formulari —el conjunt
de controls fills que es dibuixen sobre seu—; sense aquesta línia, el botó existiria com a
objecte en memòria però mai apareixeria en pantalla.
- El model d'esdeveniments dels controls:
Click i els delegats del Mòdul 4
Click i els delegats del Mòdul 4La lliçó de Delegats i Esdeveniments (Mòdul 4) va explicar que un esdeveniment és, en essència,
una llista de mètodes (delegats) que s'executen quan alguna cosa passa —allà l'exemple va ser
Biblioteca.PrestecRegistrat—. Els controls de Windows Forms fan servir exactament el mateix
mecanisme: Button.Click és un esdeveniment de tipus EventHandler, al qual et subscrius amb
+=, igual que vas fer amb PrestecRegistrat:
public partial class FormulariPrincipal : Form
{
public FormulariPrincipal()
{
InitializeComponent(); // generat pel dissenyador: crea i col·loca els controls
botoPrestar.Click += BotoPrestar_Click; // subscripcio a l'esdeveniment, igual que al Modul 4
}
private void BotoPrestar_Click(object? remitent, EventArgs arguments)
{
// codi que s'executa cada vegada que l'usuari fa clic a "Prestar"
}
}La signatura (object? remitent, EventArgs arguments) és el patró estàndard d'esdeveniments de
.NET: remitent és l'objecte que ha disparat l'esdeveniment (aquí, botoPrestar) i arguments
transporta informació addicional (buida a Click, però no en altres esdeveniments com
KeyPress, que inclou la tecla premuda). Reconèixer aquesta signatura és clau: és la mateixa
estructura object, EventArgs que fan servir pràcticament tots els esdeveniments de la
biblioteca estàndard de .NET, no només els de Windows Forms.
- Exemple complet: un formulari de préstecs per a BiblioTech
Es reutilitza el domini ja construït —Biblioteca, MaterialBibliotecari, Llibre, Soci—
sense modificar ni una sola classe. El formulari només afegeix una capa visual a sobre:
// FormulariPrincipal.cs
public partial class FormulariPrincipal : Form
{
private readonly Biblioteca _biblioteca;
public FormulariPrincipal(Biblioteca biblioteca)
{
InitializeComponent();
_biblioteca = biblioteca;
CarregarCatalegEnListBox();
botoPrestar.Click += BotoPrestar_Click;
}
private void CarregarCatalegEnListBox()
{
llistaCataleg.Items.Clear();
foreach (MaterialBibliotecari material in _biblioteca.Cataleg)
{
// ToString() no esta sobreescrit a MaterialBibliotecari, aixi que el text es compon aqui
llistaCataleg.Items.Add($"{material.Titol} ({material.Autor})");
}
}
private void BotoPrestar_Click(object? remitent, EventArgs arguments)
{
int indexSeleccionat = llistaCataleg.SelectedIndex;
if (indexSeleccionat < 0)
{
MessageBox.Show("Selecciona un llibre de la llista abans de prestar.", "Avis");
return;
}
MaterialBibliotecari materialSeleccionat = _biblioteca.Cataleg[indexSeleccionat];
if (!materialSeleccionat.Disponible)
{
MessageBox.Show($"'{materialSeleccionat.Titol}' ja esta prestat.", "Avis");
return;
}
materialSeleccionat.Prestar(); // logica de domini ja existent, Modul 2
MessageBox.Show($"Prestec registrat: {materialSeleccionat.Titol}", "Prestec realitzat");
CarregarCatalegEnListBox(); // refresca la llista per reflectir el nou estat
}
}// Program.cs
Biblioteca biblioteca = new Biblioteca();
biblioteca.AfegirMaterial(new Llibre("Rayuela", "Julio Cortazar", "978-84-376-0495-4"));
biblioteca.AfegirMaterial(new Llibre("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1"));
ApplicationConfiguration.Initialize();
Application.Run(new FormulariPrincipal(biblioteca));Cap concepte de Biblioteca, MaterialBibliotecari o Llibre ha canviat: el formulari es limita
a llegir _biblioteca.Cataleg per pintar la ListBox, i a cridar
materialSeleccionat.Prestar() quan l'usuari fa clic —exactament el mateix mètode que ja feies
servir des de la consola al Mòdul 2. MessageBox.Show(...) mostra un quadre de diàleg emergent,
la forma més simple de comunicar un missatge a l'usuari en Windows Forms, l'equivalent visual del
Console.WriteLine dels mòduls anteriors.
Per a un catàleg més gran, un DataGridView amb llistaCataleg.DataSource = _biblioteca.Cataleg
(en lloc d'una ListBox emplenada manualment) mostraria automàticament una taula amb una columna
per propietat pública, tot i que caldria ajustar quines columnes mostrar per no exposar
Disponible com a text pla sense format.
- Bones pràctiques en connectar la interfície amb el domini
- El formulari no ha de contenir lògica de negoci:
BotoPrestar_ClickcridamaterialSeleccionat.Prestar(), no reimplementa la comprovació de disponibilitat dins del formulari —aquesta lògica ja viu, correctament, aMaterialBibliotecarides del Mòdul 2. - Passar
Bibliotecaal constructor del formulari, com es fa aquí, en lloc de crear-la dins del mateix formulari: manté el formulari desacoblat de com es construeix o configuraBiblioteca(amb persistència en text, JSON, SQLite... segons el que s'ha vist al Mòdul 5), un principi que es retomarà amb més profunditat al Mòdul 8 en parlar d'injecció de dependències. - Refrescar la interfície després de cada canvi d'estat: com a
CarregarCatalegEnListBox()després dePrestar(), perquè el que veu l'usuari coincideixi sempre amb l'estat real de_biblioteca.
Errors Comuns i Consells
- Editar
Form1.Designer.csa mà: és codi generat pel dissenyador visual; qualsevol canvi fet a mà es pot perdre la propera vegada que s'editi el formulari des del dissenyador. Els canvis de comportament van al fitxer.cs"normal" (Form1.cs), no al.Designer.cs. - Oblidar comprovar
SelectedIndex < 0: si l'usuari fa clic a "Prestar" sense haver seleccionat res a laListBox,SelectedIndexval-1; accedir al catàleg amb aquest índex llançaria una excepció en temps d'execució. - Barrejar lògica de negoci dins del mètode d'esdeveniment: duplicar aquí la comprovació de
Disponibleen lloc de delegar-la aPrestar()(que ja la fa, Mòdul 2) generaria dues fonts de veritat que podrien desincronitzar-se amb el temps. - Consell: Windows Forms només compila i s'executa a Windows; si el teu equip de desenvolupament fa servir sistemes operatius diferents, tingues-ho en compte abans de triar-lo per a un projecte nou.
Exercicis
-
Afegeix a
FormulariPrincipalunTextBoxanomenatquadreTitoli un segonButtonanomenatbotoCercar. A l'esdevenimentClickdebotoCercar, filtra_biblioteca.Catalegper títol (fent servirContains, ja vist amb cadenes al Mòdul 1) i mostra només els resultats allistaCataleg. -
Modifica
BotoPrestar_Clickperquè, en lloc deMessageBox.Show, mostri el missatge de confirmació en unLabelanomenatetiquetaEstatsituat a la part inferior del formulari.
Solucions
private void BotoCercar_Click(object? remitent, EventArgs arguments)
{
string textCerca = quadreTitol.Text;
llistaCataleg.Items.Clear();
IEnumerable<MaterialBibliotecari> coincidencies = _biblioteca.Cataleg
.Where(material => material.Titol.Contains(textCerca, StringComparison.OrdinalIgnoreCase));
foreach (MaterialBibliotecari material in coincidencies)
{
llistaCataleg.Items.Add($"{material.Titol} ({material.Autor})");
}
}
private void BotoPrestar_Click(object? remitent, EventArgs arguments)
{
int indexSeleccionat = llistaCataleg.SelectedIndex;
if (indexSeleccionat < 0)
{
etiquetaEstat.Text = "Selecciona un llibre de la llista abans de prestar.";
return;
}
MaterialBibliotecari materialSeleccionat = _biblioteca.Cataleg[indexSeleccionat];
if (!materialSeleccionat.Disponible)
{
etiquetaEstat.Text = $"'{materialSeleccionat.Titol}' ja esta prestat.";
return;
}
materialSeleccionat.Prestar();
etiquetaEstat.Text = $"Prestec registrat: {materialSeleccionat.Titol}";
CarregarCatalegEnListBox();
}
Conclusió
En aquesta lliçó has construït la primera interfície gràfica real de BiblioTech amb Windows
Forms: un projecte dotnet new winforms, controls bàsics (ListBox, Button, Label)
col·locats amb el dissenyador visual, i el model d'esdeveniments dels controls (Click), que
resulta ser exactament el mateix mecanisme de delegats i esdeveniments del Mòdul 4 aplicat a la
interacció de l'usuari. Tot això sense tocar ni una línia de Biblioteca, MaterialBibliotecari
o Llibre: la interfície és una capa nova a sobre del domini ja construït.
La lliçó següent presenta WPF, una alternativa més moderna a Windows Forms, amb una diferència de fons important: en lloc de col·locar controls imperativament des del dissenyador, WPF descriu la interfície de forma declarativa amb XAML, i separa amb més disciplina la interfície de la lògica mitjançant el patró MVVM i el data binding — veuràs el mateix catàleg de BiblioTech, però connectat a la interfície d'una manera força diferent.
Curs de Programació en C#
Mòdul 1: Introducció al C#
- Introducció al C#
- Configuració de l'Entorn de Desenvolupament
- Programa Hola Món
- Sintaxi i Estructura Bàsica
- Variables i Tipus de Dades
- Arrays i Cadenes de Text
Mòdul 2: Estructures de Control
Mòdul 3: Programació Orientada a Objectes
- Classes i Objectes
- Mètodes
- Constructors i Destructors
- Herència
- Polimorfisme
- Encapsulació
- Abstracció
- Structs i Records: Tipus per Valor i per Referència
Mòdul 4: Conceptes Avançats de C#
- Interfícies
- Delegats i Esdeveniments
- Pattern Matching i Característiques Modernes de C#
- Genèrics
- Col·leccions
- LINQ (Consulta Integrada en el Llenguatge)
- Programació Asíncrona
Mòdul 5: Treballant amb Dades
- Entrada/Sortida de Fitxers
- Serialització
- Connectivitat amb Bases de Dades
- Entity Framework
- Treball amb JSON i Consum d'APIs REST
Mòdul 6: Temes Avançats
- Reflexió
- Atributs
- Programació Dinàmica
- Gestió de Memòria i Recollida d'Escombraries
- Multifil i Programació Paral·lela
Mòdul 7: Construcció d'Aplicacions
Mòdul 8: Bones Pràctiques i Patrons de Disseny
- Estàndards de Codificació i Bones Pràctiques
- Patrons de Disseny
- Injecció de Dependències i Inversió de Control
- Proves Unitàries
- Revisió i Refactorització de Codi
