--- 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 Prism’s 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` - **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` - **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`, 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 view’s `DataContext`. - If the selected tab’s UID does not match the view’s 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 view’s `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`) - **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()`), 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`), but `SelectionChanged` handler does not check `sender` type. None identified beyond the above.