# WPF MVVM Tutorial - Stap voor Stap Handleiding
## Inhoudsopgave
1. [Introductie](#introductie)
2. [Vereisten](#vereisten)
3. [Wat is MVVM?](#wat-is-mvvm)
4. [Stap 1: Project Opzetten](#stap-1-project-opzetten)
5. [Stap 2: Projectstructuur Aanmaken](#stap-2-projectstructuur-aanmaken)
6. [Stap 3: Het Model Bouwen](#stap-3-het-model-bouwen)
7. [Stap 4: RelayCommand Implementeren](#stap-4-relaycommand-implementeren)
8. [Stap 5: Het ViewModel Bouwen](#stap-5-het-viewmodel-bouwen)
9. [Stap 6: De View Bouwen](#stap-6-de-view-bouwen)
10. [Stap 7: Testen en Uitvoeren](#stap-7-testen-en-uitvoeren)
11. [Concepten Uitgelegd](#concepten-uitgelegd)
12. [Veelgemaakte Fouten](#veelgemaakte-fouten)
---
## Introductie
Deze handleiding leidt je stap voor stap door het bouwen van een WPF-applicatie met het MVVM (Model-View-ViewModel) ontwerppatroon. Je bouwt een eenvoudige productenbeheer applicatie waarbij je leert over:
- Data Binding
- Observable Collections
- Property Change Notifications
- ICommand Pattern
- Separation of Concerns
**Eindresultaat**: Een werkende applicatie waar je producten kunt bekijken, selecteren en toevoegen met automatische UI-updates.
---
## Vereisten
### Software
- Visual Studio 2022 (Community, Professional of Enterprise)
- .NET 8.0 SDK
### Kennis
- Basis kennis van C#
- Object-georiënteerd programmeren
- Basiskennis van XAML (niet vereist maar handig)
---
## Wat is MVVM?
MVVM staat voor **Model-View-ViewModel** en is een ontwerppatroon dat helpt bij het scheiden van:
- **Model**: De data en business logica
- **View**: De gebruikersinterface (XAML)
- **ViewModel**: De tussenpersoon die data voorbereidt voor de View
### Voordelen van MVVM:
- **Scheiding van zorgen**: UI-code gescheiden van business logica
- **Testbaarheid**: ViewModels kunnen eenvoudig worden getest zonder UI
- **Herbruikbaarheid**: Models en ViewModels zijn herbruikbaar
- **Data Binding**: Automatische synchronisatie tussen UI en data
### Diagram:
```
View (XAML) <------ Data Binding ------> ViewModel
|
|
Model
```
---
## Stap 1: Project Opzetten
### 1.1 Nieuw Project Aanmaken
1. Open Visual Studio 2022
2. Klik op **"Create a new project"**
3. Zoek naar **"WPF Application"** (niet WPF App (.NET Framework)!)
4. Selecteer **"WPF Application"** en klik **Next**
### 1.2 Project Configureren
1. **Project name**: `MVVM_DEMO`
2. **Location**: Kies een geschikte locatie
3. **Solution name**: `MVVM_DEMO`
4. Klik **Next**
### 1.3 Framework Selecteren
1. Selecteer **.NET 8.0** als framework
2. Klik **Create**
### 1.4 Wat Krijg Je?
Visual Studio creëert automatisch:
- `App.xaml` en `App.xaml.cs` - De applicatie startpunt
- `MainWindow.xaml` en `MainWindow.xaml.cs` - Het hoofdvenster
- `MVVM_DEMO.csproj` - Project configuratie
---
## Stap 2: Projectstructuur Aanmaken
Een goede mappenstructuur is essentieel voor overzichtelijke MVVM-applicaties.
### 2.1 Mappen Aanmaken
In de **Solution Explorer**:
1. **Rechtsklik** op het project `MVVM_DEMO`
2. Selecteer **Add > New Folder**
3. Maak de volgende mappen aan:
- `Models`
- `ViewModels`
- `Views`
- `Commands`
- `ValueConverters` (voorlopig leeg, voor toekomstige uitbreidingen)
### 2.2 MainWindow.xaml Verplaatsen
1. **Sleep** `MainWindow.xaml` naar de `Views` map
2. Visual Studio vraagt of je namespace references wilt updaten - klik **Yes**
### 2.3 Projectstructuur Controleren
Je projectstructuur zou er nu zo uit moeten zien:
```
MVVM_DEMO/
├── Commands/
├── Models/
├── ViewModels/
├── Views/
│ ├── MainWindow.xaml
│ └── MainWindow.xaml.cs
├── ValueConverters/
├── App.xaml
└── MVVM_DEMO.csproj
```
---
## Stap 3: Het Model Bouwen
Het **Model** bevat de data structuur. We gaan een `Product` class maken.
### 3.1 Product Class Aanmaken
1. **Rechtsklik** op de `Models` map
2. Selecteer **Add > Class...**
3. Naam: `Product.cs`
4. Klik **Add**
### 3.2 Product Class Implementeren
Open `Product.cs` en vervang de inhoud met:
```csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MVVM_DEMO.Models
{
public class Product
{
private string _productName;
private decimal _productPrice;
public string ProductName
{
get => _productName;
set
{
_productName = value;
OnPropertyChanged();
}
}
public decimal ProductPrice
{
get => _productPrice;
set
{
_productPrice = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
```
### 3.3 Code Uitleg
#### Private Fields
```csharp
private string _productName;
private decimal _productPrice;
```
- **Backing fields** voor de properties
- Conventie: underscore prefix voor private fields
#### Properties met OnPropertyChanged
```csharp
public string ProductName
{
get => _productName;
set
{
_productName = value;
OnPropertyChanged();
}
}
```
- **get**: Retourneert de waarde van het private field
- **set**: Zet de nieuwe waarde EN roept `OnPropertyChanged()` aan
- Dit zorgt ervoor dat de UI wordt genotificeerd bij wijzigingen
#### INotifyPropertyChanged Event
```csharp
public event PropertyChangedEventHandler PropertyChanged;
```
- Event dat wordt afgevuurd wanneer een property verandert
- De UI kan zich abonneren op dit event
#### OnPropertyChanged Method
```csharp
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
```
- **[CallerMemberName]**: Automatisch de naam van de aanroepende property
- **PropertyChanged?.Invoke**: Veilig aanroepen (alleen als er listeners zijn)
- **PropertyChangedEventArgs**: Bevat de naam van de gewijzigde property
### 3.4 Waarom INotifyPropertyChanged?
Zonder `INotifyPropertyChanged` weet de UI niet wanneer data verandert. Met dit pattern:
1. Property wordt gewijzigd
2. `OnPropertyChanged()` wordt aangeroepen
3. Event wordt afgevuurd
4. UI luistert naar het event
5. UI update zichzelf automatisch
---
## Stap 4: RelayCommand Implementeren
**Commands** zijn de MVVM-manier om button clicks en andere UI-acties af te handelen zonder code-behind.
### 4.1 RelayCommand Class Aanmaken
1. **Rechtsklik** op de `Commands` map
2. Selecteer **Add > Class...**
3. Naam: `RelayCommand.cs`
4. Klik **Add**
### 4.2 RelayCommand Implementeren
Open `RelayCommand.cs` en vervang de inhoud met:
```csharp
using System;
using System.Windows.Input;
namespace MVVM_DEMO.Commands
{
///
/// A command whose sole purpose is to relay its functionality to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
///
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func? _canExecute;
///
/// Occurs when changes occur that affect whether or not the command should execute.
///
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
///
/// Creates a new command that can always execute.
///
/// The execution logic.
public RelayCommand(Action execute) : this(execute, null)
{
}
///
/// Creates a new command.
///
/// The execution logic.
/// The execution status logic.
public RelayCommand(Action execute, Func? canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
///
/// Determines whether this command can execute in its current state.
///
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// true if this command can be executed; otherwise, false.
public bool CanExecute(object? parameter)
{
return _canExecute == null || _canExecute(parameter);
}
///
/// Executes the command.
///
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
public void Execute(object? parameter)
{
_execute(parameter);
}
}
}
```
### 4.3 Code Uitleg
#### ICommand Interface
```csharp
public class RelayCommand : ICommand
```
- **ICommand**: WPF interface voor commando's
- Vereist: `Execute()`, `CanExecute()`, en `CanExecuteChanged` event
#### Private Fields
```csharp
private readonly Action _execute;
private readonly Func? _canExecute;
```
- **_execute**: De method die wordt uitgevoerd wanneer het commando wordt aangeroepen
- **_canExecute**: Optionele method die bepaalt of het commando kan worden uitgevoerd
- **Action**: Delegate voor een method zonder return waarde
- **Func**: Delegate voor een method die een boolean retourneert
#### CanExecuteChanged Event
```csharp
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
```
- Koppelt aan WPF's `CommandManager`
- WPF controleert automatisch of commando's kunnen worden uitgevoerd
- Buttons worden automatisch disabled als `CanExecute` false retourneert
#### Constructors
```csharp
public RelayCommand(Action execute) : this(execute, null)
{
}
public RelayCommand(Action execute, Func? canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
```
- **Eerste constructor**: Voor commando's die altijd kunnen worden uitgevoerd
- **Tweede constructor**: Voor commando's met validatie logica
- **Null check**: Gooit exception als `execute` null is
#### Execute Method
```csharp
public void Execute(object? parameter)
{
_execute(parameter);
}
```
- Roept de `_execute` delegate aan
- Wordt aangeroepen wanneer de gebruiker de actie triggert (bijv. button click)
#### CanExecute Method
```csharp
public bool CanExecute(object? parameter)
{
return _canExecute == null || _canExecute(parameter);
}
```
- Als `_canExecute` null is: altijd true (commando kan altijd)
- Anders: roep `_canExecute` aan en retourneer het resultaat
### 4.4 Waarom RelayCommand?
Zonder commando's zou je code-behind nodig hebben:
```csharp
// SLECHT: Code-behind (niet MVVM)
private void Button_Click(object sender, RoutedEventArgs e)
{
// Logic hier
}
```
Met RelayCommand:
```csharp
// GOED: MVVM met Commands
public ICommand AddProductCommand { get; set; }
AddProductCommand = new RelayCommand(ExecuteAddProduct, CanExecuteAddProduct);
```
**Voordelen**:
- Testbaar (zonder UI)
- Herbruikbaar
- Separation of concerns
- Automatische enable/disable logica
---
## Stap 5: Het ViewModel Bouwen
Het **ViewModel** is het hart van MVVM - het verbindt de data (Model) met de UI (View).
### 5.1 MainViewModel Class Aanmaken
1. **Rechtsklik** op de `ViewModels` map
2. Selecteer **Add > Class...**
3. Naam: `MainViewModel.cs`
4. Klik **Add**
### 5.2 MainViewModel Implementeren
Open `MainViewModel.cs` en vervang de inhoud met:
```csharp
using MVVM_DEMO.Commands;
using MVVM_DEMO.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MVVM_DEMO.ViewModels
{
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection _products;
private Product _selectedProduct;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// constructor
public MainViewModel()
{
_products = new ObservableCollection();
LoadData();
// Initialize commands
AddProductCommand = new RelayCommand(ExecuteAddProduct, CanExecuteAddProduct);
}
// read data
private void LoadData()
{
_products.Add(new Product { ProductName = "Product 1",ProductPrice = 10 });
_products.Add(new Product { ProductName = "Product 2", ProductPrice = 20 });
_products.Add(new Product { ProductName = "Product 3", ProductPrice = 30 });
Products = _products;
}
// properties
public ObservableCollection Products
{
get => _products;
set
{
_products = value;
OnPropertyChanged();
}
}
public Product SelectedProduct
{
get => _selectedProduct;
set
{
_selectedProduct = value;
OnPropertyChanged();
}
}
// Commands
public ICommand AddProductCommand { get; set; }
// Command methods
private void ExecuteAddProduct(object? parameter)
{
Random random = new Random();
int randomPrice = random.Next(10, 100);
Products.Add(new Product
{
ProductName = $"Product {Products.Count + 1}",
ProductPrice = randomPrice
});
}
private bool CanExecuteAddProduct(object? parameter)
{
// You can add validation logic here
// For now, always allow adding products
return true;
}
}
}
```
### 5.3 Code Uitleg - Deel 1: Fields en INotifyPropertyChanged
#### Private Fields
```csharp
private ObservableCollection _products;
private Product _selectedProduct;
```
- **_products**: Backing field voor de productenlijst
- **_selectedProduct**: Backing field voor het geselecteerde product
#### INotifyPropertyChanged
```csharp
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
```
- Zelfde pattern als in het Model
- Notificeert de UI over property wijzigingen
### 5.4 Code Uitleg - Deel 2: Constructor en Data Loading
#### Constructor
```csharp
public MainViewModel()
{
_products = new ObservableCollection();
LoadData();
// Initialize commands
AddProductCommand = new RelayCommand(ExecuteAddProduct, CanExecuteAddProduct);
}
```
- Initialiseert de `ObservableCollection`
- Laadt initiële data via `LoadData()`
- Maakt het `AddProductCommand` aan met execute en canExecute methods
#### LoadData Method
```csharp
private void LoadData()
{
_products.Add(new Product { ProductName = "Product 1", ProductPrice = 10 });
_products.Add(new Product { ProductName = "Product 2", ProductPrice = 20 });
_products.Add(new Product { ProductName = "Product 3", ProductPrice = 30 });
Products = _products;
}
```
- Voegt 3 testproducten toe
- In een echte applicatie zou dit data uit een database kunnen halen
- **Object initializer syntax**: `new Product { PropertyName = value }`
### 5.5 Code Uitleg - Deel 3: Properties
#### Products Property
```csharp
public ObservableCollection Products
{
get => _products;
set
{
_products = value;
OnPropertyChanged();
}
}
```
- **ObservableCollection**: Speciale collectie die automatisch de UI notificeert bij Add/Remove
- Publieke property voor data binding vanuit XAML
#### SelectedProduct Property
```csharp
public Product SelectedProduct
{
get => _selectedProduct;
set
{
_selectedProduct = value;
OnPropertyChanged();
}
}
```
- Houdt bij welk product momenteel geselecteerd is
- Bij wijziging wordt de UI automatisch geüpdatet
### 5.6 Code Uitleg - Deel 4: Command Implementation
#### Command Property
```csharp
public ICommand AddProductCommand { get; set; }
```
- **ICommand**: Interface voor commando's in WPF
- Kan gebonden worden aan buttons in XAML
#### ExecuteAddProduct Method
```csharp
private void ExecuteAddProduct(object? parameter)
{
Random random = new Random();
int randomPrice = random.Next(10, 100);
Products.Add(new Product
{
ProductName = $"Product {Products.Count + 1}",
ProductPrice = randomPrice
});
}
```
- **Wordt aangeroepen** wanneer de button wordt geklikt
- **Random prijs** tussen 10 en 100
- **String interpolation**: `$"Product {Products.Count + 1}"`
- **ObservableCollection.Add**: Automatische UI update
#### CanExecuteAddProduct Method
```csharp
private bool CanExecuteAddProduct(object? parameter)
{
return true;
}
```
- Bepaalt of het commando kan worden uitgevoerd
- `true`: button is enabled
- `false`: button is disabled
- Hier altijd `true`, maar je zou validatie kunnen toevoegen
**Voorbeeld met validatie**:
```csharp
private bool CanExecuteAddProduct(object? parameter)
{
return Products.Count < 10; // Max 10 producten
}
```
### 5.7 Waarom ObservableCollection?
Vergelijk met een normale List:
```csharp
// SLECHT: Normale List
List products = new List();
products.Add(new Product()); // UI wordt NIET geüpdatet!
// GOED: ObservableCollection
ObservableCollection products = new ObservableCollection();
products.Add(new Product()); // UI wordt WEL geüpdatet!
```
**ObservableCollection** implementeert `INotifyCollectionChanged` en update de UI automatisch bij:
- Add
- Remove
- Clear
- Replace
---
## Stap 6: De View Bouwen
De **View** is de gebruikersinterface in XAML. We gaan `MainWindow.xaml` aanpassen.
### 6.1 MainWindow.xaml Aanpassen
Open `Views/MainWindow.xaml` en vervang de inhoud met:
```xml
```
### 6.2 XAML Code Uitleg - Deel 1: Namespaces en Window
#### Namespaces
```xml
xmlns:vm="clr-namespace:MVVM_DEMO.ViewModels"
```
- **xmlns**: XML namespace declaratie
- **vm**: Prefix voor ons ViewModel namespace
- **clr-namespace**: .NET namespace
- Nu kunnen we `vm:MainViewModel` gebruiken in XAML
#### DataContext
```xml
```
- **DataContext**: De data bron voor alle bindings in deze Window
- Maakt een nieuwe instantie van `MainViewModel` aan
- Alle child controls erven deze DataContext
**Alternatief (in code-behind)**:
```csharp
// In MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
```
### 6.3 XAML Code Uitleg - Deel 2: Layout
#### Grid en StackPanel
```xml
```
- **Grid**: Basis layout container
- **StackPanel**: Stapelt child controls verticaal (standaard)
### 6.4 XAML Code Uitleg - Deel 3: ComboBox
```xml
```
#### Properties Uitleg:
- **x:Name**: Identificatie naam (voor code-behind toegang)
- **Width/Height**: Afmetingen in pixels
- **Margin**: Ruimte rondom (hier: 10 pixels aan alle kanten)
- **VerticalAlignment**: Verticale uitlijning
#### Data Binding Properties:
- **ItemsSource**: `{Binding Products}`
- Bindt aan de `Products` property in het ViewModel
- Toont alle producten in de lijst
- **SelectedItem**: `{Binding SelectedProduct, Mode=TwoWay}`
- Bindt aan de `SelectedProduct` property
- **Mode=TwoWay**: Wijzigingen gaan beide kanten op
- View → ViewModel: Gebruiker selecteert een product
- ViewModel → View: Code wijzigt SelectedProduct
- **DisplayMemberPath**: `"ProductName"`
- Welke property van Product wordt getoond in de ComboBox
- Toont de naam, niet het hele object
#### Binding Modes Uitgelegd:
```
OneWay: ViewModel → View (default voor meeste properties)
TwoWay: ViewModel ↔ View (default voor user input controls)
OneTime: ViewModel → View (alleen bij initialisatie)
OneWayToSource: View → ViewModel
```
### 6.5 XAML Code Uitleg - Deel 4: Button
```xml
```
#### Properties Uitleg:
- **Content**: De tekst op de button
- **Command**: `{Binding AddProductCommand}`
- Bindt aan het `AddProductCommand` in het ViewModel
- Geen `Click` event in code-behind nodig!
- Bij klik wordt `Execute` van het commando aangeroepen
**Verschil met code-behind**:
```xml
```
### 6.6 XAML Code Uitleg - Deel 5: TextBoxes
#### Product Naam TextBox
```xml
```
#### Binding Uitleg:
- **Text**: `{Binding SelectedProduct.ProductName}`
- **Nested binding**: Bindt aan een property van een property
- `SelectedProduct` is een property van MainViewModel
- `ProductName` is een property van het Product object
- **UpdateSourceTrigger=PropertyChanged**
- **PropertyChanged**: Update bij elke toetsaanslag
- Default zou zijn: Update bij focus verlies
- Geeft real-time updates
#### UpdateSourceTrigger Opties:
```
PropertyChanged: Bij elke wijziging (real-time)
LostFocus: Bij verlies van focus (default)
Explicit: Alleen bij expliciete aanroep
```
#### Product Prijs TextBox
```xml
```
- Zelfde concept als ProductName
- Bindt aan `ProductPrice` property
- Automatische conversie van `decimal` naar `string` en vice versa
### 6.7 MainWindow.xaml.cs Aanpassen
Open `Views/MainWindow.xaml.cs` en vervang de inhoud met:
```csharp
using MVVM_DEMO.Models;
using MVVM_DEMO.ViewModels;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace MVVM_DEMO
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// ViewModel is already set in XAML via Window.DataContext
comboBox.SelectionChanged += ComboBox_SelectionChanged;
// initial selection
if (comboBox.Items.Count > 0)
{
comboBox.SelectedIndex = 0;
}
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// display selected product details
if (comboBox.SelectedItem != null && DataContext is MainViewModel viewModel)
{
/*viewModel.productName = ((Product)comboBox.SelectedItem).ProductName;
viewModel.productPrice = (int)((Product)comboBox.SelectedItem).ProductPrice;
viewModel.OnPropertyChanged(nameof(viewModel.productName));
viewModel.OnPropertyChanged(nameof(viewModel.productPrice));*/
}
}
// btnAddProduct_Click removed - now using Command binding in XAML
}
}
```
#### Code-behind Uitleg:
- **Minimale code-behind**: Dit is het doel van MVVM!
- **SelectionChanged event**: Zorgt voor initiële selectie
- **Commented code**: Oude manier (niet meer nodig dankzij binding)
- **Opmerking over Command**: Button gebruikt nu Command binding
**Belangrijk**: In pure MVVM zou zelfs de `SelectionChanged` event handler vermeden worden, maar voor beginners is dit een acceptabele compromise.
---
## Stap 7: Testen en Uitvoeren
### 7.1 Build het Project
1. Druk op **Ctrl + Shift + B** of
2. Menu: **Build > Build Solution**
3. Controleer de **Output** window voor errors
### 7.2 Project Uitvoeren
1. Druk op **F5** of
2. Klik op de groene **Start** button
3. De applicatie zou moeten opstarten
### 7.3 Functionaliteit Testen
#### Test 1: Producten Bekijken
1. Open de ComboBox dropdown
2. Je zou 3 producten moeten zien:
- Product 1
- Product 2
- Product 3
#### Test 2: Product Selecteren
1. Selecteer een product uit de ComboBox
2. De TextBoxes zouden automatisch moeten updaten met:
- Product naam
- Product prijs
#### Test 3: Product Toevoegen
1. Klik op de **"Add Product"** button
2. Een nieuw product wordt toegevoegd aan de lijst
3. Het nieuwe product verschijnt in de ComboBox
4. De naam is "Product 4" (of hoger)
5. De prijs is willekeurig tussen 10 en 100
#### Test 4: Live Editing
1. Selecteer een product
2. Typ in de **Product Name** TextBox
3. Wijzig de naam (bijv. "Laptop")
4. Open de ComboBox weer
5. De naam zou onmiddellijk geüpdatet moeten zijn!
#### Test 5: Prijs Editing
1. Selecteer een product
2. Wijzig de prijs in de **Product Price** TextBox
3. Selecteer een ander product en terug
4. De prijs zou behouden moeten blijven
### 7.4 Troubleshooting
#### Probleem: Applicatie start niet
- **Oplossing**: Controleer of er build errors zijn
- Check: `Error List` window (View > Error List)
#### Probleem: ComboBox is leeg
- **Oplossing**:
- Controleer of `LoadData()` wordt aangeroepen in constructor
- Controleer `ItemsSource` binding in XAML
- Zet een breakpoint in `LoadData()` method
#### Probleem: TextBoxes updaten niet
- **Oplossing**:
- Controleer `SelectedProduct` binding
- Controleer of `OnPropertyChanged()` wordt aangeroepen
- Controleer `UpdateSourceTrigger=PropertyChanged`
#### Probleem: Button doet niets
- **Oplossing**:
- Controleer `Command` binding
- Zet breakpoint in `ExecuteAddProduct` method
- Controleer of `AddProductCommand` wordt geïnitialiseerd
---
## Concepten Uitgelegd
### 1. Data Binding
**Wat is het?**
Data Binding is het automatisch synchroniseren van data tussen View en ViewModel.
**Syntax**:
```xml
```
**Hoe werkt het?**
1. WPF maakt een binding object
2. Binding abonneert zich op `PropertyChanged` event
3. Bij wijziging update WPF de UI automatisch
4. Bij user input update WPF het ViewModel (TwoWay)
**Binding Path**:
```xml
```
### 2. INotifyPropertyChanged
**Waarom nodig?**
WPF moet weten wanneer data verandert om de UI te updaten.
**Implementatie Pattern**:
```csharp
// 1. Implementeer interface
public class MyClass : INotifyPropertyChanged
{
// 2. Declareer event
public event PropertyChangedEventHandler PropertyChanged;
// 3. Helper method
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// 4. Gebruik in properties
private string _myProperty;
public string MyProperty
{
get => _myProperty;
set
{
_myProperty = value;
OnPropertyChanged(); // Automatisch "MyProperty" als naam
}
}
}
```
**CallerMemberName Attribute**:
```csharp
// Zonder CallerMemberName
OnPropertyChanged("MyProperty"); // Foutgevoelig!
// Met CallerMemberName
OnPropertyChanged(); // Automatisch de juiste naam
```
### 3. ObservableCollection
**Verschil met List**:
```csharp
// List
List products = new List();
products.Add(new Product()); // UI update: ❌
// ObservableCollection
ObservableCollection products = new ObservableCollection();
products.Add(new Product()); // UI update: ✅
```
**Events**:
- `CollectionChanged`: Fired bij Add, Remove, Clear, Replace
- Automatisch gedetecteerd door WPF bindings
**Wanneer gebruiken?**:
- Voor collections die gebonden zijn aan de UI
- Niet voor interne data (gebruik List)
- Niet voor read-only data (gebruik IEnumerable)
### 4. ICommand Pattern
**Waarom Commands?**
- Scheiding van UI en logica
- Testbaar zonder UI
- Automatische enable/disable logica
- Herbruikbaar
**ICommand Interface**:
```csharp
public interface ICommand
{
event EventHandler CanExecuteChanged;
bool CanExecute(object parameter);
void Execute(object parameter);
}
```
**RelayCommand Gebruik**:
```csharp
// In ViewModel
public ICommand MyCommand { get; set; }
// Constructor
MyCommand = new RelayCommand(ExecuteMethod, CanExecuteMethod);
// Execute method
private void ExecuteMethod(object? parameter)
{
// Doe iets
}
// CanExecute method
private bool CanExecuteMethod(object? parameter)
{
return true; // of false om button te disablen
}
```
**XAML Binding**:
```xml
```
### 5. DataContext
**Wat is het?**
De data bron voor alle bindings in een control en zijn children.
**Inheritance**:
```xml
```
**Manieren om te zetten**:
```xml
DataContext = new MainViewModel();
```
### 6. Binding Modes
**OneWay** (default voor de meeste properties):
```
ViewModel → View
```
```xml
```
- View update bij ViewModel wijziging
- ViewModel update NIET bij View wijziging
**TwoWay** (default voor input controls):
```
ViewModel ↔ View
```
```xml
```
- Wijzigingen gaan beide kanten op
- Gebruikelijk voor user input
**OneTime**:
```
ViewModel → View (only once)
```
- Alleen bij initialisatie
- Geen updates daarna
- Performance voordeel
**OneWayToSource**:
```
View → ViewModel
```
- Omgekeerde van OneWay
- Zeldzaam gebruikt
### 7. UpdateSourceTrigger
Bepaalt **wanneer** de binding wordt geüpdatet.
**PropertyChanged**:
```xml
```
- Bij elke toetsaanslag
- Real-time updates
- Hogere load
**LostFocus** (default):
```xml
```
- Bij verlies van focus
- Minder updates
- Betere performance
**Explicit**:
```xml
```
- Alleen bij expliciete aanroep
```csharp
BindingExpression binding = txtBox.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
```
---
## Veelgemaakte Fouten
### Fout 1: Vergeten INotifyPropertyChanged te implementeren
**Symptoom**: UI update niet bij data wijziging
**Fout Code**:
```csharp
public class Product
{
public string Name { get; set; } // ❌
}
```
**Correcte Code**:
```csharp
public class Product : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(); // ✅
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
### Fout 2: List gebruiken in plaats van ObservableCollection
**Symptoom**: UI update niet bij Add/Remove
**Fout Code**:
```csharp
public List Products { get; set; } // ❌
```
**Correcte Code**:
```csharp
public ObservableCollection Products { get; set; } // ✅
```
### Fout 3: Binding Mode vergeten bij TwoWay binding
**Symptoom**: Wijzigingen in View komen niet in ViewModel
**Fout Code**:
```xml
```
**Correcte Code**:
```xml
```
**Note**: Voor de meeste input controls (TextBox, ComboBox, CheckBox) is TwoWay de default. Maar het is beter om expliciet te zijn!
### Fout 4: OnPropertyChanged vergeten aan te roepen
**Symptoom**: UI update niet
**Fout Code**:
```csharp
public string Name
{
get => _name;
set
{
_name = value; // ❌ Geen OnPropertyChanged
}
}
```
**Correcte Code**:
```csharp
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(); // ✅
}
}
```
### Fout 5: DataContext niet zetten
**Symptoom**: Bindings werken niet, data is null
**Fout Code**:
```xml
```
**Correcte Code**:
```xml
```
### Fout 6: Command niet initialiseren
**Symptoom**: Button doet niets bij klik
**Fout Code**:
```csharp
public ICommand MyCommand { get; set; } // ❌ null!
public MainViewModel()
{
// Vergeten te initialiseren
}
```
**Correcte Code**:
```csharp
public ICommand MyCommand { get; set; }
public MainViewModel()
{
MyCommand = new RelayCommand(ExecuteMethod); // ✅
}
```
### Fout 7: Property naam typo in CallerMemberName
**Symptoom**: Verkeerde property wordt geüpdatet
**Fout Code**:
```csharp
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Naam"); // ❌ Verkeerde naam!
}
}
```
**Correcte Code**:
```csharp
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged(); // ✅ Automatisch juiste naam
}
}
```
### Fout 8: Namespace vergeten in XAML
**Symptoom**: ViewModel niet gevonden
**Fout Code**:
```xml
```
**Correcte Code**:
```xml
```
### Fout 9: UpdateSourceTrigger vergeten voor real-time updates
**Symptoom**: TextBox update pas bij focus verlies
**Fout Code**:
```xml
```
**Correcte Code (voor real-time)**:
```xml
```
### Fout 10: Null reference bij nested binding
**Symptoom**: Crash bij opstarten of null reference exception
**Fout Code**:
```xml
```
```csharp
public Product SelectedProduct { get; set; } // null bij opstarten! ❌
```
**Oplossing 1**: Initialiseer met default waarde
```csharp
public Product SelectedProduct { get; set; } = new Product(); // ✅
```
**Oplossing 2**: Gebruik FallbackValue in binding
```xml
```
**Oplossing 3**: Initiële selectie in code
```csharp
if (comboBox.Items.Count > 0)
{
comboBox.SelectedIndex = 0; // ✅
}
```
---
## Uitbreidingsopdrachten
Nu je de basis begrijpt, probeer deze uitbreidingen:
### Opdracht 1: Delete Product Command
Voeg een "Delete Product" button toe:
1. Maak een `DeleteProductCommand`
2. Implementeer `ExecuteDeleteProduct` method
3. Implementeer `CanExecuteDeleteProduct` (alleen als er een product geselecteerd is)
4. Voeg button toe aan XAML
**Hint**:
```csharp
private bool CanExecuteDeleteProduct(object? parameter)
{
return SelectedProduct != null;
}
```
### Opdracht 2: Edit Product Command
Maak een dialoog om producten te bewerken:
1. Maak een nieuw Window `EditProductWindow.xaml`
2. Maak een `EditProductViewModel`
3. Gebruik `ShowDialog()` om het window te tonen
4. Sla wijzigingen op
### Opdracht 3: Value Converter
Maak een value converter voor prijs formatting:
1. Maak een class in `ValueConverters` folder
2. Implementeer `IValueConverter`
3. Converteer decimal naar string met currency symbool
4. Gebruik in binding: `{Binding Price, Converter={StaticResource PriceConverter}}`
**Voorbeeld**:
```csharp
public class PriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is decimal price)
{
return $"€ {price:F2}";
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
```
### Opdracht 4: Input Validatie
Voeg validatie toe voor product naam en prijs:
1. Implementeer `IDataErrorInfo` in Product class
2. Valideer in property setters
3. Toon error messages in UI
4. Disable "Add Product" bij ongeldige input
### Opdracht 5: Persistentie
Sla producten op in een file:
1. Maak een `ProductRepository` class
2. Implementeer `SaveToFile()` en `LoadFromFile()`
3. Gebruik JSON serialization
4. Laad data bij opstarten
5. Sla op bij wijzigingen
---
## Samenvatting
Je hebt nu een volledige WPF MVVM applicatie gebouwd! Je hebt geleerd:
### Patronen en Concepten:
- ✅ MVVM architectuur pattern
- ✅ INotifyPropertyChanged voor property change notification
- ✅ ICommand pattern voor button logic
- ✅ Data Binding (OneWay, TwoWay)
- ✅ ObservableCollection voor automatische UI updates
### Implementatie Details:
- ✅ Model class met property notification
- ✅ ViewModel met commands en observable collections
- ✅ View met XAML data binding
- ✅ RelayCommand voor commando implementatie
- ✅ Separation of concerns
### Best Practices:
- ✅ Minimale code-behind
- ✅ Testbare code
- ✅ Herbruikbare componenten
- ✅ Clean architecture
**Volgende stappen**:
1. Experimenteer met de uitbreidingsopdrachten
2. Lees de officiële Microsoft WPF documentatie
3. Bouw je eigen MVVM applicatie
4. Onderzoek MVVM frameworks zoals Prism of MVVM Light
Succes met je verdere MVVM ontwikkeling!