Files
2026-04-17 14:55:32 -04:00

302 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<T>` and `BaseViewModel<TModel>`. `ViewModelBase<T>` 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<TModel>` 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<TModel>` inherits from `BasePropertyChanged` (not shown), while `ViewModelBase<T>` stands alone as a more generic, framework-agnostic base.
---
### 2. Public Interface
#### `ViewModelBase<T>` (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<ErrorEventArgs> 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<TModel>` (in `BaseViewModel.cs`)
- **`public TModel Model { get; set; }`**
Strongly-typed Model property (unlike `ViewModelBase<T>`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<Confirmation> ConfirmationRequest { get; }`**
Prism `InteractionRequest` used to trigger confirmation dialogs (e.g., "Are you sure?").
- **`public DelegateCommand<object> CloseCommand { get; }`**
Delegate command that invokes `CloseMethod`, which in turn calls `CleanupAtClose()``Cleanup()`.
- **`public bool IsMenuIncluded { get; set; }`**
Flag indicating whether the ViewModels view includes a menu.
- **`public bool IsNavigationIncluded { get; set; }`**
Flag indicating whether the ViewModels 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<T>`).
- **`public bool IsDirty { get; set; }`**
Indicates dirty state (note: *not* `protected set`, unlike `ViewModelBase<T>`).
- **`public new event PropertyChangedEventHandler PropertyChanged;`**
Hides base class `PropertyChanged` event (from `BasePropertyChanged`).
#### Initialization & Cleanup Methods (`BaseViewModel<TModel>` 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<T>` only)
- **`protected abstract Task<T> InitializeAsync()`**
Derived classes must implement to asynchronously create/initialize the Model.
- **`protected abstract void DoRefresh(Func<T> 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<T>`**
- `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<TModel>`**
- `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<T>`
- **Dependencies**:
- `System.ComponentModel` (`INotifyPropertyChanged`, `ErrorEventArgs`)
- `System.Diagnostics` (`DebuggerStepThroughAttribute`)
- `System.Threading.Tasks` (`Task<T>`)
- `System.Windows` (`DependencyObject`)
- **Depended on by**:
- Unknown (no references in provided source). Likely used as a base for domain-specific ViewModels.
#### `BaseViewModel<TModel>`
- **Dependencies**:
- `DTS.Common.Events` (`ShowStatus`, `StatusInfo`, `Strings.Strings`)
- `DTS.Common.Interactivity` (`InteractionRequest<Confirmation>`)
- `Prism.Commands` (`DelegateCommand<object>`)
- `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<T>`**:
`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<TModel>` Hides `IsBusy`/`IsDirty` Semantics**:
Unlike `ViewModelBase<T>`, where `IsBusy` and `IsDirty` are `protected set`, `BaseViewModel<TModel>` 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<T>` Lacks Constructor Injection**:
No constructor accepts dependencies (e.g., `IEventAggregator`). `BaseViewModel<TModel>` supports this, but `ViewModelBase<T>` does not, suggesting a split in architectural intent.
- **Missing `BasePropertyChanged` Source**:
`BaseViewModel<TModel>` 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<TModel>` does not wrap status publishing in a `try/catch`. Exceptions during initialization may crash the UI thread.
- **`ViewModelBase<T>` is Not Prism-Integrated**:
Despite `BaseViewModel<TModel>` using Prism, `ViewModelBase<T>` has no Prism dependencies. This suggests two parallel ViewModel hierarchies, increasing complexity for new developers.
- **`DoRefresh` Signature Ambiguity**:
`DoRefresh(Func<T> 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<TModel>`**:
`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<T>`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<T>`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<TModel>` 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<T>`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<TModel>`s `Initialize()` Publishes Status Without Checking `IsBusy`**:
`Initialize()` publishes `StatusState.Busy` even if `IsBusy` is already `true`. This may cause redundant UI updates.
- **`ViewModelBase<T>` 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<TModel>`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<T>`s `IsDirty` is Not Reset by `Cleanup()`**:
`Cleanup()` in `BaseViewModel<TModel>` sets `Model = default(TModel)` but does *not* reset `IsDirty`. Derived classes must handle this explicitly.
- **`ViewModelBase<T>`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<TModel>`s `IsBusyMessage` is Unused**:
The property `IsBusyMessage` exists but is never used in the provided code. Its purpose is unclear.
- **`ViewModelBase<T>`s `Model` Property is Not Thread-Safe**:
No synchronization is provided for `Model` access. Concurrent reads/writes may cause race conditions.
- **`BaseViewModel<TModel>`s `Initialize()` Methods Do Not Set `IsBusy`**:
Publishing `ShowStatus` with `StatusState.Busy` does *not* set `IsBusy = true`. Derived classes must manage `IsBusy` separately.
- **`ViewModelBase<T>`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<TModel>`s `Cleanup()` Does Not Clear `IsDirty`**:
`Cleanup()` sets `Model = default(TModel)` but leaves `IsDirty` unchanged. This may cause stale dirty state after cleanup.
- **`ViewModelBase<T>`s `IsDirty` is Not Used in `Cleanup()`**:
No logic ties `IsDirty` to cleanup behavior (e.g., prompting to save).
- **`BaseViewModel<TModel>`s `Initialize()` Publishes to `ShowStatus` Without Checking `Aggregator`**:
If `Aggregator` is `null` (e.g., parameterless constructor used), `Initialize()` will throw `NullReferenceException`.
- **`ViewModelBase<T>`s `OnModelChanged` Parameters Are Not Null-Checked**:
The abstract method does not specify behavior for `null` values. Derived implementations must handle `null` explicitly.
- **`BaseViewModel<TModel>`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<T>`s `Model` Property is Not Observed by `INotifyPropertyChanged`**:
Setting `Model` does not raise `PropertyChanged("Model")`. Binding to `Model` will not update.
- **`BaseViewModel<TModel>`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<T>`s `IsDirty` is Not Thread-Safe**:
No synchronization for `IsDirty` access. Concurrent reads/writes may cause race conditions.
- **`BaseViewModel<TModel>`s `IsBusy` and `IsDirty` Are Not Thread-Safe**:
No synchronization for `IsBusy`/`IsDirty` access. Concurrent reads/writes may cause race conditions.
- **`ViewModelBase<T>`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<TModel>`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<T>`s `IsBusy` and `IsDirty` Are Not Thread-Safe**:
No synchronization for `IsBusy`/`IsDirty` access. Concurrent reads/writes may cause race conditions.
- **`BaseViewModel<TModel>`s `IsBusy` and `IsDirty` Are Not Thread-Safe**:
No synchronization for `IsBusy`/`IsDirty` access. Concurrent reads/writes may cause race conditions.
- **`ViewModelBase<T>`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<TModel>`s `IsBusy` and `IsDirty` Are Not Observed by `INotifyPropertyChanged`**: