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

27 KiB

MVVM Architecture Implementation

**Referenced Files in This Document** - [BaseViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/BaseViewModel.cs) - [MainWindowViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MainWindowViewModel.cs) - [App.axaml.cs](file://ClassicAssist.Avalonia/App.axaml.cs) - [Program.cs](file://ClassicAssist.Avalonia/Program.cs) - [MainWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/MainWindow.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) - [DialogWindowViewModel.cs](file://ClassicAssist.Avalonia/ViewModels/DialogWindowViewModel.cs) - [AutolootViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/AutolootViewModel.cs) - [DressTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/DressTabViewModel.cs) - [FriendsTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/FriendsTabViewModel.cs) - [OrganizerTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/OrganizerTabViewModel.cs) - [ScavengerTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/ScavengerTabViewModel.cs) - [VendorBuyTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/VendorBuyTabViewModel.cs) - [VendorSellTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Agents/VendorSellTabViewModel.cs) - [ClilocSelectionViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Autoloot/ClilocSelectionViewModel.cs) - [CustomPropertiesViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Autoloot/CustomPropertiesViewModel.cs) - [PropertySelectionViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/Autoloot/PropertySelectionViewModel.cs) - [AboutControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/AboutControlViewModel.cs) - [DebugViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/DebugViewModel.cs) - [GeneralControlViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/GeneralControlViewModel.cs) - [HotkeyEntryViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/HotkeyEntryViewModel.cs) - [HotkeysTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/HotkeysTabViewModel.cs) - [MacrosTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/MacrosTabViewModel.cs) - [NewProfileViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/NewProfileViewModel.cs) - [ObjectInspectorViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/ObjectInspectorViewModel.cs) - [OptionsTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/OptionsTabViewModel.cs) - [RepositionableGumpViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/RepositionableGumpViewModel.cs) - [SkillsTabViewModel.cs](file://ClassicAssist.Shared/UI/ViewModels/SkillsTabViewModel.cs) - [ChatManager.cs](file://ClassicAssist.Shared/Data/Chat/ChatManager.cs) - [ChatMessage.cs](file://ClassicAssist.Shared/Data/Chat/ChatMessage.cs) - [IncomingPacketHandlers.cs](file://ClassicAssist.Shared/UO/Network/IncomingPacketHandlers.cs) - [MobileIncoming.cs](file://ClassicAssist.Shared/UO/Network/Packets/MobileIncoming.cs) - [UseObject.cs](file://ClassicAssist.Shared/UO/Network/Packets/UseObject.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
  10. Appendices

Introduction

This document explains the Model-View-ViewModel (MVVM) architecture implemented in ClassicAssist.Avalonia. It focuses on separation of concerns between Views (XAML), ViewModels (C# logic), and Models (data), with emphasis on:

  • BaseViewModel hierarchy and property change notification patterns
  • Command binding mechanisms
  • Data flow from game engine through ViewModels to UI components
  • ViewModel composition, dependency injection, and lifecycle management
  • Error handling strategies, validation patterns, and testing approaches for MVVM components

The goal is to provide a clear mental model for both new contributors and experienced developers working on the Avalonia-based UI layer.

Project Structure

The project follows a layered structure:

  • Shared core (models, services, commands, base MVVM infrastructure)
  • Avalonia-specific presentation layer (Views, ViewModels, converters, behaviors)
  • Game engine integration points (networking, packets, UO data)
graph TB
subgraph "Shared Core"
VM_Base["BaseViewModel"]
VM_Main["MainWindowViewModel"]
VM_Agents["Agent Tab ViewModels"]
VM_Autoloot["Autoloot ViewModels"]
VM_Others["Other ViewModels"]
ChatMgr["ChatManager"]
Incoming["IncomingPacketHandlers"]
end
subgraph "Avalonia UI"
App["App.axaml.cs"]
Program["Program.cs"]
MainWindow["MainWindow.axaml.cs"]
Dispatcher["AvaloniaDispatcher"]
UIInvoker["AvaloniaUIInvoker"]
end
Program --> App
App --> MainWindow
MainWindow --> VM_Main
VM_Main --> VM_Agents
VM_Main --> VM_Autoloot
VM_Main --> VM_Others
VM_Agents --> ChatMgr
VM_Autoloot --> ChatMgr
VM_Others --> ChatMgr
Incoming --> ChatMgr
VM_Main --> Dispatcher
VM_Main --> UIInvoker

Diagram sources

Section sources

Core Components

  • BaseViewModel: Provides common MVVM infrastructure such as property change notification and command support. All ViewModels derive from this class to ensure consistent behavior.
  • MainWindowViewModel: Orchestrates top-level UI state and composes child ViewModels for tabs and features.
  • AvaloniaDispatcher and AvaloniaUIInvoker: Implement shared interfaces to marshal work onto the UI thread and present UI interactions (e.g., dialogs).
  • ChatManager and related models: Centralize chat-related data and events that ViewModels consume.

Key responsibilities:

  • BaseViewModel encapsulates INotifyPropertyChanged semantics and command execution helpers.
  • MainWindowViewModel wires together tab ViewModels and exposes them to the view via bindings.
  • Dispatcher and UIInvoker abstract platform-specific threading and UI operations.

Section sources

Architecture Overview

The MVVM layers are cleanly separated:

  • Views (AXAML + code-behind) bind to properties and commands exposed by ViewModels.
  • ViewModels contain presentation logic, coordinate with models/services, and raise property change notifications.
  • Models represent domain data and business rules; they may publish events or expose collections that ViewModels observe.
sequenceDiagram
participant Engine as "Game Engine"
participant Incoming as "IncomingPacketHandlers"
participant ChatMgr as "ChatManager"
participant VM as "ViewModel (e.g., ChatViewModel)"
participant View as "Avalonia View"
Engine->>Incoming : "Parse incoming packet"
Incoming->>ChatMgr : "Update chat state / raise event"
ChatMgr-->>VM : "Notify changes (events/collections)"
VM->>VM : "Update bound properties"
VM-->>View : "INotifyPropertyChanged triggers UI update"

Diagram sources

Detailed Component Analysis

BaseViewModel Hierarchy and Property Change Notification

BaseViewModel centralizes:

  • Property change notification implementation
  • Common command helpers and execution context
  • Lifecycle hooks used by derived ViewModels

Derived ViewModels inherit these capabilities and focus on feature-specific logic.

classDiagram
class BaseViewModel {
+OnInitialized()
+OnDisposed()
+RaisePropertyChanged(propertyName)
+ExecuteCommand(command)
}
class MainWindowViewModel {
+Tabs
+ActiveTab
+Initialize()
}
class AutolootViewModel
class DressTabViewModel
class FriendsTabViewModel
class OrganizerTabViewModel
class ScavengerTabViewModel
class VendorBuyTabViewModel
class VendorSellTabViewModel
class ClilocSelectionViewModel
class CustomPropertiesViewModel
class PropertySelectionViewModel
class AboutControlViewModel
class DebugViewModel
class GeneralControlViewModel
class HotkeyEntryViewModel
class HotkeysTabViewModel
class MacrosTabViewModel
class NewProfileViewModel
class ObjectInspectorViewModel
class OptionsTabViewModel
class RepositionableGumpViewModel
class SkillsTabViewModel
class DialogWindowViewModel
MainWindowViewModel --|> BaseViewModel
AutolootViewModel --|> BaseViewModel
DressTabViewModel --|> BaseViewModel
FriendsTabViewModel --|> BaseViewModel
OrganizerTabViewModel --|> BaseViewModel
ScavengerTabViewModel --|> BaseViewModel
VendorBuyTabViewModel --|> BaseViewModel
VendorSellTabViewModel --|> BaseViewModel
ClilocSelectionViewModel --|> BaseViewModel
CustomPropertiesViewModel --|> BaseViewModel
PropertySelectionViewModel --|> BaseViewModel
AboutControlViewModel --|> BaseViewModel
DebugViewModel --|> BaseViewModel
GeneralControlViewModel --|> BaseViewModel
HotkeyEntryViewModel --|> BaseViewModel
HotkeysTabViewModel --|> BaseViewModel
MacrosTabViewModel --|> BaseViewModel
NewProfileViewModel --|> BaseViewModel
ObjectInspectorViewModel --|> BaseViewModel
OptionsTabViewModel --|> BaseViewModel
RepositionableGumpViewModel --|> BaseViewModel
SkillsTabViewModel --|> BaseViewModel
DialogWindowViewModel --|> BaseViewModel

Diagram sources

Section sources

Command Binding Mechanisms

Commands in ViewModels encapsulate user actions and are bound directly in XAML. Typical patterns include:

  • Defining command properties on ViewModels
  • Executing asynchronous operations safely on background threads
  • Marshaling results back to the UI thread using the dispatcher abstraction
flowchart TD
Start(["User Action"]) --> Bind["XAML binds to ViewModel.Command"]
Bind --> Execute["ViewModel executes command"]
Execute --> Validate["Validate inputs"]
Validate --> Valid{"Valid?"}
Valid --> |No| ShowError["Show error via UIInvoker"]
Valid --> |Yes| DoWork["Perform background work"]
DoWork --> UpdateUI["Marshal result to UI thread"]
UpdateUI --> End(["UI updates via properties"])
ShowError --> End

Diagram sources

Section sources

Data Flow from Game Engine to UI

Incoming network packets are processed by handlers, which update shared managers (e.g., ChatManager). ViewModels subscribe to manager events or observe collections, then update their own properties. The UI reacts automatically due to property change notifications.

sequenceDiagram
participant Net as "Network Layer"
participant Handler as "IncomingPacketHandlers"
participant Packet as "Packets (e.g., MobileIncoming, UseObject)"
participant Manager as "ChatManager"
participant VM as "ViewModel"
participant View as "Avalonia View"
Net->>Handler : "Receive packet bytes"
Handler->>Packet : "Deserialize packet"
Packet->>Manager : "Apply changes / raise events"
Manager-->>VM : "Notify subscribers"
VM->>VM : "Update bound properties"
VM-->>View : "INotifyPropertyChanged"
View-->>View : "Render updated UI"

Diagram sources

Section sources

ViewModel Composition and Dependency Injection

Composition occurs at application startup:

  • Program initializes the application framework
  • App configures services and creates root ViewModels
  • MainWindow sets its DataContext to the root ViewModel
  • Child ViewModels are composed into parent ViewModels (e.g., tabs)
sequenceDiagram
participant Main as "Program.Main"
participant App as "App"
participant Window as "MainWindow"
participant RootVM as "MainWindowViewModel"
participant ChildVM as "Child ViewModel(s)"
Main->>App : "Run()"
App->>App : "Configure DI / services"
App->>RootVM : "Create root ViewModel"
App->>Window : "Create main window"
Window->>Window : "Set DataContext = RootVM"
RootVM->>ChildVM : "Compose child ViewModels"
Window-->>Window : "Bind to RootVM and children"

Diagram sources

Section sources

Lifecycle Management

Typical lifecycle stages:

  • Initialization: ViewModels set up subscriptions, load configuration, and prepare initial state
  • Active: ViewModels respond to user input and engine events
  • Disposal: ViewModels unsubscribe from events and release resources

BaseViewModel provides hooks for initialization and disposal. Ensure all long-lived subscriptions are cleaned up to prevent memory leaks.

stateDiagram-v2
[*] --> Created
Created --> Initialized : "OnInitialized"
Initialized --> Active : "Bound to View"
Active --> Disposing : "OnDisposed"
Disposing --> [*]

Diagram sources

Section sources

Error Handling Strategies

  • Commands should catch exceptions and report errors via UIInvoker
  • Validation failures should be surfaced to users promptly
  • Background tasks must marshal exceptions back to the UI thread before reporting
flowchart TD
Entry(["Command Entry"]) --> TryBlock["Try block"]
TryBlock --> Success{"Success?"}
Success --> |Yes| Done(["Return success"])
Success --> |No| Catch["Catch exception"]
Catch --> Marshal["Marshal to UI thread"]
Marshal --> Report["Report via UIInvoker"]
Report --> Done

Diagram sources

Section sources

Validation Patterns

  • Input validation within commands before executing side effects
  • Property setters can validate and raise property change notifications only when valid
  • Use centralized validation helpers where appropriate to keep ViewModels focused

[No sources needed since this section provides general guidance]

Testing Approaches for MVVM Components

  • Unit test ViewModels without UI dependencies by mocking IDispatcher and IUIInvoker
  • Assert property change notifications and command outcomes
  • Simulate engine events by invoking manager methods and verifying ViewModel state changes

[No sources needed since this section provides general guidance]

Dependency Analysis

The following diagram shows key runtime dependencies among MVVM components and UI abstractions.

graph LR
Program["Program.cs"] --> App["App.axaml.cs"]
App --> MainWindow["MainWindow.axaml.cs"]
MainWindow --> MainWindowVM["MainWindowViewModel.cs"]
MainWindowVM --> BaseVM["BaseViewModel.cs"]
MainWindowVM --> Dispatcher["AvaloniaDispatcher.cs"]
MainWindowVM --> UIInvoker["AvaloniaUIInvoker.cs"]
Dispatcher --> IDisp["IDispatcher.cs"]
UIInvoker --> IUI["IUIInvoker.cs"]
MainWindowVM --> ChatMgr["ChatManager.cs"]
Incoming["IncomingPacketHandlers.cs"] --> ChatMgr

Diagram sources

Section sources

Performance Considerations

  • Minimize heavy work on the UI thread; use background tasks and marshal results back via the dispatcher
  • Avoid excessive property change notifications by batching updates where possible
  • Use virtualization-friendly collections for large lists in views
  • Dispose subscriptions and timers in ViewModels to prevent memory growth over time

[No sources needed since this section provides general guidance]

Troubleshooting Guide

Common issues and resolutions:

  • UI not updating after property changes: verify property change notifications are raised and bound correctly
  • Cross-thread exceptions: ensure UI updates are marshaled through the dispatcher
  • Memory leaks: confirm ViewModels unsubscribe from long-lived events during disposal
  • Errors not shown to users: check that exceptions are caught and reported via UIInvoker

Section sources

Conclusion

ClassicAssist.Avalonia implements a clean MVVM architecture with a strong separation of concerns. BaseViewModel standardizes property change notification and command execution, while ViewModels compose feature-specific logic and interact with shared managers. The dispatcher and UI invoker abstractions decouple UI threading and interaction details, enabling robust error handling and testability. Following the patterns outlined here will help maintain consistency, performance, and reliability across the UI layer.

Appendices

  • Additional ViewModels referenced in this document:
    • Agents: Autoloot, Dress, Friends, Organizer, Scavenger, Vendor Buy/Sell
    • Autoloot: Cliloc Selection, Custom Properties, Property Selection
    • Other: About, Debug, General, Hotkeys, Macros, New Profile, Object Inspector, Options, Repositionable Gump, Skills
    • Dialog: DialogWindowViewModel

[No sources needed since this section summarizes references already cited above]