Files
2026-04-17 14:55:32 -04:00

163 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.