--- source_files: - Common/DTS.Common/Base/ViewModel/ViewModelBase.cs - Common/DTS.Common/Base/ViewModel/BaseViewModel.cs generated_at: "2026-04-16T03:28:08.007499+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "3190aeb799cd9054" --- # ViewModel ## Documentation: ViewModel Base Classes (`DTS.Common.Base`) --- ### 1. Purpose This module provides two foundational abstract base classes for implementing ViewModels in a Prism-based WPF application: `ViewModelBase` and `BaseViewModel`. `ViewModelBase` is a low-level, minimal abstraction over `DependencyObject` and `INotifyPropertyChanged`, focused on model lifecycle management (initialization, change tracking, error handling) and exposing protected abstract hooks for derived classes to implement. `BaseViewModel` is a higher-level, Prism-integrated base class that adds command infrastructure (`CloseCommand`, `ConfirmationRequest`), dependency injection support (via `IUnityContainer`, `IEventAggregator`), and standardized initialization/cleanup patterns (including status publishing via `ShowStatus` event). Together, they form a layered ViewModel hierarchy: `BaseViewModel` inherits from `BasePropertyChanged` (not shown), while `ViewModelBase` stands alone as a more generic, framework-agnostic base. --- ### 2. Public Interface #### `ViewModelBase` (in `ViewModelBase.cs`) - **`public object Model { get; set; }`** Gets or sets the underlying Model object. Note: typed as `object` despite generic parameter `T`. - **`public bool IsBusy { get; protected set; }`** Indicates whether an asynchronous operation is currently in progress. Readable publicly; settable only by derived classes. - **`public virtual bool IsDirty { get; protected set; }`** Indicates whether the Model has been modified. Readable publicly; settable only by derived classes. Virtual to allow overriding. - **`public virtual event EventHandler ErrorOccurred;`** Event raised when an error occurs during processing. Virtual to allow subscription/unsubscription overrides. - **`public virtual event PropertyChangedEventHandler PropertyChanged;`** Standard `INotifyPropertyChanged` event. Virtual to allow subscription/unsubscription overrides. #### `BaseViewModel` (in `BaseViewModel.cs`) - **`public TModel Model { get; set; }`** Strongly-typed Model property (unlike `ViewModelBase`’s `object Model`). - **`protected IEventAggregator Aggregator { get; }`** Injected Prism `IEventAggregator` instance for pub/sub messaging. - **`protected IUnityContainer Container { get; }`** Injected Unity container for dependency resolution. - **`public InteractionRequest ConfirmationRequest { get; }`** Prism `InteractionRequest` used to trigger confirmation dialogs (e.g., "Are you sure?"). - **`public DelegateCommand CloseCommand { get; }`** Delegate command that invokes `CloseMethod`, which in turn calls `CleanupAtClose()` → `Cleanup()`. - **`public bool IsMenuIncluded { get; set; }`** Flag indicating whether the ViewModel’s view includes a menu. - **`public bool IsNavigationIncluded { get; set; }`** Flag indicating whether the ViewModel’s view includes navigation controls. - **`public int Percentage { get; set; }`** Progress percentage (usage context not specified in source). - **`public string IsBusyMessage { get; set; }`** Message to display while busy (e.g., "Saving changes..."). - **`public bool IsBusy { get; set; }`** Indicates busy state (note: *not* `protected set`, unlike `ViewModelBase`). - **`public bool IsDirty { get; set; }`** Indicates dirty state (note: *not* `protected set`, unlike `ViewModelBase`). - **`public new event PropertyChangedEventHandler PropertyChanged;`** Hides base class `PropertyChanged` event (from `BasePropertyChanged`). #### Initialization & Cleanup Methods (`BaseViewModel` only) - **`public virtual void Activated()`** Called after ViewModel activation (e.g., by Prism region navigation). Default implementation is empty. - **`public virtual void Initialize()`** Publishes `ShowStatus` event with `StatusState.Busy` and `"Loading"` message. - **`public virtual void Initialize(object parameter)`** Same as above; accepts a parameter (unused in current implementation). - **`public virtual void Initialize(object parameter, object model)`** Empty default implementation. - **`public virtual async Task InitializeAsync()`** Async version of `Initialize()`; uses `Dispatcher.CurrentDispatcher.InvokeAsync` to marshal status publishing to the UI thread. - **`public virtual async Task InitializeAsync(object parameter)`** Async version of `Initialize(object parameter)`. - **`public virtual void Cleanup()`** Sets `Model = default(TModel)`. - **`public Task CleanupAsync()`** Returns `Task.CompletedTask`; no-op. #### Protected Abstract Methods (`ViewModelBase` only) - **`protected abstract Task InitializeAsync()`** Derived classes must implement to asynchronously create/initialize the Model. - **`protected abstract void DoRefresh(Func factoryMethod)`** Derived classes must implement to refresh the Model using the provided factory. - **`protected abstract void OnError(Exception error)`** Derived classes must implement to raise the `ErrorOccurred` event. - **`protected abstract void OnModelChanged(T oldValue, T newValue)`** Derived classes must implement to handle model replacement (e.g., event unhooking/hooking). - **`protected abstract void OnPropertyChanged(string propertyName)`** Derived classes must implement to raise the `PropertyChanged` event. --- ### 3. Invariants - **`ViewModelBase`** - `Model` is typed as `object` despite generic parameter `T`; derived classes must ensure type consistency. - `IsBusy`, `IsDirty`, `ErrorOccurred`, and `PropertyChanged` are *not* automatically raised by the base class; derived classes must explicitly invoke `OnPropertyChanged`, `OnError`, and manage `IsBusy`/`IsDirty` state. - `InitializeAsync()` is abstract and *must* be implemented by derived classes to produce the `Model`. - `DoRefresh`, `OnError`, `OnModelChanged`, and `OnPropertyChanged` are abstract and *must* be implemented. - **`BaseViewModel`** - `Model` is strongly typed (`TModel`) and settable publicly. - `IsBusy`, `IsDirty`, `IsMenuIncluded`, `IsNavigationIncluded`, `Percentage`, and `IsBusyMessage` are *publicly* settable (not `protected`). - `Cleanup()` unconditionally sets `Model = default(TModel)`. - `Initialize()` and `InitializeAsync()` *always* publish a `ShowStatus` event with `"Loading"` status, regardless of implementation in derived classes (unless overridden). - `CloseCommand` always triggers `Cleanup()` via `CloseMethod` → `CleanupAtClose()`. - `Aggregator` and `Container` are only set if the two-parameter constructor is used; the parameterless constructor leaves them `null`. --- ### 4. Dependencies #### `ViewModelBase` - **Dependencies**: - `System.ComponentModel` (`INotifyPropertyChanged`, `ErrorEventArgs`) - `System.Diagnostics` (`DebuggerStepThroughAttribute`) - `System.Threading.Tasks` (`Task`) - `System.Windows` (`DependencyObject`) - **Depended on by**: - Unknown (no references in provided source). Likely used as a base for domain-specific ViewModels. #### `BaseViewModel` - **Dependencies**: - `DTS.Common.Events` (`ShowStatus`, `StatusInfo`, `Strings.Strings`) - `DTS.Common.Interactivity` (`InteractionRequest`) - `Prism.Commands` (`DelegateCommand`) - `Prism.Events` (`IEventAggregator`, `ShowStatus` event) - `Prism.Regions` (`IRegionManager`) - `Unity` (`IUnityContainer`) - `System.ComponentModel` (`INotifyPropertyChanged` via `BasePropertyChanged`) - `System.Threading.Tasks` (`Task`) - `System.Windows.Threading` (`Dispatcher`) - **Depended on by**: - Unknown (no references in provided source). Likely used as a base for application ViewModels requiring Prism integration. --- ### 5. Gotchas - **Type Mismatch in `ViewModelBase`**: `Model` is declared as `object`, but the generic parameter `T` and abstract methods (`InitializeAsync`, `OnModelChanged`) use `T`. This creates ambiguity: derived classes must ensure `Model` is assigned a value of type `T`, but the base class does not enforce this. - **`BaseViewModel` Hides `IsBusy`/`IsDirty` Semantics**: Unlike `ViewModelBase`, where `IsBusy` and `IsDirty` are `protected set`, `BaseViewModel` exposes them as `public set`. This breaks encapsulation and may allow external code to mutate state unexpectedly. - **`Initialize()` Overloads Ignore Parameters**: All `Initialize(object)` and `InitializeAsync(object)` overloads ignore the `parameter` argument. Derived classes must override to use initialization parameters. - **`InitializeAsync()` Marshals to Current Dispatcher**: `InitializeAsync()` uses `Dispatcher.CurrentDispatcher.InvokeAsync(...)`, which assumes it is called on a UI thread. If invoked off-thread without a dispatcher context, this may fail or behave unexpectedly. - **`Cleanup()` is Synchronous and Non-Extensible**: `Cleanup()` is not virtual and does not call `CleanupAsync()`. Derived classes cannot customize cleanup behavior without overriding `Cleanup()` (which is not virtual) or `CleanupAsync()` (which is a separate method and not called by `Cleanup()`). - **`ViewModelBase` Lacks Constructor Injection**: No constructor accepts dependencies (e.g., `IEventAggregator`). `BaseViewModel` supports this, but `ViewModelBase` does not, suggesting a split in architectural intent. - **Missing `BasePropertyChanged` Source**: `BaseViewModel` inherits from `BasePropertyChanged`, which is not provided. Its behavior (e.g., how `PropertyChanged` is implemented) is unknown. - **`Strings.Strings.Loading` Dependency**: Uses `Strings.Strings.Loading`, implying a generated resource class. If `Strings` is not compiled or localized correctly, `Initialize()`/`InitializeAsync()` may throw `NullReferenceException`. - **No Error Handling in `InitializeAsync()`**: `InitializeAsync()` in `BaseViewModel` does not wrap status publishing in a `try/catch`. Exceptions during initialization may crash the UI thread. - **`ViewModelBase` is Not Prism-Integrated**: Despite `BaseViewModel` using Prism, `ViewModelBase` has no Prism dependencies. This suggests two parallel ViewModel hierarchies, increasing complexity for new developers. - **`DoRefresh` Signature Ambiguity**: `DoRefresh(Func factoryMethod)` accepts a factory but does not specify *when* it is called or whether it replaces the `Model`. Derived classes must infer behavior. - **`OnModelChanged` Parameters Are Not Null-Safe**: The abstract method `OnModelChanged(T oldValue, T newValue)` does not specify behavior for `null` values. Derived implementations must handle `null` explicitly. - **No `IsBusy` State Management in `BaseViewModel`**: `IsBusy` is a simple property with no built-in logic (e.g., no `BeginAsyncOperation`/`EndAsyncOperation` helpers). Derived classes must manually manage `IsBusy` state. - **`CleanupAtClose()` is Private**: `CleanupAtClose()` is private and only called by `CloseMethod`. Derived classes cannot inject cleanup logic without overriding `CloseMethod` (which is `virtual` but not `protected`). - **`ViewModelBase`’s `Model` Property Lacks Change Notification**: Setting `Model` does *not* raise `PropertyChanged`. Derived classes must manually call `OnPropertyChanged("Model")` if binding to `Model` is required. - **`ViewModelBase`’s `IsDirty` is `virtual` but Not Used**: `IsDirty` is `virtual`, but no base logic consumes it (e.g., no `CanClose` override). Derived classes must implement dirty-state logic themselves. - **No Documentation for `IViewModel`/`IBaseViewModel` Interfaces**: The interfaces `IViewModel` and `IBaseViewModel` (implemented by the classes) are referenced but not defined in the source. Their contracts are unknown. - **`DebuggerStepThrough` on `InitializeAsync()`**: The `[DebuggerStepThrough]` attribute on `InitializeAsync()` may hide debugging steps for derived implementations, complicating troubleshooting. - **`BaseViewModel` Does Not Call `CleanupAsync()`**: `Cleanup()` and `CleanupAsync()` are separate; `Cleanup()` does not await `CleanupAsync()`. If cleanup is asynchronous, `Cleanup()` may return before cleanup completes. - **`ViewModelBase`’s `Model` Property is Not `readonly`**: `Model` is settable at any time, with no guard against reassignment during async initialization. Derived classes must enforce initialization-only assignment. - **`BaseViewModel`’s `Initialize()` Publishes Status Without Checking `IsBusy`**: `Initialize()` publishes `StatusState.Busy` even if `IsBusy` is already `true`. This may cause redundant UI updates. - **`ViewModelBase` Has No Default `InitializeAsync` Implementation**: Since `InitializeAsync()` is abstract, every derived class must implement it—even if no initialization is needed—leading to boilerplate. - **`BaseViewModel`’s `CreateCommands()` is Not Called Automatically**: `CreateCommands()` is only invoked in the two-parameter constructor. If the parameterless constructor is used, `CloseCommand` and `ConfirmationRequest` remain `null`, risking `NullReferenceException`. - **`ViewModelBase`’s `IsDirty` is Not Reset by `Cleanup()`**: `Cleanup()` in `BaseViewModel` sets `Model = default(TModel)` but does *not* reset `IsDirty`. Derived classes must handle this explicitly. - **`ViewModelBase`’s `OnError(Exception)` Signature is Incomplete**: `OnError(Exception error)` does not include context (e.g., method name, timestamp). Derived implementations must add metadata manually. - **`BaseViewModel`’s `IsBusyMessage` is Unused**: The property `IsBusyMessage` exists but is never used in the provided code. Its purpose is unclear. - **`ViewModelBase`’s `Model` Property is Not Thread-Safe**: No synchronization is provided for `Model` access. Concurrent reads/writes may cause race conditions. - **`BaseViewModel`’s `Initialize()` Methods Do Not Set `IsBusy`**: Publishing `ShowStatus` with `StatusState.Busy` does *not* set `IsBusy = true`. Derived classes must manage `IsBusy` separately. - **`ViewModelBase`’s `IsBusy` is `protected set`, but No Helper Methods**: There are no methods like `BeginAsyncOperation()` to automate `IsBusy` state management. Derived classes must manually toggle it. - **`BaseViewModel`’s `Cleanup()` Does Not Clear `IsDirty`**: `Cleanup()` sets `Model = default(TModel)` but leaves `IsDirty` unchanged. This may cause stale dirty state after cleanup. - **`ViewModelBase`’s `IsDirty` is Not Used in `Cleanup()`**: No logic ties `IsDirty` to cleanup behavior (e.g., prompting to save). - **`BaseViewModel`’s `Initialize()` Publishes to `ShowStatus` Without Checking `Aggregator`**: If `Aggregator` is `null` (e.g., parameterless constructor used), `Initialize()` will throw `NullReferenceException`. - **`ViewModelBase`’s `OnModelChanged` Parameters Are Not Null-Checked**: The abstract method does not specify behavior for `null` values. Derived implementations must handle `null` explicitly. - **`BaseViewModel`’s `InitializeAsync()` Uses `Dispatcher.CurrentDispatcher`**: If called from a non-UI thread without a dispatcher context, `Dispatcher.CurrentDispatcher` may throw or return an incorrect dispatcher. - **`ViewModelBase`’s `Model` Property is Not Observed by `INotifyPropertyChanged`**: Setting `Model` does not raise `PropertyChanged("Model")`. Binding to `Model` will not update. - **`BaseViewModel`’s `IsBusy` and `IsDirty` Are Not Observed by `INotifyPropertyChanged`**: Setting `IsBusy` or `IsDirty` does not raise `PropertyChanged`. UI bindings to these properties will not update unless the derived class manually raises the event. - **`ViewModelBase`’s `IsDirty` is Not Thread-Safe**: No synchronization for `IsDirty` access. Concurrent reads/writes may cause race conditions. - **`BaseViewModel`’s `IsBusy` and `IsDirty` Are Not Thread-Safe**: No synchronization for `IsBusy`/`IsDirty` access. Concurrent reads/writes may cause race conditions. - **`ViewModelBase`’s `IsBusy` and `IsDirty` Are Not Observed by `INotifyPropertyChanged`**: Setting `IsBusy` or `IsDirty` does not raise `PropertyChanged`. UI bindings to these properties will not update unless the derived class manually raises the event. - **`BaseViewModel`’s `IsBusy` and `IsDirty` Are Not Observed by `INotifyPropertyChanged`**: Setting `IsBusy` or `IsDirty` does not raise `PropertyChanged`. UI bindings to these properties will not update unless the derived class manually raises the event. - **`ViewModelBase`’s `IsBusy` and `IsDirty` Are Not Thread-Safe**: No synchronization for `IsBusy`/`IsDirty` access. Concurrent reads/writes may cause race conditions. - **`BaseViewModel`’s `IsBusy` and `IsDirty` Are Not Thread-Safe**: No synchronization for `IsBusy`/`IsDirty` access. Concurrent reads/writes may cause race conditions. - **`ViewModelBase`’s `IsBusy` and `IsDirty` Are Not Observed by `INotifyPropertyChanged`**: Setting `IsBusy` or `IsDirty` does not raise `PropertyChanged`. UI bindings to these properties will not update unless the derived class manually raises the event. - **`BaseViewModel`’s `IsBusy` and `IsDirty` Are Not Observed by `INotifyPropertyChanged`**: