Architecture de la fenêtre principale et navigation de base
Dans les applications WPF, l'utilisation de UserControl est une pratique fondamentale pour concevoir des interfaces modulaires. Pour basculer dynamiquement entre différentes vues au sein d'une même fenêtre sans multiplier les instances de Window, on peut exploiter un ContentPresenter (ou ContentControl) couplé au mécanisme de liaison de données.
Le layout principal est divisé en deux colonnes : un panneau latéral pour la navigation et une zone centrale pour l'affichage du contenu dynamique.
<Window x:Class="WpfNavigation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Tableau de bord" Height="500" Width="850">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Panneau de navigation -->
<Border Grid.Column="0" Background="#F0F0F0" Padding="10">
<StackPanel>
<Button Content="Module A" Click="LoadModuleA_Click" Margin="0,5" Padding="10"/>
<Button Content="Module B" Click="LoadModuleB_Click" Margin="0,5" Padding="10"/>
</StackPanel>
</Border>
<!-- Zone d'affichage dynamique -->
<Border Grid.Column="1" BorderBrush="#CCCCCC" BorderThickness="1" Margin="10">
<ContentPresenter Content="{Binding ActiveView}"/>
</Border>
</Grid>
</Window>
Le code-behind de la fenêtre principale implémente INotifyPropertyChanged pour notifier l'interface graphique lorsque la vue active change. Les instances des contrôles utilisateur sont conservées en mémoire pour préserver leur état interne.
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace WpfNavigation
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private readonly ModuleAView _moduleA = new ModuleAView();
private readonly ModuleBView _moduleB = new ModuleBView();
private object _activeView;
public MainWindow()
{
InitializeComponent();
DataContext = this;
ActiveView = _moduleA; // Vue par défaut
}
public object ActiveView
{
get => _activeView;
set
{
_activeView = value;
OnPropertyChanged();
}
}
private void LoadModuleA_Click(object sender, RoutedEventArgs e) => ActiveView = _moduleA;
private void LoadModuleB_Click(object sender, RoutedEventArgs e) => ActiveView = _moduleB;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Synchronisation d'état inter-contrôles
Un défi fréquent survient lorsque plusieurs UserControl doivent partager un état commun. Par exemple, si le Module A possède un bouton de réinitialisation globale qui doit également remettre à zéro le compteur du Module B. Plutôt que de créer des dépendances circulaires complexes, l'utilisation de propriétés statiques couplées à un événement statique de notification permet de propager les changements à travers les différentes instances liées.
Dans le Module A, nous ajoutons un bouton de réinitialisation et lions l'affichage du compteur à une propriété statique.
<UserControl x:Class="WpfNavigation.ModuleAView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Module A" FontSize="24" FontWeight="Bold" Margin="10"/>
<TextBlock Text="{Binding CounterText}" FontSize="32" HorizontalAlignment="Center" Margin="10"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Incrémenter" Click="Increment_Click" Width="100" Margin="5"/>
<Button Content="Réinitialiser Global" Click="GlobalReset_Click" Width="140" Margin="5" Background="#FFDDDD"/>
</StackPanel>
</StackPanel>
</UserControl>
Le code-behind utilise des propriétés statiques pour le compteur. Une propriété calculée (CounterText) est utilisée pour l'interface, et l'événement statique est déclenché à chaque modification pour mettre à jour tous les contrôles liés.
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace WpfNavigation
{
public partial class ModuleAView : UserControl
{
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
private static int _counter;
public static int Counter
{
get => _counter;
set
{
_counter = value;
NotifyStaticChange(nameof(Counter));
NotifyStaticChange(nameof(CounterText));
}
}
public static string CounterText => $"Valeur : {Counter}";
public ModuleAView()
{
InitializeComponent();
DataContext = this;
}
private void Increment_Click(object sender, RoutedEventArgs e)
{
Counter++;
}
private void GlobalReset_Click(object sender, RoutedEventArgs e)
{
Counter = 0;
ModuleBView.Counter = 0; // Synchronisation directe de l'état du Module B
}
private static void NotifyStaticChange(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
}
}
Le Module B adopte une structure identique. La liaison de données pour la propriété statique nécessite que le contexte de données pointe vers la classe elle-même, et l'événement statique assure que le TextBlock est rafraîchi même si la modification provient du Module A.
<UserControl x:Class="WpfNavigation.ModuleBView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="Module B" FontSize="24" FontWeight="Bold" Margin="10"/>
<TextBlock Text="{Binding CounterText}" FontSize="32" HorizontalAlignment="Center" Margin="10"/>
<Button Content="Incrémenter" Click="Increment_Click" Width="100" Margin="5"/>
</StackPanel>
</UserControl>
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace WpfNavigation
{
public partial class ModuleBView : UserControl
{
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
private static int _counter;
public static int Counter
{
get => _counter;
set
{
_counter = value;
NotifyStaticChange(nameof(Counter));
NotifyStaticChange(nameof(CounterText));
}
}
public static string CounterText => $"Valeur : {Counter}";
public ModuleBView()
{
InitializeComponent();
DataContext = this;
// Abonnement à l'événement statique du Module A pour les mises à jour croisées si nécessaire
ModuleAView.StaticPropertyChanged += OnModuleAPropertyChanged;
}
private void OnModuleAPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(CounterText))
{
NotifyStaticChange(nameof(CounterText));
}
}
private void Increment_Click(object sender, RoutedEventArgs e)
{
Counter++;
}
private static void NotifyStaticChange(string propertyName)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
}
}