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

174 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
source_files:
- Common/DTS.Common/Base/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.