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,174 @@
---
source_files:
- Common/DTS.Common/Base/Classes/BaseUserControl.cs
- Common/DTS.Common/Base/Classes/BasePropertyChanged.cs
- Common/DTS.Common/Base/Classes/DisplayResourceAttribute.cs
- Common/DTS.Common/Base/Classes/DescriptionResourceAttribute.cs
- Common/DTS.Common/Base/Classes/DynamicTypeDescriptor.cs
generated_at: "2026-04-16T03:27:54.091271+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "7d5e0417dd645280"
---
# Classes
### **Purpose**
This module provides foundational infrastructure for WPF-based UI components and data binding within the DTS system, specifically enabling property change notification, dynamic property metadata customization, and localized display names/descriptions for properties and enums. It supports two primary patterns: `BaseUserControl` for WPF user controls that require `INotifyPropertyChanged` integration, and `BasePropertyChanged` for non-UI classes needing the same notification capability. Additionally, it offers attribute-based localization via `DisplayResourceAttribute` and `DescriptionResourceAttribute`, which resolve display strings from a centralized resource file (`Strings.Strings`). Finally, `DynamicTypeDescriptor` enables runtime modification of property metadata (e.g., `DisplayName`, `Category`, `ReadOnly`, `Browsable`) for use with `PropertyGrid` controls, particularly to override or extend metadata from sources like Entity Framework where attributes cannot be applied directly.
---
### **Public Interface**
#### **`BaseUserControl`**
- **Namespace**: `DTS.Common.Base.Classes`
- **Inherits**: `UserControl`
- **Abstract**: Yes
- **Constructors**: None (abstract base class)
- **Events**:
- `event PropertyChangedEventHandler PropertyChanged`
- **Methods**:
- `protected bool SetProperty<T>(ref T storage, T value, string propertyName = null)`
- Sets `storage` to `value` if not equal, raises `PropertyChanged`, and returns `true`; otherwise returns `false`. Used to implement property setters with change notification.
- `protected void OnPropertyChanged(string propertyName = null)`
- Raises the `PropertyChanged` event with the given property name (or `null` for all properties).
#### **`BasePropertyChanged`**
- **Namespace**: `DTS.Common.Base`
- **Implements**: `IBasePropertyChanged` (not shown in source, but referenced)
- **Abstract**: Yes
- **Constructors**: None (abstract base class)
- **Events**:
- `public virtual event PropertyChangedEventHandler PropertyChanged`
- **Methods**:
- `public bool SetProperty<T>(ref T storage, T value, string propertyName = null)`
- Same semantics as `BaseUserControl.SetProperty`.
- `public virtual void OnPropertyChanged(string propertyName = null)`
- Same semantics as `BaseUserControl.OnPropertyChanged`.
#### **`DisplayResourceAttribute`**
- **Namespace**: `DTS.Common.Base.Classes`
- **Inherits**: `DisplayNameAttribute`
- **Constructors**:
- `public DisplayResourceAttribute(string resourceId)`
- Stores `resourceId` for later lookup.
- **Properties**:
- `public override string DisplayName`
- Returns the localized string from `Strings.Strings.ResourceManager.GetString(resourceId)`; if not found, returns `"##ResourceNotFound##" + resourceId`.
#### **`DescriptionResourceAttribute`**
- **Namespace**: `DTS.Common.Base.Classes`
- **Inherits**: `DescriptionAttribute`
- **Constructors**:
- `public DescriptionResourceAttribute(string resourceId)`
- Stores `resourceId` for later lookup.
- **Properties**:
- `public override string Description`
- Returns the localized string from `Strings.Strings.ResourceManager.GetString(resourceId)`; if not found, returns `"##DescriptionNotFound##" + resourceId`.
#### **`DynamicTypeDescriptor`**
- **Namespace**: `DTS.Common.Base.Classes`
- **Implements**: `ICustomTypeDescriptor`, `INotifyPropertyChanged`
- **Sealed**: Yes
- **Constructors**:
- `public DynamicTypeDescriptor(Type type)`
- Initializes from a base `type`, preserving original properties, attributes, converters, and editors. Only includes browsable properties.
- **Events**:
- `public event PropertyChangedEventHandler PropertyChanged`
- **Properties**:
- `public PropertyDescriptorCollection OriginalProperties { get; }`
- `public PropertyDescriptorCollection Properties { get; }`
- `public object Component { get; private set; }`
- `public string ClassName { get; set; }`
- `public string ComponentName { get; set; }`
- **Methods**:
- `public T GetPropertyValue<T>(string name, T defaultValue)`
- Gets the value of property `name` from the `Component`, returning `defaultValue` on failure.
- `public void SetPropertyValue(string name, object value)`
- Sets the value of property `name` on the `Component`.
- `public PropertyDescriptor AddProperty(...)`
- Overloads to add a new dynamic property with specified metadata (`displayName`, `description`, `category`, `readOnly`, `hasDefaultValue`, `defaultValue`, `uiTypeEditor`).
- `public void RemoveProperty(string name)`
- Removes a property by name from `Properties`.
- `public void AddProperty(PropertyDescriptor property)`
- Adds a pre-constructed `PropertyDescriptor` to `Properties`.
- `public DynamicTypeDescriptor FromComponent(object component)`
- Creates a new `DynamicTypeDescriptor` instance bound to a specific `component`, copying original metadata and creating `DynamicProperty` wrappers for each property.
- `internal void OnValueChanged(PropertyDescriptor prop)`
- Raises `PropertyChanged` for the given property.
- `internal static T GetAttribute<T>(AttributeCollection attributes)`
- Helper to extract an attribute of type `T` from a collection.
#### **`DynamicTypeDescriptor.DynamicProperty`** *(nested class)*
- **Namespace**: `DTS.Common.Base.Classes.DynamicTypeDescriptor`
- **Inherits**: `PropertyDescriptor`, `INotifyPropertyChanged`
- **Internal**: Yes
- **Constructors**:
- `internal DynamicProperty(DynamicTypeDescriptor descriptor, Type type, object value, string name, Attribute[] attrs)`
- `internal DynamicProperty(DynamicTypeDescriptor descriptor, PropertyDescriptor existing, object component)`
- **Events**:
- `public event PropertyChangedEventHandler PropertyChanged`
- **Properties**:
- `public object Value { get; set; }`
- `public override AttributeCollection Attributes { get; }`
- `public override bool CanResetValue(object component)`
- `public override Type ComponentType { get; }`
- `public override object GetValue(object component)`
- `public override string Category { get; }`
- `public override string Description { get; }`
- `public override string DisplayName { get; }`
- `public override bool IsBrowsable { get; }`
- `public override bool IsReadOnly { get; }`
- `public override Type PropertyType { get; }`
- **Methods**:
- `public void RemoveAttributesOfType<T>()`
- Removes all attributes of type `T` from the internal `_attributes` list.
- `public void SetCategory(string category)`
- `public void SetDescription(string description)`
- `public void SetDisplayName(string displayName)`
- `public void SetBrowsable(bool browsable)`
- `public void SetIsReadOnly(bool readOnly)`
- `public override void ResetValue(object component)`
- `public override void SetValue(object component, object value)`
- `public override bool ShouldSerializeValue(object component)`
- `public override object GetEditor(Type editorBaseType)`
- `public void SetEditor(Type editorBaseType, object obj)`
- `public IList<Attribute> AttributesList { get; }`
---
### **Invariants**
- `SetProperty<T>` **must** compare `storage` and `value` using `Equals`, and only update `storage` and raise `PropertyChanged` if they differ.
- `OnPropertyChanged` **must** be thread-safe in the sense that it safely invokes `PropertyChanged?.Invoke(...)` (null-conditional operator used).
- `DisplayResourceAttribute.DisplayName` and `DescriptionResourceAttribute.Description` **must** fall back to `"##ResourceNotFound##" + resourceId` or `"##DescriptionNotFound##" + resourceId` respectively if the resource string is missing.
- `DynamicTypeDescriptor` **must** only include *browsable* properties in its `Properties` collection during construction.
- `DynamicTypeDescriptor.FromComponent` **must** create a *shallow copy* of internal fields (`_typeConverter`, `_editors`, `_attributes`, `_events`, `OriginalProperties`) and wrap each property in a new `DynamicProperty` instance bound to the new `component`.
- `DynamicProperty` overrides of `DisplayName`, `Description`, `Category`, `IsBrowsable`, and `IsReadOnly` **must** prioritize instance-set values (e.g., `_displayName`, `_browsable`) over base/attribute values.
- `DynamicProperty.SetValue` and `ResetValue` **must** call `_descriptor.OnValueChanged(this)` after modifying the value.
---
### **Dependencies**
- **Depends on**:
- `System.ComponentModel` (`INotifyPropertyChanged`, `PropertyChangedEventArgs`, `PropertyDescriptor`, `AttributeCollection`, etc.)
- `System.Windows.Controls` (`UserControl`)
- `System.Drawing.Design` (`UITypeEditor`, `EditorAttribute`)
- `Strings.Strings` (a generated resource class, likely from `Strings.resx`, accessed via `Strings.Strings.ResourceManager`)
- **Used by**:
- UI components inheriting from `BaseUserControl` (e.g., custom WPF user controls).
- View models or domain classes inheriting from `BasePropertyChanged`.
- Code that dynamically configures `PropertyGrid` metadata (e.g., for configuration editors), using `DynamicTypeDescriptor`.
- Code that requires localized property names/descriptions via `DisplayResourceAttribute`/`DescriptionResourceAttribute` (e.g., on enum fields or properties).
---
### **Gotchas**
- **`DynamicTypeDescriptor` does not support adding properties *after* `FromComponent` is called**: Properties added via `AddProperty` to the *original* `DynamicTypeDescriptor` are not reflected in instances created via `FromComponent`, as it performs a shallow copy of `Properties` at construction time.
- **`DynamicProperty`s `IsReadOnly` logic is inconsistent**: It checks `_readOnly.HasValue`, but if `null`, falls back to `_existing.IsReadOnly` *or* inspects `ReadOnlyAttribute` on the *current* `Attributes`—not necessarily the original propertys metadata.
- **`DisplayResourceAttribute` and `DescriptionResourceAttribute` assume resource IDs follow a strict naming convention** (e.g., `PropertyName_Description`), but the attributes themselves accept arbitrary `resourceId` strings. Mismatched IDs cause silent fallback to `"##...NotFound##"`.
- **`DynamicTypeDescriptor.FromComponent` performs a shallow copy of `_editors` and `_attributes`**: Modifications to these collections in one instance may affect others.
- **`BaseUserControl` and `BasePropertyChanged` duplicate identical functionality** (`SetProperty`, `OnPropertyChanged`). This duplication suggests a possible refactor opportunity (e.g., shared base or interface), but is preserved for architectural reasons (e.g., avoiding inheritance conflicts with `UserControl`).
- **No validation or exception handling in `GetPropertyValue`/`SetPropertyValue` beyond `ArgumentNullException`**: Casting failures in `GetPropertyValue` return `defaultValue` silently, which may mask data type mismatches.
- **`DynamicTypeDescriptor` does not implement `ICustomTypeDescriptor` methods fully for all overloads** (e.g., `GetEvents(Attribute[])` ignores the filter).
- **`DynamicProperty`s `ShouldSerializeValue` always returns `false` unless backed by `_existing`**, which may interfere with serialization logic expecting persistence of dynamic properties.
- **None of the `Set*` methods on `DynamicProperty` (e.g., `SetDisplayName`) raise `PropertyChanged`**, so UI bound to these metadata properties will not update automatically.

