4 MVVM, del code-behind al patrón
Llegamos a MVVM por pasos, no de golpe. Para quien aún no ha
visto enlaces (bindings), arrancamos por lo más intuitivo y
construimos hacia el patrón. Cada pestaña resuelve un problema
de la anterior.
El punto de partida para el grupo nuevo: le pones nombre al
control con x:Name y lo manipulas directo desde
el archivo .xaml.cs.
<VerticalStackLayout Padding="20" Spacing="12">
<Entry x:Name="entradaNombre" Placeholder="Tu nombre" />
<Button x:Name="btnSaludar" Text="Saludar" Clicked="btnSaludar_Clicked" />
<Label x:Name="lblSaludo" FontSize="20" />
</VerticalStackLayout>
private void btnSaludar_Clicked(object sender, EventArgs e)
{
string nombre = entradaNombre.Text; // leo el control
lblSaludo.Text = $"¡Hola, {nombre}!"; // escribo el control
}
El problema: al crecer, la lógica queda
amarrada a la pantalla, no se puede probar sin la UI y el
code-behind se vuelve inmanejable. La UI y la lógica están
acopladas. Eso es lo que MVVM resuelve.
Un binding es un puente automático entre
una propiedad del control y una del objeto de datos.
INotifyPropertyChanged es lo que dispara la
actualización: el objeto avisa "cambié" y la UI se
redibuja sola.
public class SaludoModel : INotifyPropertyChanged
{
private string _saludo;
public string Saludo
{
get => _saludo;
set { _saludo = value; OnPropertyChanged(); } // avisa a la UI
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string n = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
}
<Label Text="{Binding Saludo}" FontSize="20" />
Un detalle que confunde a todos: el binding
tiene dirección. Label.Text es
OneWay (datos → UI). Un
Entry.Text es TwoWay (UI ↔ datos):
lo que el usuario escribe regresa al ViewModel solo. Si
alguna vez no se refleja lo esperado, revisa el modo:
{Binding Nombre, Mode=TwoWay}.
Pruébalo tú mismo abajo para ver el binding en acción.
El puente conceptual clave:
el mismo binding se crea desde el code-behind, en C#. Sirve para entender qué hace
{Binding} por debajo.
var modelo = new SaludoModel { Saludo = "¡Hola!" };
this.BindingContext = modelo; // 1) fuente de datos
// 2) conecto Text del Label con la propiedad Saludo del modelo
lblSaludo.SetBinding(Label.TextProperty, "Saludo");
// esto ahora actualiza la UI SOLO, sin tocar el control:
modelo.Saludo = "¡Hola desde el binding!";
lblSaludo.SetBinding(Label.TextProperty,
"Saludo")
en C# es exactamente lo mismo que
Text="{Binding Saludo}" en XAML. Una
vez que se ve el enlace creado a mano, la sintaxis
declarativa deja de ser magia.
Model: datos y reglas.
View: el XAML, solo presentación.
ViewModel: el intermediario con propiedades
y comandos. Las acciones van por Command, no
por eventos Clicked.
public class SaludoViewModel : INotifyPropertyChanged
{
public string Nombre { get; set; }
private string _saludo;
public string Saludo { get => _saludo; set { _saludo = value; OnPropertyChanged(); } }
public ICommand SaludarCommand { get; }
public SaludoViewModel()
=> SaludarCommand = new Command(() => Saludo = $"¡Hola, {Nombre}!");
}
<Entry Text="{Binding Nombre}" Placeholder="Tu nombre" />
<Button Text="Saludar" Command="{Binding SaludarCommand}" />
<Label Text="{Binding Saludo}" FontSize="20" />
La View se conecta al ViewModel una sola vez:
BindingContext = new SaludoViewModel();. A
partir de ahí, pantalla y lógica evolucionan por separado.
En una app real no usas new:
registras la View y el ViewModel en
MauiProgram.cs y MAUI los inyecta por
constructor. Así el ViewModel recibe sus dependencias —un
repositorio, un servicio de red— sin acoplarse a nada.
// MauiProgram.cs
builder.Services.AddTransient<SaludoViewModel>();
builder.Services.AddTransient<MainPage>();
En proyectos reales casi nadie escribe
INotifyPropertyChanged a mano: el
CommunityToolkit.Mvvm genera esa plomería por
ti a partir de atributos. Este es el "después";
justo abajo desglosamos
cómo funciona y qué problema resuelve.
public partial class SaludoViewModel : ObservableObject
{
[ObservableProperty] private string nombre; // genera Nombre + notificación
[ObservableProperty] private string saludo;
[RelayCommand] // genera SaludarCommand
private void Saludar() => Saludo = $"¡Hola, {Nombre}!";
}
El MVVM Toolkit de Microsoft: qué problema resuelve
MVVM funciona, pero tiene un costo escondido: escribir
INotifyPropertyChanged a mano es repetitivo y
frágil. Por cada propiedad necesitas un campo, un
getter, un setter y una llamada a
OnPropertyChanged(); por cada acción, una
propiedad ICommand y su inicialización. En un
ViewModel con 8 propiedades y 4 comandos, la mayor parte del
código es plomería, no lógica —y si olvidas un solo
OnPropertyChanged(), la UI deja de actualizarse y
el error es silencioso: no truena,
simplemente no pasa nada.
Cómo lo resuelve: el paquete
CommunityToolkit.Mvvm —el .NET Community Toolkit,
con soporte de Microsoft— usa
generadores de código (source generators). En
tiempo de compilación lee tus atributos y escribe la plomería
por ti. Tú declaras la intención; el compilador genera el
código real.
Tres reglas y ya:
-
La clase hereda de
ObservableObject (que ya
implementa INotifyPropertyChanged) y se marca
partial, para que el generador pueda agregarle
código.
-
[ObservableProperty] sobre un campo
private string nombre; genera la propiedad
pública Nombre con notificación incluida. La
regla de nombres: el campo en camelCase, la
propiedad generada en PascalCase.
-
[RelayCommand] sobre un método
Guardar() genera un
ICommand llamado GuardarCommand,
listo para enlazar en el XAML.
A mano ~22 líneas · 2 propiedades y 1 comando
public class ClienteViewModel : INotifyPropertyChanged
{
private string _nombre;
public string Nombre
{
get => _nombre;
set { _nombre = value; OnPropertyChanged(); }
}
private string _correo;
public string Correo
{
get => _correo;
set { _correo = value; OnPropertyChanged(); }
}
private ICommand _guardar;
public ICommand GuardarCommand =>
_guardar ??= new Command(Guardar);
private void Guardar() { /* ... */ }
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string n = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
}
Con el toolkit mismo comportamiento · 6 líneas
public partial class ClienteViewModel : ObservableObject
{
[ObservableProperty] private string nombre;
[ObservableProperty] private string correo;
[RelayCommand]
private void Guardar() { /* ... */ }
}
Se instala con
dotnet add package CommunityToolkit.Mvvm.
No es magia. Esto es, en esencia, lo que el generador escribe
por ti a partir de
[ObservableProperty] private string nombre;
// código generado automáticamente en tiempo de compilación
public string Nombre
{
get => nombre;
set => SetProperty(ref nombre, value); // SetProperty compara y avisa a la UI
}
Lo que además te da, cuando lo necesites
Por qué importa en móvil: menos código es
menos superficie de bugs. Y como el ciclo de vida ya te
complica bastante, quieres un ViewModel limpio y fácil de
probar —no un muro de plomería donde esconderse.