# 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](#introduction) 2. [Project Structure](#project-structure) 3. [Core Components](#core-components) 4. [Architecture Overview](#architecture-overview) 5. [Detailed Component Analysis](#detailed-component-analysis) 6. [Dependency Analysis](#dependency-analysis) 7. [Performance Considerations](#performance-considerations) 8. [Troubleshooting Guide](#troubleshooting-guide) 9. [Conclusion](#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 ```mermaid graph TB subgraph "Avalonia UI Layer" A_MainWindow["MainWindow.axaml
Code-behind: MainWindow.axaml.cs"] A_GeneralControl["GeneralControl.axaml
Code-behind: GeneralControl.axaml.cs"] A_DialogWindow["DialogWindow.axaml
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
(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** - [MainWindow.axaml](file://ClassicAssist.Avalonia/Views/MainWindow.axaml) - [MainWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/MainWindow.axaml.cs) - [GeneralControl.axaml](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml) - [GeneralControl.axaml.cs](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml.cs) - [DialogWindow.axaml](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml) - [DialogWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml.cs) - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) **Section sources** - [App.axaml.cs](file://ClassicAssist.Avalonia/App.axaml.cs) - [Program.cs](file://ClassicAssist.Avalonia/Program.cs) ## 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** - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) ## 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. ```mermaid 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** - [App.axaml.cs](file://ClassicAssist.Avalonia/App.axaml.cs) - [Program.cs](file://ClassicAssist.Avalonia/Program.cs) - [MainWindow.axaml](file://ClassicAssist.Avalonia/Views/MainWindow.axaml) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) ## 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. ```mermaid 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** - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) **Section sources** - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) ### 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. ```mermaid 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** - [MainWindow.axaml](file://ClassicAssist.Avalonia/Views/MainWindow.axaml) - [MainWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/MainWindow.axaml.cs) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) **Section sources** - [MainWindow.axaml](file://ClassicAssist.Avalonia/Views/MainWindow.axaml) - [MainWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/MainWindow.axaml.cs) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) ### 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. ```mermaid 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** - [DialogWindow.axaml](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml) - [DialogWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) **Section sources** - [DialogWindow.axaml](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml) - [DialogWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/DialogWindow.axaml.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) ### 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. ```mermaid classDiagram class GeneralControl { <> +Bindings to ViewModel } class GeneralControlViewModel { +State +RefreshCommand +Initialize() } GeneralControl --> GeneralControlViewModel : "DataContext" ``` **Diagram sources** - [GeneralControl.axaml](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml) - [GeneralControl.axaml.cs](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) **Section sources** - [GeneralControl.axaml](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml) - [GeneralControl.axaml.cs](file://ClassicAssist.Avalonia/Views/GeneralControl.axaml.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) ### 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. ```mermaid 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** - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) **Section sources** - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) ## 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) ```mermaid 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** - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) **Section sources** - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) ## 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** - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) ## 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]