Skip to main content
The Presentation Layer contains the WPF user interface, ViewModels, and View-related logic. It follows the MVVM (Model-View-ViewModel) pattern to separate UI from business logic.

Layer Overview

Location: ~/workspace/source/Chapi/Presentation/ Responsibilities:
  • Display user interface (XAML Views)
  • Handle user input and interactions
  • Implement ViewModels for data binding
  • Manage navigation and dialogs
  • Convert data for display (Value Converters)
Dependencies:
  • ✅ Depends on Application Layer (use cases)
  • ✅ Depends on Domain Layer (entities for display)
  • ✅ WPF framework dependencies
  • ❌ No dependencies on Infrastructure

MVVM Architecture

ViewModels

ViewModels expose data and commands for Views to bind to.

Base ViewModel

Command Implementation

Main ViewModels

Value Converters

Converters transform data between ViewModel and View.

View Structure

Views are organized by functionality:
Presentation/Views/
├── Tabs/
│   ├── ChangesView.xaml       # Git changes and commits
│   ├── HistoryView.xaml       # Commit history
│   ├── ReleasesView.xaml      # GitHub releases
│   ├── WorkspaceView.xaml     # Tasks and notes
│   └── AssistantView.xaml     # AI chat assistant
├── Dialogs/
│   ├── CloneRepositoryDialog.xaml
│   ├── SwitchBranchDialog.xaml
│   ├── CreateReleaseDialog.xaml
│   └── GitConfigDialog.xaml
└── Agent/
    ├── SqlGeneratorView.xaml
    ├── AddMethodView.xaml
    └── RollbackSelectorView.xaml

Data Binding Examples

Dialog Pattern

Chapi uses MaterialDesignThemes for dialogs:
// Show dialog
var dialog = new CloneRepositoryDialog
{
    DataContext = new CloneRepositoryViewModel()
};

var result = await DialogHost.Show(dialog, "RootDialog");

if (result is bool success && success)
{
    // Handle dialog result
}

Presentation Patterns

MVVM Pattern

Separation of UI (View) from logic (ViewModel)

Command Pattern

RelayCommand and AsyncRelayCommand for actions

Observable Collections

Auto-updating lists with ObservableCollection<T>

Value Converters

Transform data between ViewModel and View

Performance Considerations

Always update UI collections on the dispatcher thread:
await Application.Current.Dispatcher.InvokeAsync(() =>
{
    Changes.Clear();
    foreach (var vm in viewModels)
        Changes.Add(vm);
});
Use VirtualizingStackPanel for large lists:
<ListBox VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.VirtualizationMode="Recycling">
</ListBox>
Load data asynchronously to avoid blocking UI:
public AsyncRelayCommand LoadChangesCommand { get; }

LoadChangesCommand = new AsyncRelayCommand(
    async _ => await LoadChangesAsync());

Testing ViewModels

[Test]
public void CommitSummary_WhenSet_RaisesCanExecuteChanged()
{
    // Arrange
    var viewModel = new ChangesViewModel(/* dependencies */);
    var commandRaised = false;
    viewModel.CommitCommand.CanExecuteChanged += (s, e) => commandRaised = true;

    // Act
    viewModel.CommitSummary = "Test commit";

    // Assert
    Assert.IsTrue(commandRaised);
}

[Test]
public void CanCommit_ReturnsFalse_WhenNoSummary()
{
    // Arrange
    var viewModel = new ChangesViewModel(/* dependencies */);
    viewModel.CommitSummary = "";

    // Act
    var canExecute = viewModel.CommitCommand.CanExecute(null);

    // Assert
    Assert.IsFalse(canExecute);
}

Application Layer

Use cases consumed by ViewModels

Domain Layer

Entities displayed in Views

WPF Resources

Official WPF documentation

Build docs developers (and LLMs) love