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.