Une appli de bureau a un seul thread qui dessine l'écran. S'il attend le réseau, la fenêtre se fige et Windows affiche « Ne répond pas ». Le pattern MVVM sépare ce qu'on voit (la View) de l'état et des actions (le ViewModel), qu'on peut tester sans écran.
Choisir la technologie Confirmé
| Technologie | Plateformes | Points forts | À savoir |
|---|---|---|---|
| WinForms | Windows | Rapide pour des outils internes, designer | MVVM difficile, DPI à surveiller |
| WPF | Windows | XAML, binding puissant, très mature | Référence pour le métier Windows en .NET 10 |
| WinUI 3 (Windows App SDK) | Windows 10/11 | Fluent Design natif | Écosystème plus jeune |
| .NET MAUI | Android, iOS, Windows, macOS | Un code pour mobile et bureau | Desktop moins abouti que le mobile |
| Avalonia (tiers, open source) | Windows, Linux, macOS, web | XAML proche de WPF, vraiment multiplateforme | Hors Microsoft |
| Blazor Hybrid | Via MAUI / WPF / WinForms | Réutilise des composants web Razor | WebView embarquée |
MVVM Junior
// CommunityToolkit.Mvvm : les source generators écrivent INotifyPropertyChanged et les commandes
public sealed partial class OrdersViewModel(IOrdersApi api) : ObservableObject
{
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private string? _error;
public ObservableCollection<OrderSummary> Orders { get; } = [];
[RelayCommand(IncludeCancelCommand = true)]
private async Task LoadAsync(CancellationToken ct)
{
IsBusy = true; Error = null;
try
{
var items = await api.GetOrdersAsync(ct); // le thread UI reste libre
Orders.Clear(); // on est revenu sur le thread UI
foreach (var o in items) Orders.Add(o);
}
catch (OperationCanceledException) { }
catch (HttpRequestException ex) { Error = $"Serveur injoignable : {ex.Message}"; }
finally { IsBusy = false; }
}
}
Le thread UI et le Dispatcher
Seul le thread qui a créé un contrôle peut le modifier. Après un await lancé depuis le thread UI, le SynchronizationContext fait reprendre la suite sur ce thread : on peut toucher l'interface directement. Depuis un autre thread (timer, socket, Task.Run), passez par Dispatcher.InvokeAsync (WPF) ou Control.Invoke (WinForms).
Hôte générique et DI dans WPF
public partial class App : Application
{
private readonly IHost _host = Host.CreateDefaultBuilder()
.ConfigureServices(s =>
{
s.AddHttpClient<IOrdersApi, OrdersApi>(c => c.BaseAddress = new("https://api.shop.fr/"))
.AddStandardResilienceHandler();
s.AddTransient<OrdersViewModel>();
s.AddSingleton<MainWindow>();
}).Build();
protected override async void OnStartup(StartupEventArgs e) // async void toléré : handler d'événement
{
await _host.StartAsync();
_host.Services.GetRequiredService<MainWindow>().Show();
}
}
Déploiement Confirmé
| Mode | Mise à jour | Remarque |
|---|---|---|
| MSIX | Auto-update, Store ou intranet | Installation propre, sandbox partielle |
| ClickOnce | Auto-update simple | Supporté sur .NET moderne, idéal en intranet |
| Velopack (tiers) | Deltas, multiplateforme | Successeur moderne de Squirrel |
| Self-contained + single-file | À la charge de l'appli | Pas de runtime à installer, binaire plus gros |
.Resultdans un handler de bouton : interblocage garanti avec leSynchronizationContextUI.- Modifier une
ObservableCollectiondepuis un autre thread : exception. Revenir sur le thread UI ou utiliserBindingOperations.EnableCollectionSynchronization. - Fuites par événements : un ViewModel abonné à un service singleton n'est jamais collecté. Utiliser
WeakReferenceMessenger. - Erreurs de binding silencieuses : surveillez la fenêtre Sortie de Visual Studio (Failures de binding XAML).
À retenir
- WPF + MVVM + CommunityToolkit.Mvvm + hôte générique : socle robuste sous Windows.
- Tout appel réseau ou disque en async, le thread UI ne doit jamais attendre.
- Le client lourd consomme l'API comme n'importe quel client : résilience, auth OIDC (PKCE), gestion hors ligne.