Ontwerp een C# applicatie met MVVM-architectuur en data binding
description
Met deze prompt leer je hoe je een C# applicatie structureert volgens het MVVM-patroon, wat leidt tot betere code-organisatie, eenvoudigere testbaarheid en onderhoudbaarheid. Dit is vooral nuttig voor UI-gedreven applicaties waar duidelijke scheiding van verantwoordelijkheden essentieel is.
prompt
try_prompt
Help me een C# applicatie te ontwerpen die gebruikmaakt van de MVVM-architectuur (Model-View-ViewModel) en data binding voor een gestructureerde en onderhoudbare code. Beschrijf hoe ... more
generate_helper
...
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hier is een overzicht en voorbeeld van het ontwerpen van een C#-applicatie met MVVM-architectuur voor een takenbeheerder met realtime UI-updates.
**1. Scheiding van lagen in MVVM:**
- **Model:** Bevat de data en business logica. Bijvoorbeeld een `Task`-klasse die de gegevens van een taak beschrijft.
- **View:** De gebruikersinterface (bijvoorbeeld een WPF Window of UserControl). Deze maakt gebruik van data binding om de UI te koppelen aan de ViewModel.
- **ViewModel:** De brug tussen Model en View. Bevat properties en commands die de View bindt en zorgt voor het synchroniseren van data. Het implementeert vaak `INotifyPropertyChanged` voor realtime updates.
---
**2. Voorbeeldimplementatie:**
**a. Model**
```csharp
public class Taak : INotifyPropertyChanged
{
private string _titel;
private bool _voltooid;
public string Titel
{
get => _titel;
set
{
_titel = value;
OnPropertyChanged(nameof(Titel));
}
}
public bool Voltooid
{
get => _voltooid;
set
{
_voltooid = value;
OnPropertyChanged(nameof(Voltooid));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
**b. ViewModel**
```csharp
public class TaakBeheerViewModel : INotifyPropertyChanged
{
public ObservableCollection<Taak> Taken { get; set; }
private string _nieuweTaakTitel;
public string NieuweTaakTitel
{
get => _nieuweTaakTitel;
set
{
_nieuweTaakTitel = value;
OnPropertyChanged(nameof(NieuweTaakTitel));
}
}
public ICommand VoegTaakToeCommand { get; }
public TaakBeheerViewModel()
{
Taken = new ObservableCollection<Taak>();
VoegTaakToeCommand = new RelayCommand(VoegTaakToe, CanVoegTaakToe);
}
private void VoegTaakToe()
{
var nieuweTaak = new Taak { Titel = NieuweTaakTitel, Voltooid = false };
Taken.Add(nieuweTaak);
NieuweTaakTitel = ""; // Reset invoerveld
}
private bool CanVoegTaakToe()
{
return !string.IsNullOrWhiteSpace(NieuweTaakTitel);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
**c. RelayCommand (voor commands)**
```csharp
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute();
public void Execute(object parameter) => _execute();
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
```
**d. View (XAML voorbeeld voor WPF)**
```xml
<Window x:Class="TaakManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Taakbeheer" Height="350" Width="400">
<Window.DataContext>
<local:TaakBeheerViewModel/>
</Window.DataContext>
<StackPanel Margin="10">
<TextBox Text="{Binding NieuweTaakTitel, UpdateSourceTrigger=PropertyChanged}" PlaceholderText="Voer nieuwe taak in"/>
<Button Content="Voeg Toe" Command="{Binding VoegTaakToeCommand}" Margin="0,5,0,10"/>
<ListBox ItemsSource="{Binding Taken}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Voltooid, Mode=TwoWay}" Margin="0,0,10,0"/>
<TextBlock Text="{Binding Titel}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
```
---
**3. Realtime UI-updates:**
- Gebruik `ObservableCollection<T>` voor de lijst van taken zodat UI automatisch wordt bijgewerkt bij toevoegingen of verwijderingen.
- Implementeer `INotifyPropertyChanged` in je modellen en ViewModels zodat wijzigingen in properties direct zichtbaar zijn in de UI.
- Commands zoals `VoegTaakToeCommand` zorgen voor interactie zonder code-behind.
---
**Samenvatting:**
- Scheid je data (Model), gebruikersinterface (View) en logica/binding (ViewModel).
- Gebruik data binding en `INotifyPropertyChanged` voor realtime updates.
- Maak gebruik van `ObservableCollection` voor collecties die dynamisch worden gewijzigd.
- Commands zorgen voor interactie zonder direct event-handling in de code-behind.
Met deze architectuur wordt je applicatie gestructureerd, makkelijk te onderhouden en reageert deze direct op datawijzigingen.
provider: deepseek
model: deepseek-chat
Hier is een gestructureerde C# MVVM-implementatie voor een taakbeheerapplicatie met realtime UI-updates:
## 1. Projectstructuur
```
TaskManagerApp/
├── Models/
├── ViewModels/
├── Views/
├── Services/
└── Data/
```
## 2. Model Laag
```csharp
// Models/TaskItem.cs
public class TaskItem : INotifyPropertyChanged
{
private string _title;
private string _description;
private bool _isCompleted;
private DateTime _dueDate;
public string Title
{
get => _title;
set
{
_title = value;
OnPropertyChanged();
}
}
public string Description
{
get => _description;
set
{
_description = value;
OnPropertyChanged();
}
}
public bool IsCompleted
{
get => _isCompleted;
set
{
_isCompleted = value;
OnPropertyChanged();
OnPropertyChanged(nameof(StatusColor));
}
}
public DateTime DueDate
{
get => _dueDate;
set
{
_dueDate = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsOverdue));
}
}
public string StatusColor => IsCompleted ? "Green" : (IsOverdue ? "Red" : "Gray");
public bool IsOverdue => DueDate < DateTime.Now && !IsCompleted;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
## 3. ViewModel Laag
```csharp
// ViewModels/MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<TaskItem> _tasks;
private TaskItem _selectedTask;
private string _newTaskTitle;
private string _newTaskDescription;
private DateTime _newTaskDueDate = DateTime.Now.AddDays(1);
public MainViewModel()
{
_tasks = new ObservableCollection<TaskItem>();
// Commands initialiseren
AddTaskCommand = new RelayCommand(AddTask, CanAddTask);
DeleteTaskCommand = new RelayCommand(DeleteTask, CanDeleteTask);
ToggleCompleteCommand = new RelayCommand(ToggleComplete);
// Voorbeeld data
LoadSampleData();
}
public ObservableCollection<TaskItem> Tasks => _tasks;
public TaskItem SelectedTask
{
get => _selectedTask;
set
{
_selectedTask = value;
OnPropertyChanged();
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
public string NewTaskTitle
{
get => _newTaskTitle;
set
{
_newTaskTitle = value;
OnPropertyChanged();
AddTaskCommand.RaiseCanExecuteChanged();
}
}
public string NewTaskDescription
{
get => _newTaskDescription;
set
{
_newTaskDescription = value;
OnPropertyChanged();
}
}
public DateTime NewTaskDueDate
{
get => _newTaskDueDate;
set
{
_newTaskDueDate = value;
OnPropertyChanged();
}
}
// Commands
public RelayCommand AddTaskCommand { get; }
public RelayCommand DeleteTaskCommand { get; }
public RelayCommand ToggleCompleteCommand { get; }
private void AddTask()
{
var newTask = new TaskItem
{
Title = NewTaskTitle,
Description = NewTaskDescription,
DueDate = NewTaskDueDate,
IsCompleted = false
};
Tasks.Add(newTask);
// Velden resetten
NewTaskTitle = string.Empty;
NewTaskDescription = string.Empty;
NewTaskDueDate = DateTime.Now.AddDays(1);
}
private bool CanAddTask() => !string.IsNullOrWhiteSpace(NewTaskTitle);
private void DeleteTask()
{
if (SelectedTask != null)
{
Tasks.Remove(SelectedTask);
SelectedTask = null;
}
}
private bool CanDeleteTask() => SelectedTask != null;
private void ToggleComplete()
{
if (SelectedTask != null)
{
SelectedTask.IsCompleted = !SelectedTask.IsCompleted;
}
}
private void LoadSampleData()
{
Tasks.Add(new TaskItem { Title = "Documentatie schrijven", Description = "MVVM documentatie afmaken", DueDate = DateTime.Now.AddDays(2) });
Tasks.Add(new TaskItem { Title = "Code review", Description = "Pull request reviewen", DueDate = DateTime.Now.AddDays(-1), IsCompleted = true });
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
## 4. RelayCommand Implementatie
```csharp
// ViewModels/RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void RaiseCanExecuteChanged() => CommandManager.InvalidateRequerySuggested();
}
```
## 5. View Laag (WPF XAML)
```xml
<!-- Views/MainWindow.xaml -->
<Window x:Class="TaskManagerApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Taak Manager" Height="600" Width="800">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!-- Nieuwe taak toevoegen -->
<GroupBox Grid.Row="0" Grid.ColumnSpan="2" Header="Nieuwe Taak" Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding NewTaskTitle, UpdateSourceTrigger=PropertyChanged}"
Width="200" Margin="5" PlaceholderText="Taak titel"/>
<TextBox Text="{Binding NewTaskDescription}"
Width="300" Margin="5" PlaceholderText="Beschrijving"/>
<DatePicker SelectedDate="{Binding NewTaskDueDate}"
Margin="5" Width="120"/>
<Button Content="Toevoegen"
Command="{Binding AddTaskCommand}"
Margin="5" Width="80"/>
</StackPanel>
</GroupBox>
<!-- Taken lijst -->
<GroupBox Grid.Row="1" Grid.Column="0" Header="Taken Lijst" Margin="0,0,5,0">
<ListBox ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}"
DisplayMemberPath="Title">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="{Binding StatusColor}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="2"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</GroupBox>
<!-- Taak details -->
<GroupBox Grid.Row="1" Grid.Column="1" Header="Taak Details" Margin="5,0,0,0">
<StackPanel>
<TextBlock Text="{Binding SelectedTask.Title, StringFormat='Titel: {0}'}"
FontWeight="Bold" Margin="5"/>
<TextBlock Text="{Binding SelectedTask.Description, StringFormat='Beschrijving: {0}'}"
Margin="5"/>
<TextBlock Text="{Binding SelectedTask.DueDate, StringFormat='Deadline: {0:dd/MM/yyyy}'}"
Margin="5"/>
<CheckBox IsChecked="{Binding SelectedTask.IsCompleted}"
Content="Voltooid" Margin="5"/>
<StackPanel Orientation="Horizontal" Margin="5">
<Button Content="Verwijderen"
Command="{Binding DeleteTaskCommand}"
Margin="0,0,5,0" Width="80"/>
<Button Content="Toggle Voltooid"
Command="{Binding ToggleCompleteCommand}"
Margin="5,0,0,0" Width="100"/>
</StackPanel>
</StackPanel>
</GroupBox>
<!-- Statistieken -->
<GroupBox Grid.Row="2" Grid.ColumnSpan="2" Header="Statistieken" Margin="0,10,0,0">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Tasks.Count, StringFormat='Totaal taken: {0}'}"
Margin="10,5"/>
<TextBlock Text="{Binding Path=Tasks.Count, Converter={StaticResource CompletedTasksConverter}, StringFormat='Voltooide taken: {0}'}"
Margin="10,5"/>
<TextBlock Text="{Binding Path=Tasks.Count, Converter={StaticResource OverdueTasksConverter}, StringFormat='Te late taken: {0}'}"
Margin="10,5"/>
</StackPanel>
</GroupBox>
</Grid>
</Window>
```
## 6. Data Binding Setup
```csharp
// App.xaml.cs of MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
```
## 7. Value Converters voor statistieken
```csharp
// Converters/CompletedTasksConverter.cs
public class CompletedTasksConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<TaskItem> tasks)
{
return tasks.Count(t => t.IsCompleted);
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
```
## Belangrijkste voordelen van deze architectuur:
1. **Scheiding van concerns**: Elke laag heeft een duidelijke verantwoordelijkheid
2. **Realtime updates**: INotifyPropertyChanged en ObservableCollection zorgen voor automatische UI-refresh
3. **Testbaarheid**: ViewModels zijn eenvoudig te unit testen zonder UI-afhankelijkheden
4. **Onderhoudbaarheid**: Wijzigingen in één laag hebben minimale impact op andere lagen
5. **Herbruikbaarheid**: ViewModels kunnen met verschillende Views worden gebruikt
De applicatie biedt realtime updates door de data binding: wanneer taken worden toegevoegd, verwijderd of gewijzigd, reflecteert de UI deze wijzigingen onmiddellijk zonder handmatige refresh.

