Files
DP44/enriched-qwen3-coder-next/Common/DTS.Common/RibbonControl.md

163 lines
10 KiB
Markdown
Raw Normal View History

2026-04-17 14:55:32 -04:00
---
source_files:
- Common/DTS.Common/RibbonControl/RibbonControlSelectionChanged.cs
- Common/DTS.Common/RibbonControl/RibbonControlOperation.cs
- Common/DTS.Common/RibbonControl/RibbonControlSelectionEventArgs.cs
- Common/DTS.Common/RibbonControl/RibbonControlSelectionChangeBehavior.cs
- Common/DTS.Common/RibbonControl/RibbonRegionAdapter.cs
generated_at: "2026-04-16T02:56:16.038197+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "bf8430cdc3eeb064"
---
# RibbonControl
## Documentation: Ribbon Control Selection Module
---
### 1. Purpose
This module provides infrastructure for integrating Prism-based region management with the Microsoft Ribbon control (`Microsoft.Windows.Controls.Ribbon.Ribbon`) and for publishing selection-change events when ribbon tabs are added, removed, or selected. It enables decoupled communication between ribbon UI components and view models via Prisms event aggregation system. Specifically, it adapts the `Ribbon` control to Prism regions, manages the addition/removal of `RibbonTab` and `RibbonApplicationMenu` items, and publishes `RibbonControlSelectionChanged` events whenever tab selection changes (e.g., due to user interaction or programmatic activation). This supports view synchronization and state management in a modular WPF application.
---
### 2. Public Interface
#### `RibbonControlSelectionChanged`
- **Type**: `class` inheriting from `PubSubEvent<RibbonControlSelectionEventArgs>`
- **Behavior**: A Prism `PubSubEvent` used to broadcast tab selection changes. Subscribers receive `RibbonControlSelectionEventArgs` instances indicating the operation type (`AddedItem` or `RemovedItem`) and the affected item.
#### `RibbonControlOperation`
- **Type**: `enum`
- **Values**:
- `AddedItem`: Indicates an item was added to the ribbon (e.g., a tab).
- `RemovedItem`: Indicates an item was removed from the ribbon.
#### `RibbonControlSelectionEventArgs`
- **Constructor**:
`RibbonControlSelectionEventArgs(RibbonControlOperation operation, object item)`
- **Parameters**:
- `operation`: The operation type (`AddedItem` or `RemovedItem`).
- `item`: The ribbon item (typically a `RibbonTab`) involved in the operation.
- **Properties**:
- `Operation`: Gets the operation type.
- `Item`: Gets the added/removed item (strongly typed as `object`; callers must cast appropriately).
#### `RibbonControlSelectionChangeBehavior`
- **Type**: `class` inheriting from `Behavior<Ribbon>`
- **Properties**:
- `TargetRibbonControl`: `Ribbon` dependency property (note: backing field name is `TargetRibbonProperty`, but property name is `TargetRibbonControl`).
- **Methods**:
- `OnAttached()`: Subscribes to `Ribbon.SelectionChanged`; initializes `_eventAggregator` via `ContainerLocator.Container`.
- `OnDetaching()`: Unsubscribes from `Ribbon.SelectionChanged`.
- `Ribbon_SelectionChanged(object sender, SelectionChangedEventArgs e)`:
- Publishes **two** events per `SelectionChangedEventArgs` if both `AddedItems` and `RemovedItems` are non-empty:
- One `RibbonControlSelectionChanged` event for `AddedItem` with `e.AddedItems[0]`.
- One `RibbonControlSelectionChanged` event for `RemovedItem` with `e.RemovedItems[0]`.
- Only the *first* added/removed item is used (index `0`), ignoring additional items.
#### `RibbonRegionAdapter`
- **Type**: `class` inheriting from `RegionAdapterBase<Ribbon>`, implements `IDisposable`
- **Constructor**:
`RibbonRegionAdapter(IRegionBehaviorFactory, IRegionManager, IEventAggregator)`
- Subscribes to `RibbonControlSelectionChanged` event (note: uses `TabControlSelectionChanged`, which is a typo—see *Gotchas*).
- **Methods**:
- `Adapt(IRegion region, Ribbon regionTarget)`:
- Hooks `region.ActiveViews.CollectionChanged` to add/remove `RibbonTab`/`RibbonApplicationMenu` items to/from `regionTarget.Items` or `regionTarget.ApplicationMenu`.
- Assigns a `Uid` (if missing) to new tabs using `Guid.NewGuid().GetHashCode().ToString(...)`.
- Applies `SortDescription("TabIndex", Ascending)` to `regionTarget.Items.SortDescriptions`.
- Sets `IsSelected` on a newly added tab if its header matches `AppSettings["DefaultRibbonTab"]` (case-insensitive).
- `AddRibbonTabToRegion(RibbonTab, Ribbon)`: Adds tab to `Items`, sets `Uid`, sorts, and optionally selects.
- `AddApplicationMenuToRegion(RibbonApplicationMenu, Ribbon)`: Assigns to `regionTarget.ApplicationMenu`.
- `RemoveRibbonTabFromRibbonRegion(RibbonTab, Ribbon)`: Removes tab from `Items`.
- `OnTabControlSelectionChanged(TabControlSelectionEventArgs)`:
- **Only processes `AddedItem` operations** (ignores `RemovedItem`).
- Attempts to synchronize tab selection based on `IRibbonTabInfoProvider.RibbonTabUid` from the views `DataContext`.
- If the selected tabs UID does not match the views expected tab UID, it programmatically selects the matching tab.
- `CreateRegion()`: Returns `new AllActiveRegion()` (all views remain active).
- **IDisposable**:
- `Dispose(bool)`: Unsubscribes from `RibbonControlSelectionChanged`.
- Finalizer (`~RibbonRegionAdapter`) calls `Dispose(false)`.
---
### 3. Invariants
- **Event publishing**:
- `RibbonControlSelectionChanged` is published **only** for the first item in `AddedItems` and `RemovedItems` (index `0`), even if multiple items change simultaneously.
- **Operation semantics**:
- `RibbonControlOperation.AddedItem` corresponds to items in `SelectionChangedEventArgs.AddedItems`.
- `RibbonControlOperation.RemovedItem` corresponds to items in `SelectionChangedEventArgs.RemovedItems`.
- **UID requirement**:
- `RibbonTab.Uid` must be set (either manually or auto-generated) for `RibbonRegionAdapter.OnTabControlSelectionChanged` to function correctly.
- **View contract**:
- For `OnTabControlSelectionChanged` to synchronize selection, the views `DataContext` must implement `IRibbonTabInfoProvider` and return a non-empty `RibbonTabUid`.
- **Thread safety**:
- `AddRibbonTabToRegion` uses a `lock (Lock)` around `Uid` assignment and `Items.Add`, but `RemoveRibbonTabFromRibbonRegion` does not. This may lead to race conditions during concurrent add/remove operations.
---
### 4. Dependencies
#### Internal Dependencies (from source):
- **Prism libraries**:
- `Prism.Events` (`PubSubEvent`, `IEventAggregator`)
- `Prism.Regions` (`RegionAdapterBase`, `IRegion`, `IRegionManager`)
- `Prism.Ioc` (`ContainerLocator`)
- **WPF & Ribbon**:
- `System.Windows` (`DependencyProperty`, `SelectionChangedEventArgs`)
- `System.Windows.Controls` (`Ribbon`, `RibbonTab`, `RibbonApplicationMenu`)
- `Microsoft.Windows.Controls.Ribbon` ( RibbonControlsLibrary)
- `Microsoft.Xaml.Behaviors` (`Behavior<T>`)
- **Common project types**:
- `DTS.Common.Classes.IBaseView`
- `DTS.Common.Enums.IRibbonTabInfoProvider`
- `DTS.Common.Events.TabControlSelectionChanged` *(note: typo in usage—see Gotchas)*
#### External Dependencies:
- `RibbonControlsLibrary` NuGet package (required at runtime).
- `App.config` with optional `"DefaultRibbonTab"` key.
#### Dependencies *on this module*:
- `RibbonRegionAdapter` is used to adapt `Ribbon` controls in Prism regions (e.g., `RegionNames.RibbonRegion`).
- `RibbonControlSelectionChanged` event is consumed by `RibbonRegionAdapter` and likely by other modules/view models to react to tab changes.
---
### 5. Gotchas
- **Typo in event name**:
- `RibbonRegionAdapter` subscribes to `TabControlSelectionChanged` (line: `_eventAggregator.GetEvent<TabControlSelectionChanged>()`), but the defined event is named `RibbonControlSelectionChanged`. This will cause a runtime `EventNotFoundException` unless `TabControlSelectionChanged` is defined elsewhere (not present in source).
- **Likely bug**: Should be `RibbonControlSelectionChanged`.
- **Event semantics mismatch**:
- `RibbonControlSelectionChangedEventArgs.Operation` is named `RibbonControlOperation`, but `RibbonRegionAdapter.OnTabControlSelectionChanged` checks for `TabControlOperation.RemovedItem` (again, typo—should be `RibbonControlOperation`).
- The `Operation` property in `RibbonControlSelectionEventArgs` is of type `RibbonControlOperation`, but the handler in `RibbonRegionAdapter` references `TabControlOperation` (undefined in source). This will not compile unless `TabControlOperation` exists elsewhere.
- **Only first item processed**:
- `RibbonControlSelectionChangeBehavior.Ribbon_SelectionChanged` publishes events only for `e.AddedItems[0]` and `e.RemovedItems[0]`. If multiple tabs are selected/removed in one operation (e.g., via `SelectedItems` manipulation), only the first is handled.
- **UID auto-generation side effect**:
- `AddRibbonTabToRegion` assigns a new `Uid` *every time* a tab is added if `Uid` is null/empty. This may cause issues if `Uid` is used for persistence or tracking across sessions.
- **Race condition in `RemoveRibbonTabFromRibbonRegion`**:
- Removal is not synchronized (unlike addition), risking `InvalidOperationException` if concurrent modifications occur.
- **Selection logic assumes single active tab**:
- `OnTabControlSelectionChanged` selects a tab only if `currentTab == null` or `ribbonTabInfo.RibbonTabUid != currentTab.Uid`, but does not handle cases where multiple tabs might be active (though `AllActiveRegion` is used, selection is still single-tab in `Ribbon` control).
- **No validation of `Item` type**:
- `RibbonControlSelectionEventArgs.Item` is `object`. Consumers must cast to `RibbonTab` or `RibbonApplicationMenu` safely.
- **`TargetRibbonControl` property name mismatch**:
- The `DependencyProperty` is registered as `"TargetRibbonControl"`, but the backing field is `TargetRibbonProperty`. This is valid, but the naming is inconsistent (`TargetRibbonControl` vs `TargetRibbonProperty`).
- **`RibbonRegionAdapter` subscribes to event in constructor**:
- If the adapter is created before `IEventAggregator` is fully initialized, subscription may fail. Not an issue if DI container ensures ordering, but a risk in complex setups.
- **`RibbonControlSelectionChangeBehavior` does not validate `AssociatedObject`**:
- Assumes `AssociatedObject` is non-null and implements `Ribbon` (enforced by `Behavior<Ribbon>`), but `SelectionChanged` handler does not check `sender` type.
None identified beyond the above.