--- source_files: - Common/DTS.CommonCore/Behaviors/StringMetaDataAttr.cs - Common/DTS.CommonCore/Behaviors/TrimTextBoxBehavior.cs - Common/DTS.CommonCore/Behaviors/InteractivityTemplate.cs - Common/DTS.CommonCore/Behaviors/TextBoxPasteBehavior.cs - Common/DTS.CommonCore/Behaviors/MultiSelectionBehavior.cs generated_at: "2026-04-16T02:15:57.710410+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "c2b7aea8fc779731" --- # Behaviors ## Documentation: `DTS.Common.Behaviors` Module --- ### 1. Purpose This module provides a set of WPF-specific behaviors and utility attributes to extend UI control functionality and support metadata annotations on types. It enables declarative attachment of cross-cutting concerns—such as text trimming on `TextBox` loss of focus, paste interception with custom command execution, multi-selection synchronization between a `ListBox` and an external `IList`, and dynamic injection of interactivity elements via templates—without modifying control logic directly. The module also includes a generic attribute (`StringMetaDataAttr`) for associating arbitrary string metadata with types or enum values, primarily for documentation or UI labeling purposes. --- ### 2. Public Interface #### `StringMetaDataAttr` - **`public string MetaData { get; }`** Read-only property storing the metadata string provided at construction. - **`internal StringMetaDataAttr(string attr)`** Internal constructor; sets `MetaData` to `attr`. - **`public static string GetStringMetaData(object o)`** Retrieves the `MetaData` string from a `StringMetaDataAttr` applied to the *enum value* or *type* represented by `o`. - Uses reflection on `o.GetType().GetMember(o.ToString())` to find the member. - Returns `null` if no attribute is found or if `o` is `null`/member not found. #### `TrimTextBoxBehavior` - **`public class TrimTextBoxBehavior : Behavior`** Attaches to a `TextBox` and trims its `Text` property on `LostFocus`. - On attachment: subscribes to `LostFocus`. - On `LostFocus`: trims `Text`, updates binding source if changed. - On detachment: unsubscribes from `LostFocus`. #### `InteractivityItems` - **`public class InteractivityItems : FrameworkElement`** Holds collections of behaviors and triggers for dynamic injection. - **`public List Triggers { get; }`** Lazy-initialized list of triggers. - **`public List Behaviors { get; }`** Lazy-initialized list of behaviors. - **Attached Property: `Template` (`InteractivityTemplate`)** When set on a `DependencyObject`, loads content from the `InteractivityTemplate`, extracts its behaviors/triggers, and adds them to the target object’s `Interaction.GetBehaviors()` and `Interaction.GetTriggers()` collections. #### `TextBoxPasteBehavior` - **`public static readonly DependencyProperty PasteCommandProperty`** Attached dependency property for `PasteCommand`. - **`public static ICommand GetPasteCommand(DependencyObject target)`** Gets the `PasteCommand` value. - **`public static void SetPasteCommand(DependencyObject target, ICommand value)`** Sets the `PasteCommand` value. - **`private static void PasteCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)`** Attaches/detaches handler for `CommandManager.ExecutedEvent` on the target `TextBox` (or its `MainEditBox` if `ChannelCodeBuilder`/`ChannelNameBuilder`). - **`private static void CommandExecuted(object sender, RoutedEventArgs e)`** Handles `ApplicationCommands.Paste` (or custom `RoutedUICommand` named `"Paste"`). - Retrieves text from `Clipboard`. - Calls `command.Execute(textBox)` if `command.CanExecute(null)` is true. - Sets `e.Handled = true` to suppress default paste. - On exception, publishes `PageErrorEvent` via `IEventAggregator`. #### `MultiSelectionBehavior` - **`public class MultiSelectionBehavior : Behavior`** Synchronizes `SelectedItems` of a `ListBox` with an external `IList`. - **`public IList SelectedItems { get; set; }`** Dependency property backing the external list to sync with. - **`public static readonly DependencyProperty SelectedItemsProperty`** Registered dependency property. - **`private static void SelectedItemsChanged(...)`** Handles changes to `SelectedItems`: - Clears `ListBox.SelectedItems` and repopulates from new value. - Subscribes/unsubscribes to `CollectionChanged` on the new/old value and `SelectionChanged` on `AssociatedObject`. - **`private void SourceCollectionChanged(...)`** Updates `AssociatedObject.SelectedItems` when the external `IList` changes (e.g., via `INotifyCollectionChanged`). - **`private void ListBoxSelectionChanged(...)`** Updates the external `SelectedItems` list when `ListBox` selection changes. - Uses `SelectedItemsStatus.SetUpdating(...)` to suppress notifications during bulk updates. - Skips adding items if already present. - Logs exceptions via `APILogger.Log(ex)`. --- ### 3. Invariants - **`StringMetaDataAttr.GetStringMetaData(object o)`** - Only inspects the *member name* derived from `o.ToString()`. For enums, this is the enum *name* (e.g., `"Red"`), not the value. - Returns `null` if `o` is `null`, or if `o.GetType().GetMember(...)` yields no results. - Does *not* search base types or interfaces—only the exact member named by `o.ToString()`. - **`TrimTextBoxBehavior`** - Only trims on `LostFocus`; no trimming occurs during typing or on `TextChanged`. - Binding update is triggered via `UpdateSource()` only if trimming actually changed the text. - **`InteractivityItems.Template`** - The `InteractivityTemplate.LoadContent()` must return a `FrameworkElement` containing `InteractivityItems` (or compatible structure); otherwise, behavior injection may fail silently. - `OnTemplateChanged` is invoked *after* the property is set; no guarantee about timing relative to visual tree load. - **`TextBoxPasteBehavior`** - Only intercepts `Paste` if `PasteCommand` is set *before* paste occurs (via `PasteCommandChanged` subscription logic). - Assumes `sender` is either a `TextBox`, `ChannelCodeBuilder`, or `ChannelNameBuilder`; otherwise, `textBox` may be `null` (though `PasteCommandChanged` and `CommandExecuted` both attempt fallbacks). - `Clipboard.GetText()` may throw; exceptions are caught and published as `PageErrorEvent`. - **`MultiSelectionBehavior`** - `SelectedItems` must be an `IList` (not `IEnumerable`) to support `Add`/`Remove`. - Type compatibility is checked via `type.IsAssignableFrom(item.GetType())`; items failing this are silently skipped (exception logged). - `_isUpdatingSource` and `_isUpdatingTarget` flags prevent infinite loops during bidirectional sync. - `SelectedItemsStatus.SetUpdating(...)` is used but *not defined in this module*—implies dependency on `DTS.Common.Utilities` (see *Dependencies*). --- ### 4. Dependencies #### Internal Dependencies (from imports) - **WPF Framework**: - `System.Windows`, `System.Windows.Controls`, `System.Windows.Interactivity` (for `Behavior`, `Interaction`, `TriggerBase`). - `System.Windows.Input` (for `ICommand`, `RoutedEventHandler`, `ExecutedRoutedEventArgs`, `ApplicationCommands`). - **Prism Library**: - `Microsoft.Practices.Prism.Events` (`IEventAggregator`, `EventAggregator`). - `Microsoft.Practices.ServiceLocation` (`ServiceLocator`). - **Custom Types**: - `DTS.Common.Controls.ChannelCodeBuilder`, `DTS.Common.Controls.ChannelNameBuilder` (access `MainEditBox`). - `DTS.Common.Utilities.Logging.APILogger` (used in `MultiSelectionBehavior`). - `DTS.Common.Events.PageErrorEvent`, `PageErrorArg` (used in `TextBoxPasteBehavior`). - `DTS.Common.Utilities.SelectedItemsStatus` (used in `MultiSelectionBehavior` for `SetUpdating`). #### External Dependencies - **Behavioral**: - `TrimTextBoxBehavior`, `MultiSelectionBehavior`, `InteractivityItems` require `System.Windows.Interactivity` (Blend SDK). - `TextBoxPasteBehavior` requires `System.Windows.Interactivity` indirectly via `Interaction` usage (though not directly instantiated here). #### Consumers (inferred) - Likely used in XAML via `Interaction.Behaviors`, `InteractivityItems.Template`, or attached properties (`TextBoxPasteBehavior.PasteCommand`). - `StringMetaDataAttr` is likely applied to enums or classes in other modules (e.g., `enum Status { [StringMetaData("Active")] Active }`). --- ### 5. Gotchas - **`StringMetaDataAttr.GetStringMetaData(object o)`**: - **Fails for enum *values* with `Flags` attribute** if `o.ToString()` returns a combined string (e.g., `"Red | Blue"`), as `GetMember("Red | Blue")` will not match any member. - **Does not handle nested types**—only top-level members named by `o.ToString()`. - **`TrimTextBoxBehavior`**: - Trimming may cause caret position loss or unexpected selection changes (not mitigated). - `UpdateSource()` may trigger validation errors or other side effects in the binding pipeline. - **`InteractivityItems.Template`**: - `OnTemplateChanged` assumes `interactivityTemplate.LoadContent()` returns an `InteractivityItems` instance. If not, casting to `(InteractivityItems)` will throw. - No deduplication: adding the same behavior/trigger multiple times via `Template` is possible. - **`TextBoxPasteBehavior`**: - **Critical bug in `CommandExecuted`**: ```csharp TextBox textBox = sender as TextBox; if (null == sender && sender is ChannelCodeBuilder ccb) // ← Always false: sender cannot be null *and* be a ChannelCodeBuilder ``` Should be `if (null == textBox && sender is ChannelCodeBuilder ccb)`. Same for `ChannelNameBuilder`. This will cause `textBox` to remain `null`, leading to `NullReferenceException` when accessing `textBox.RemoveHandler(...)` or `textBox.AddHandler(...)`. - `Clipboard.GetText()` may throw `ExternalException` (e.g., clipboard in use); caught but logged generically. - **`MultiSelectionBehavior`**: - `SelectedItemsStatus.SetUpdating(...)` is used but *not defined in this module*—if `SelectedItemsStatus` is missing or misconfigured, notification suppression may fail, causing performance issues or infinite loops. - `itemsToAdd.Last()` check is inefficient for large lists (O(n) per item). - `selectedItems.Contains(item)` is O(n) per item; for large lists, this may be a performance bottleneck. - No handling of `NotifyCollectionChangedAction.Replace`—only `Add`, `Remove`, and `Reset`. - **General**: - All behaviors assume WPF UI thread context (no dispatcher marshaling). - No null-safety for `AssociatedObject` in `OnDetaching`/`OnAttached` (relies on `Behavior` base class to guard). - `StringMetaDataAttr` is `internal`—cannot be instantiated outside this module, limiting extensibility. None identified beyond the above.