View File

@@ -0,0 +1,123 @@
---
source_files:
- Common/DTS.Common/Base/Interface/IBaseClass.cs
- Common/DTS.Common/Base/Interface/IViewModel.cs
- Common/DTS.Common/Base/Interface/IBaseView.cs
- Common/DTS.Common/Base/Interface/IBaseWindow.cs
- Common/DTS.Common/Base/Interface/IBaseModel.cs
- Common/DTS.Common/Base/Interface/IBasePropertyChanged.cs
- Common/DTS.Common/Base/Interface/IHeaderInfoProvider.cs
- Common/DTS.Common/Base/Interface/IBaseWindowModel.cs
- Common/DTS.Common/Base/Interface/IBaseViewModel.cs
generated_at: "2026-04-16T03:27:09.388583+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "10328346e97dc4f0"
---
# DTS.Common.Base Interfaces Documentation
## 1. Purpose
This module defines a foundational set of interfaces for implementing the MVVM (Model-View-ViewModel) pattern within the DTS application architecture. It establishes contracts for core components—`IBaseModel`, `IBaseViewModel`, `IBaseWindowModel`, `IBaseView`, `IBaseWindow`, and `IBaseClass`—that enforce standardized lifecycle management (initialization, activation, cleanup), state tracking (e.g., `IsDirty`, `IsBusy`, `IsSaved`), and data binding support via `INotifyPropertyChanged`. These interfaces decouple UI and business logic layers, enabling consistent behavior across views, viewmodels, and models while supporting asynchronous initialization and cleanup workflows.
## 2. Public Interface
### Interfaces
- **`IBaseClass`**
- *Definition*: `public interface IBaseClass : IBasePropertyChanged { }`
- *Behavior*: Serves as a base interface for classes requiring property change notification. Currently acts as a marker interface extending `IBasePropertyChanged`.
- **`IViewModel`**
- *Definition*: `public interface IViewModel { object Model { get; set; } }`
- *Behavior*: Provides a contract for viewmodels to expose and bind to a backing `Model` object.
- **`IBaseView`**
- *Definition*: `public interface IBaseView { object DataContext { get; set; } }`
- *Behavior*: Defines a view contract with a `DataContext` property for data binding (typically to a ViewModel).
- **`IBaseWindow`**
- *Definition*: `public interface IBaseWindow { object DataContext { get; set; } }`
- *Behavior*: Similar to `IBaseView`, but intended for window-level UI components; exposes `DataContext` for binding.
- **`IBaseModel`**
- *Definition*: `public interface IBaseModel : IBasePropertyChanged { bool IsSaved { get; } }`
- *Behavior*: Extends `IBasePropertyChanged` and adds a read-only `IsSaved` property indicating whether the model has been persisted.
- **`IBasePropertyChanged`**
- *Definition*: `public interface IBasePropertyChanged : INotifyPropertyChanged { void OnPropertyChanged(string propertyName); }`
- *Behavior*: Extends `INotifyPropertyChanged` and declares a required `OnPropertyChanged` method for raising change notifications.
- **`IHeaderInfoProvider<T>`**
- *Definition*: `public interface IHeaderInfoProvider<T> { T HeaderInfo { get; } }`
- *Behavior*: Provides a strongly-typed `HeaderInfo` property for binding header metadata (e.g., title, subtitle) from XAML.
- **`IBaseWindowModel`**
- *Definition*:
```csharp
public interface IBaseWindowModel : INotifyPropertyChanged
{
bool IsMenuIncluded { get; set; }
bool IsNavigationIncluded { get; set; }
bool IsBusy { get; set; }
void Activated();
bool IsDirty { get; }
void Cleanup();
Task CleanupAsync();
void Initialize();
void Initialize(object parameter);
Task InitializeAsync();
Task InitializeAsync(object parameter);
}
```
- *Behavior*: Defines lifecycle and state management for window-level models. Includes properties for UI control visibility (`IsMenuIncluded`, `IsNavigationIncluded`), busy state (`IsBusy`), dirty state (`IsDirty`), and methods for initialization/activation/cleanup (synchronous and asynchronous variants).
- **`IBaseViewModel`**
- *Definition*:
```csharp
public interface IBaseViewModel : IBasePropertyChanged
{
bool IsMenuIncluded { get; set; }
bool IsNavigationIncluded { get; set; }
bool IsBusy { get; set; }
void Activated();
bool IsDirty { get; }
void Cleanup();
Task CleanupAsync();
void Initialize();
void Initialize(object parameter);
void Initialize(object parameter, object model);
Task InitializeAsync();
Task InitializeAsync(object parameter);
}
```
- *Behavior*: Defines lifecycle and state management for viewmodels. Extends `IBasePropertyChanged` and includes all lifecycle methods of `IBaseWindowModel`, plus an overloaded `Initialize` method accepting both `parameter` and `model` arguments.
## 3. Invariants
- **`IBasePropertyChanged.OnPropertyChanged` must be called** whenever a property value changes, passing the exact property name as a string.
- **`IsDirty` is read-only** in both `IBaseViewModel` and `IBaseWindowModel`; implementations must manage its state internally (e.g., via private setters or logic).
- **`IsSaved` is read-only** in `IBaseModel`; implementations must ensure it reflects the persistence state of the model.
- **`IsBusy`, `IsMenuIncluded`, and `IsNavigationIncluded` are read-write** in `IBaseViewModel` and `IBaseWindowModel`; they must be observable via `INotifyPropertyChanged`.
- **Lifecycle methods (`Initialize`, `Cleanup`, `Activated`) must be idempotent or explicitly documented for non-idempotency**; the interface does not enforce ordering, but implementations should avoid re-initializing after cleanup.
- **`IBaseWindowModel` does not extend `IBasePropertyChanged`** (it directly implements `INotifyPropertyChanged`), while `IBaseViewModel` extends `IBasePropertyChanged`. This is an intentional distinction in the design.
## 4. Dependencies
- **Depends on**:
- `System.ComponentModel` (for `INotifyPropertyChanged` in `IBasePropertyChanged`, `IBaseWindowModel`, and `IBaseViewModel`).
- `System.Threading.Tasks` (for `Task` in `IBaseWindowModel` and `IBaseViewModel`).
- **Used by**:
- Viewmodel and model implementations throughout the codebase (e.g., concrete classes implementing `IBaseViewModel`, `IBaseModel`, `IBaseWindowModel`).
- UI framework components (e.g., views/windows implementing `IBaseView`/`IBaseWindow`) to bind to viewmodels/models.
- XAML binding systems via `IHeaderInfoProvider<T>` for header metadata.
- **No direct dependencies on other DTS modules** are evident from the source files alone.
## 5. Gotchas
- **`IBasePropertyChanged` declares `OnPropertyChanged(string propertyName)` but does not enforce null-safety or validation**; callers must ensure `propertyName` is non-null and accurate (e.g., using `nameof()`).
- **`IBaseWindowModel` and `IBaseViewModel` share overlapping members but are not related via inheritance** (e.g., `IBaseWindowModel` does not extend `IBaseViewModel`), which may lead to duplication or confusion.
- **`Initialize(object parameter, object model)` in `IBaseViewModel` is absent in `IBaseWindowModel`**—this asymmetry may cause inconsistencies if window models require model injection.
- **`IsDirty` has no setter**; implementations must define how dirty state is tracked (e.g., via property change handlers or explicit `SetDirty()` methods).
- **No default implementations are provided**—consumers must implement all interface members, including boilerplate `INotifyPropertyChanged` handling.
- **`IBaseClass` is currently empty beyond inheritance**; its purpose is unclear without further context (possibly legacy or placeholder).
- **No guidance on thread affinity** for lifecycle methods (e.g., `InitializeAsync`/`CleanupAsync`), which may lead to cross-thread UI violations if not handled carefully.
- **`IHeaderInfoProvider<T>` is generic but `T` is unconstrained**; implementations must ensure `T` is serializable or bindable if used in XAML.
*None identified beyond the above.*

