C# MVVM menu & datagrid
.xaml
<Window x:Class="WPFTutoMVVM_MenuAndDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFTutoMVVM_MenuAndDataGrid"
xmlns:vm="clr-namespace:WPFTutoMVVM_MenuAndDataGrid.src.Relay"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Menu DockPanel.Dock="Top" x:Name="MainMenu" DataContext="{Binding MenuVM}">
<MenuItem Header="_Fichier">
<MenuItem Header="_Nouveau patient" x:Name="miNewPatient" Command="{Binding NewPatientCommand}" InputGestureText="Ctrl+N"/>
<MenuItem Header="_Ouvrir..." x:Name="miOpen" InputGestureText="Ctrl+O"/>
<Separator/>
<MenuItem Header="_Exporter" x:Name="miExport"/>
<MenuItem Header="_Initialiser" x:Name="miInitialize" Command="{Binding InitializeDatabaseCommand}"/>
<Separator/>
<MenuItem Header="_Quitter" x:Name="miExit" InputGestureText="Alt+F4"/>
</MenuItem>
<MenuItem Header="_Édition">
<MenuItem Header="_Copier" x:Name="miCopy" InputGestureText="Ctrl+C"/>
<MenuItem Header="_Coller" x:Name="miPaste" InputGestureText="Ctrl+V"/>
</MenuItem>
<MenuItem Header="_Patients">
<MenuItem Header="_Liste des patients" x:Name="miListPatients"/>
<MenuItem Header="_Rechercher..." x:Name="miSearchPatients" InputGestureText="Ctrl+F"/>
</MenuItem>
<MenuItem Header="_Aide">
<MenuItem Header="_Documentation" x:Name="miDocs"/>
<MenuItem Header="À _propos" x:Name="miAbout"/>
</MenuItem>
</Menu>
<DataGrid ItemsSource="{Binding PatientVM.Patients}"
SelectedItem="{Binding PatientVM.SelectedPatient, Mode=TwoWay}"
AutoGenerateColumns="False"
IsReadOnly="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
Margin="20,30,0,8"
SelectionMode="Single"
EnableRowVirtualization="True"
EnableColumnVirtualization="True" Width="600" Height="300" MaxHeight="300" VerticalAlignment="Top" HorizontalAlignment="Left">
<DataGrid.Columns>
<DataGridTextColumn Header="Nom" Binding="{Binding LastName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Width="*" MinWidth="150" />
<DataGridTextColumn Header="Prénom" Binding="{Binding FirstName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" Width="*" MinWidth="150"/>
<DataGridTextColumn Header="Naissance"
Binding="{Binding BirthDate, StringFormat=\{0:yyyy-MM-dd\}, ValidatesOnDataErrors=True}"
Width="140"/>
<DataGridCheckBoxColumn Header="Actif" Binding="{Binding Active}" Width="80"/>
<DataGridTemplateColumn Header="Actions" Width="160">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button Content="Voir"
Command="{Binding DataContext.PatientVM.ViewCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}"/>
<Button Content="Supprimer"
Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
MainViewModel.cs
internal class MainViewModel
{
public MenuViewModel MenuVM { get; } = new();
public DataGridViewModel PatientVM { get; } = new();
}
MenuViewModel.cs
internal class MenuViewModel
{
public ICommand NewPatientCommand { get; }
public ICommand InitializeDatabaseCommand { get; }
public MenuViewModel()
{
NewPatientCommand = new RelayCommand(_ => NewPatient(), _ => true);
InitializeDatabaseCommand = new RelayCommand(InitializeDatabase, _ => true);
}
private void NewPatient()
{
// MessageBox.Show("Nouveau patient");
// var window = new CreatePractitioner.CreatePractitionerWindow();
// window.ShowDialog();
}
private void InitializeDatabase(object? choice)
{
// MedicalManagementDatabaseInit.Initialize();
MessageBox.Show("Initialisation de la base de données OK", "Info");
}
}
DataGridViewModel.cs
internal class DataGridViewModel : INotifyPropertyChanged
{
public ObservableCollection<PatientDto> Patients { get; } = new();
private readonly ICollectionView _patientsView;
public ICollectionView PatientsView => _patientsView;
private PatientDto? _selectedPatient;
public PatientDto? SelectedPatient
{
get => _selectedPatient;
set
{
if (_selectedPatient != value)
{
_selectedPatient = value;
OnPropertyChanged(nameof(SelectedPatient));
DeleteCommandAsCommand?.RaiseCanExecuteChanged();
ViewCommandAsCommand?.RaiseCanExecuteChanged();
Console.WriteLine($"SelectedPatient changed: {SelectedPatient?.FirstName}");
}
}
}
// Champ de filtre
private string _filterText = string.Empty;
public string FilterText
{
get => _filterText;
set
{
if (_filterText != value)
{
_filterText = value;
OnPropertyChanged(nameof(FilterText));
_patientsView.Refresh();
}
}
}
// Commands
public ICommand LoadCommand { get; }
public ICommand LoadAsyncCommand { get; }
public ICommand AddCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand ViewCommand { get; }
// Pour RaiseCanExecuteChanged pratique
private RelayCommand? DeleteCommandAsCommand => DeleteCommand as RelayCommand;
private RelayCommand? ViewCommandAsCommand => ViewCommand as RelayCommand;
public DataGridViewModel()
{
// Préparer la vue (tri + filtre)
_patientsView = CollectionViewSource.GetDefaultView(Patients);
_patientsView.Filter = FilterPredicate;
_patientsView.SortDescriptions.Add(new SortDescription(nameof(PatientDto.FirstName), ListSortDirection.Ascending));
// Commands
LoadCommand = new RelayCommand(_ => Load());
ViewCommand = new RelayCommand(_ => View(), _ => SelectedPatient is not null);
// Données de démonstration au démarrage
// _ = SeedDemoDataAsync();
Console.WriteLine("PatientViewModel: initializing data...");
_ = InitializeAsync();
Console.WriteLine("PatientViewModel: initializing data quit");
}
private bool FilterPredicate(object obj)
{
if (obj is not PatientDto p) return false;
if (string.IsNullOrWhiteSpace(FilterText)) return true;
return p.FirstName.Contains(FilterText, StringComparison.CurrentCultureIgnoreCase)
/*|| p.Id.ToString().Contains(FilterText, StringComparison.CurrentCultureIgnoreCase)*/;
}
private async Task SeedDemoDataAsync()
{
for (var i = 0; i < 100; i++)
{
var firstname = $"firstname {(i + 1):D4}";
var lastname = $"lastname {(i + 1):D4}";
var birthdate = new DateTime(1990, 6, 1 + i % 12);
Patients.Add(new PatientDto()
{
FirstName = firstname,
LastName = lastname,
BirthDate = birthdate
}
);
}
}
private async Task InitializeAsync()
{
// Laisse l'UI respirer
await Task.Yield();
try
{
await SeedDemoDataAsync();
}
catch (Exception ex)
{
// TODO: log/afficher l'erreur
// _logger.LogError(ex, "Erreur pendant SeedDemoDataAsync");
}
}
// Chargement sync (ex. depuis une source)
private void Load()
{
Patients.Clear();
foreach (var p in Enumerable.Range(1, 5).Select(i =>
new PatientDto { FirstName = $"Patient {i}" }))
{
Patients.Add(p);
}
}
private void View()
{
// Dans MVVM pur, on lèverait un Event ou on utiliserait un service de navigation.
// Ici, le code-behind (MainWindow) peut écouter une notification via event ou Message.
// OnRequestInfo?.Invoke(this, $"Dossier de: {SelectedPatient?.FirstName})";// (ID {SelectedPatient?.Id})";
Console.WriteLine($"View patient: {SelectedPatient?.FirstName}");
}
// Événement pour que la vue affiche des infos (MessageBox) sans casser MVVM
public event EventHandler<string>? OnRequestInfo;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string? prop = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
Relay Command
internal class RelayCommand : ICommand
{
private readonly Action<object?> _execute;
private readonly Func<object?, bool>? _canExecute;
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => _canExecute?.Invoke(parameter) ?? true;
public void Execute(object? parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}