This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
---
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`**: