180 lines
11 KiB
Markdown
180 lines
11 KiB
Markdown
|
|
---
|
|||
|
|
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<TextBox>`**
|
|||
|
|
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<TriggerBase> Triggers { get; }`**
|
|||
|
|
Lazy-initialized list of triggers.
|
|||
|
|
- **`public List<Behavior> 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<ListBox>`**
|
|||
|
|
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<T>`, `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<T>` base class to guard).
|
|||
|
|
- `StringMetaDataAttr` is `internal`—cannot be instantiated outside this module, limiting extensibility.
|
|||
|
|
|
|||
|
|
None identified beyond the above.
|