Files
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

18 KiB

MVVM Pattern Implementation

**Referenced Files in This Document** - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [MainWindow.axaml](file://ClassicAssist.Avalonia/Views/MainWindow.axaml) - [MainWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/MainWindow.axaml.cs) - [App.axaml.cs](file://ClassicAssist.Avalonia/App.axaml.cs) - [Program.cs](file://ClassicAssist.Avalonia/Program.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) - [DialogWindow.axaml](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml) - [DialogWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) - [GeneralControl.axaml](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml) - [GeneralControl.axaml.cs](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion

Introduction

This document explains how the Model-View-ViewModel (MVVM) pattern is implemented in ClassicAssist.Avalonia. It focuses on separation of concerns between Views (AXAML), ViewModels (C# classes), and Models (business logic), with emphasis on:

  • The BaseViewModel that provides common functionality such as property change notification and command binding
  • Data binding mechanisms and event handling patterns
  • Lifecycle management of ViewModels
  • Concrete examples from MainWindow and other key views
  • Testing strategies for ViewModels and best practices to maintain clean separation between presentation logic and business rules

Project Structure

The Avalonia UI layer follows a clear folder structure aligned with MVVM:

  • Views are defined in AXAML files under ClassicAssist.Avalonia/Views, with code-behind files (.axaml.cs) kept minimal
  • ViewModels live in ClassicAssist.Shared/UI/ViewModels and implement presentation logic and data binding
  • Models and shared services reside in ClassicAssist.Shared, including UI infrastructure like dispatchers and UI invokers
  • App initialization wires up the root ViewModel and ensures UI updates occur on the correct thread
graph TB
subgraph "Avalonia UI Layer"
A_MainWindow["MainWindow.axaml<br/>Code-behind: MainWindow.axaml.cs"]
A_GeneralControl["GeneralControl.axaml<br/>Code-behind: GeneralControl.axaml.cs"]
A_DialogWindow["DialogWindow.axaml<br/>Code-behind: DialogWindow.axaml.cs"]
end
subgraph "Shared UI Infrastructure"
B_BaseViewModel["BaseViewModel.cs"]
B_IDispatcher["IDispatcher.cs"]
B_IUIInvoker["IUIInvoker.cs"]
B_AvaloniaDispatcher["AvaloniaDispatcher.cs"]
B_AvaloniaUIInvoker["AvaloniaUIInvoker.cs"]
end
subgraph "Shared Business Logic"
C_Models["Models and Managers<br/>(ClassicAssist.Shared/Data/*)"]
end
A_MainWindow --> B_BaseViewModel
A_GeneralControl --> B_BaseViewModel
A_DialogWindow --> B_BaseViewModel
B_BaseViewModel --> B_IDispatcher
B_BaseViewModel --> B_IUIInvoker
B_AvaloniaDispatcher --> B_IDispatcher
B_AvaloniaUIInvoker --> B_IUIInvoker
A_MainWindow --> C_Models
A_GeneralControl --> C_Models
A_DialogWindow --> C_Models

Diagram sources

Section sources

Core Components

  • BaseViewModel: Provides common MVVM utilities such as property change notification and command support. All ViewModels inherit from this base to ensure consistent behavior and reduce boilerplate.
  • MainWindowViewModel: Root ViewModel for the main window; orchestrates UI state and delegates business operations to models/managers.
  • DialogWindowViewModel: Lightweight ViewModel used by modal dialogs to expose properties and commands bound from AXAML.
  • GeneralControlViewModel: Example of a reusable control ViewModel demonstrating encapsulated presentation logic.
  • Dispatcher and UI Invoker abstractions: Ensure UI updates occur on the UI thread regardless of background work.

Key responsibilities:

  • Views (AXAML): Define layout, styling, and declarative bindings to ViewModel properties and commands. Code-behind should remain thin and avoid business logic.
  • ViewModels: Expose bindable properties and commands, handle user interactions, coordinate with models, and update UI state safely via dispatcher/invoker.
  • Models: Contain business rules, data access, and domain logic. They do not depend on UI frameworks.

Section sources

Architecture Overview

At runtime, the application initializes the UI framework, creates the root ViewModel, and sets it as the DataContext for the main view. ViewModels use the dispatcher to marshal UI updates onto the UI thread and invoke UI helpers through the UI invoker abstraction.

sequenceDiagram
participant App as "App.axaml.cs"
participant Program as "Program.cs"
participant MainWindow as "MainWindow.axaml"
participant VM as "MainWindowViewModel"
participant Dispatcher as "AvaloniaDispatcher"
participant UIInvoker as "AvaloniaUIInvoker"
Program->>App : Initialize UI framework
App->>MainWindow : Create main window
App->>VM : Instantiate root ViewModel
App->>MainWindow : Set DataContext = VM
MainWindow-->>VM : Bindings resolve properties/commands
VM->>Dispatcher : ExecuteOnUIThread(action)
Dispatcher-->>MainWindow : Invoke UI update
VM->>UIInvoker : Show dialog / message
UIInvoker-->>MainWindow : Present UI element

Diagram sources

Detailed Component Analysis

BaseViewModel

BaseViewModel centralizes MVVM essentials:

  • Property change notification: Derived classes raise notifications when properties change so bindings update automatically.
  • Command support: Common command infrastructure enables binding user actions to methods or ICommand implementations.
  • Thread safety helpers: Integration with dispatcher/invoker to ensure UI updates happen on the UI thread.

Best practices:

  • Always raise property change notifications after updating backing fields.
  • Use commands for all user interactions exposed to AXAML.
  • Avoid direct UI references; rely on dispatcher and UI invoker abstractions.
classDiagram
class BaseViewModel {
+RaisePropertyChanged(propertyName)
+ExecuteCommand(command)
+EnsureUiThread(action)
}
class MainWindowViewModel {
+Properties...
+Commands...
+Initialize()
}
class DialogWindowViewModel {
+Title
+Message
+OkCommand
+CancelCommand
}
class GeneralControlViewModel {
+State
+RefreshCommand
}
MainWindowViewModel --|> BaseViewModel
DialogWindowViewModel --|> BaseViewModel
GeneralControlViewModel --|> BaseViewModel

Diagram sources

Section sources

MainWindow and MainWindowViewModel

The main window demonstrates typical MVVM usage:

  • AXAML defines controls and binds their properties and commands to MainWindowViewModel members.
  • Code-behind remains minimal, typically only wiring up initial DataContext if needed.
  • MainWindowViewModel exposes properties for UI state and commands for user actions, coordinating with models/managers.

Data binding flow:

  • User interaction triggers a command in MainWindowViewModel.
  • ViewModel updates properties and raises notifications.
  • AXAML bindings reflect changes automatically.
sequenceDiagram
participant View as "MainWindow.axaml"
participant VM as "MainWindowViewModel"
participant Dispatcher as "AvaloniaDispatcher"
View->>VM : Command invoked (e.g., "OpenSettings")
VM->>VM : Perform action / update state
VM->>Dispatcher : EnsureUiThread(updateUI)
Dispatcher-->>View : Update bound properties
View-->>User : Reflect new state

Diagram sources

Section sources

DialogWindow and DialogWindowViewModel

Modal dialogs follow the same MVVM principles:

  • DialogWindow.axaml binds to DialogWindowViewModel properties (title, message) and commands (OK, Cancel).
  • DialogWindowViewModel encapsulates dialog-specific state and actions without referencing UI elements directly.
flowchart TD
Start(["Show Dialog"]) --> Bind["Bind Title/Message/Commands"]
Bind --> UserAction{"User clicks OK or Cancel?"}
UserAction --> |OK| Confirm["Confirm action in ViewModel"]
UserAction --> |Cancel| Close["Close dialog"]
Confirm --> UpdateUI["Update UI via dispatcher"]
UpdateUI --> End(["Dialog Closed"])
Close --> End

Diagram sources

Section sources

GeneralControl and GeneralControlViewModel

Reusable controls demonstrate encapsulation:

  • GeneralControl.axaml binds to GeneralControlViewModel properties and commands.
  • GeneralControlViewModel manages its own state and exposes a refresh command, keeping the control self-contained and testable.
classDiagram
class GeneralControl {
<<AXAML>>
+Bindings to ViewModel
}
class GeneralControlViewModel {
+State
+RefreshCommand
+Initialize()
}
GeneralControl --> GeneralControlViewModel : "DataContext"

Diagram sources

Section sources

Dispatcher and UI Invoker Abstractions

To keep ViewModels UI-framework agnostic:

  • IDispatcher and IUIInvoker define contracts for executing code on the UI thread and invoking UI operations.
  • AvaloniaDispatcher and AvaloniaUIInvoker provide concrete implementations for Avalonia.
classDiagram
class IDispatcher {
+ExecuteOnUIThread(action)
}
class IUIInvoker {
+ShowDialog(dialogViewModel)
+ShowMessage(message)
}
class AvaloniaDispatcher {
+ExecuteOnUIThread(action)
}
class AvaloniaUIInvoker {
+ShowDialog(dialogViewModel)
+ShowMessage(message)
}
IDispatcher <|.. AvaloniaDispatcher
IUIInvoker <|.. AvaloniaUIInvoker

Diagram sources

Section sources

Dependency Analysis

ViewModels depend on:

  • BaseViewModel for common functionality
  • Dispatcher and UI invoker abstractions for cross-thread UI updates
  • Models/managers for business logic (not shown here but present in Shared/Data)
graph LR
VM_Main["MainWindowViewModel"] --> Base["BaseViewModel"]
VM_Dialog["DialogWindowViewModel"] --> Base
VM_General["GeneralControlViewModel"] --> Base
Base --> Disp["IDispatcher"]
Base --> Inv["IUIInvoker"]
Disp --> DispImpl["AvaloniaDispatcher"]
Inv --> InvImpl["AvaloniaUIInvoker"]

Diagram sources

Section sources

Performance Considerations

  • Minimize heavy work on the UI thread; offload to background tasks and marshal results back using the dispatcher.
  • Use efficient collections and avoid unnecessary property updates; batch updates where possible.
  • Keep AXAML bindings simple and avoid complex converters in hot paths.
  • Reuse ViewModels and controls when appropriate to reduce allocation overhead.

[No sources needed since this section provides general guidance]

Troubleshooting Guide

Common issues and resolutions:

  • UI not updating after property changes: Ensure property change notifications are raised correctly in BaseViewModel-derived classes.
  • Cross-thread exceptions: Always use the dispatcher to execute UI updates from background threads.
  • Commands not firing: Verify command bindings in AXAML and that commands are properly exposed and enabled.
  • Dialogs not showing: Use the UI invoker abstraction to present dialogs instead of direct UI calls.

Section sources

Conclusion

ClassicAssist.Avalonia implements MVVM cleanly by separating Views (AXAML), ViewModels (C#), and Models (business logic). BaseViewModel standardizes property change notification and command support, while dispatcher and UI invoker abstractions ensure thread-safe UI updates. MainWindow, DialogWindow, and GeneralControl illustrate proper MVVM usage, enabling testable and maintainable presentation logic. Following these patterns helps keep the UI responsive and the codebase organized.

[No sources needed since this section summarizes without analyzing specific files]