init
This commit is contained in:
302
enriched-qwen3-coder-next/Common/DTS.Common/Base/ViewModel.md
Normal file
302
enriched-qwen3-coder-next/Common/DTS.Common/Base/ViewModel.md
Normal 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 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<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`**:
|
||||
|
||||
Reference in New Issue
Block a user