163 lines
10 KiB
Markdown
163 lines
10 KiB
Markdown
---
|
||
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<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 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<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. |