--- 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(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(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(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(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()` - 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 AttributesList { get; }` --- ### **Invariants** - `SetProperty` **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 property’s 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.