View File

@@ -0,0 +1,44 @@
---
source_files:
- Common/DTS.Common/Base/Model/BaseModel.cs
generated_at: "2026-04-16T03:28:03.611960+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "8eb3b4d63e76d9ee"
---
# Model
## Documentation: `BaseModel<TModel>` Class
### 1. Purpose
`BaseModel<TModel>` is an abstract base class intended to provide foundational functionality for model objects within the DTS system. It encapsulates a generic `Model` property (of type `TModel`) and tracks whether the model instance has been persisted (via the `IsSaved` flag). It inherits from `BasePropertyChanged`, implying support for property change notifications (e.g., for UI binding), and implements `IBaseModel`, suggesting integration with a broader model abstraction layer. Its primary role is to standardize model representation and persistence state tracking across derived model types.
### 2. Public Interface
- **`public TModel Model { get; set; }`**
Gets or sets the underlying model object. This is the core data container the `BaseModel<TModel>` wraps.
- **`public bool IsSaved { get; private set; }`**
Gets a boolean indicating whether the model has been saved (e.g., to a database or other persistent store). The setter is `private`, meaning only the class itself (or derived classes via base access) can update this flag—typically after a successful save operation.
- **`public BaseModel()`**
Public parameterless constructor. Allows instantiation of concrete derived classes. Note: Although the class is `abstract`, the constructor is public—this is a known pattern in C# for abstract classes to enable derived-class initialization.
### 3. Invariants
- `Model` may be `null` unless constrained by derived implementations (no explicit null-checking or initialization is present in this base class).
- `IsSaved` starts as `false` by default (C# default for `bool`) and can only be set to `true` internally (e.g., by derived classes or the base class itself).
- `BaseModel<TModel>` enforces that `TModel` must be a reference type (`class`), as specified by the generic constraint.
### 4. Dependencies
- **Inherits from**: `BasePropertyChanged` — implies reliance on a custom property change notification infrastructure (likely implementing `INotifyPropertyChanged` or similar).
- **Implements**: `IBaseModel` — suggests a contract interface defined elsewhere in the codebase (not shown here).
- **No external NuGet or framework dependencies** are evident from this file alone (aside from standard .NET types like `System` implicitly used by `bool`, `class`, etc.).
- **Used by**: Any concrete model class that derives from `BaseModel<TModel>`. Likely consumed by data persistence services, view models, or UI layers that rely on `IBaseModel`.
### 5. Gotchas
- The constructor is `public` despite the class being `abstract`. While valid in C#, this may mislead developers into attempting direct instantiation (which would fail at compile time due to `abstract`). Ensure documentation clarifies that only derived classes may be instantiated.
- `IsSaved` has no public setter, but no corresponding `MarkAsSaved()` or similar method is exposed—derived classes must manage this state internally (e.g., via internal methods or direct assignment in constructors). This could lead to inconsistent state if not handled carefully.
- No validation or lifecycle hooks (e.g., `OnSaving`, `OnSaved`) are defined in this base class. Persistence logic must be implemented in derived classes.
- The commented-out `//DependencyObject` suggests past or potential future integration with WPFs dependency property system, but no such dependency is active in the current implementation.
- **None identified from source alone** regarding behavioral quirks beyond the above structural observations.

View File

@@ -0,0 +1,98 @@
---
source_files:
- Common/DTS.Common/Base/View/BaseWindow.cs
- Common/DTS.Common/Base/View/BaseView.cs
generated_at: "2026-04-16T03:28:18.795367+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "5f5625420afe05bf"
---
# View
## Documentation: `DTS.Common.Base` View Base Classes
---
### 1. Purpose
This module provides foundational WPF view base classes (`BaseWindow` and `BaseView`) that standardize common UI behavior across the application. Specifically, they expose two key properties—`IsDirty` and `HeaderInfo`—by delegating to corresponding interfaces implemented by the `DataContext`. This enables consistent dirty-state tracking and tab header rendering across different view types (windows and user controls) without requiring each view to reimplement the logic.
---
### 2. Public Interface
#### `BaseWindow` (inherits `System.Windows.Window`, implements `IBaseWindow`)
- **`public bool IsDirty { get; }`**
Returns `true` if the `DataContext` implements `IBaseWindowModel` *and* its `IsDirty` property is `true`; otherwise `false`. Returns `false` if `DataContext` is `null` or does not implement `IBaseWindowModel`.
- **`public string HeaderInfo { get; }`**
Returns the `HeaderInfo` string from the `DataContext` if it implements `IHeaderInfoProvider<string>`; otherwise returns `string.Empty`. Returns `string.Empty` if `DataContext` is `null`.
#### `BaseView` (inherits `System.Windows.Controls.UserControl`, implements `IBaseView`)
- **`public bool IsDirty { get; }`**
Returns `true` if the `DataContext` implements `IBaseViewModel` *and* its `IsDirty` property is `true`; otherwise `false`. Returns `false` if `DataContext` is `null` or does not implement `IBaseViewModel`.
- **`public string HeaderInfo { get; }`**
Returns the `HeaderInfo` string from the `DataContext` if it implements `IHeaderInfoProvider<string>`; otherwise returns `string.Empty`. Returns `string.Empty` if `DataContext` is `null`.
> **Note**: Both classes use *read-only* properties with no public setters. They are *passive aggregators*—they do not modify state or raise change notifications themselves.
---
### 3. Invariants
- **`IsDirty` behavior**:
- Always returns `false` if `DataContext` is `null`.
- Always returns `false` if `DataContext` does not implement the *expected interface* (`IBaseWindowModel` for `BaseWindow`, `IBaseViewModel` for `BaseView`).
- Otherwise, returns the exact value of `DataContext.IsDirty`.
- **`HeaderInfo` behavior**:
- Always returns `string.Empty` if `DataContext` is `null`.
- Always returns `string.Empty` if `DataContext` does not implement `IHeaderInfoProvider<string>`.
- Otherwise, returns the exact value of `DataContext.HeaderInfo`.
- **No side effects**: Neither property performs I/O, mutation, or triggers events. They are pure getters.
---
### 4. Dependencies
#### Internal Dependencies (from source):
- **`System.Windows`** (WPF core types: `Window`, `UserControl`)
- **`DTS.Common.Base`** namespace (same assembly/module):
- Interfaces: `IBaseWindow`, `IBaseView`, `IBaseWindowModel`, `IBaseViewModel`, `IHeaderInfoProvider<string>`
*(These interfaces are referenced but not defined in the provided source—must be defined elsewhere.)*
#### External Dependencies:
- **WPF runtime** (`PresentationFramework`, `WindowsBase`)
- **ReSharper attributes** (`// ReSharper disable once CheckNamespace` suggests use of ReSharper for namespace conventions, but no runtime dependency)
#### Inferred Consumers:
- Any WPF view that inherits from `BaseWindow` or `BaseView`.
- UI layers (e.g., `TabControl`) that bind to `HeaderInfo` and `IsDirty` on views.
- ViewModels/Models implementing `IBaseWindowModel`, `IBaseViewModel`, or `IHeaderInfoProvider<string>`.
---
### 5. Gotchas
- **Interface mismatch risk**:
`BaseWindow` expects `IBaseWindowModel` for `IsDirty`, while `BaseView` expects `IBaseViewModel`. Using the wrong model type (e.g., `IBaseViewModel` in a `BaseWindow`) will cause `IsDirty` to always return `false`, even if the model is dirty.
- **No change notification**:
These properties do *not* raise `INotifyPropertyChanged` events. If the underlying `IsDirty` or `HeaderInfo` values change, the view will *not* automatically update unless the `DataContext` itself raises property change notifications *and* the binding is set up to requery (e.g., via `BindingExpression.UpdateSource()` or `Mode=OneWay` with `INotifyPropertyChanged` on the *model*).
- **`string.Empty` vs `null`**:
`HeaderInfo` returns `string.Empty` (not `null`) when no provider is found—this avoids XAML binding errors but may require explicit handling if `null` is semantically meaningful.
- **No validation or error handling**:
If `DataContext.HeaderInfo` throws an exception, it will propagate unhandled. Similarly, if `DataContext` is a disposed or partially initialized object, behavior is undefined.
- **Ambiguous naming**:
The XML comments say "bound object data data" (double "data")—suggests legacy copy-paste or incomplete editing. Not a functional issue, but indicates possible tech debt.
- **No explicit interface implementation**:
Properties are *public* on the view classes, not explicit interface implementations. This may cause confusion if `IBaseWindow`/`IBaseView` are intended to be used as contracts.
> **None identified from source alone** for additional gotchas beyond the above.

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`**: