--- source_files: - DataPRO/Modules/Channels/ChannelCodes/ViewModel/ChannelCodesListViewModel.cs generated_at: "2026-04-17T16:02:02.258497+00:00" model: "zai-org/GLM-5-FP8" schema_version: 1 sha256: "4a52e53369e1f84f" --- # ChannelCodesListViewModel Documentation ## 1. Purpose `ChannelCodesListViewModel` is a Prism-based ViewModel that manages the display, editing, validation, and persistence of Channel Codes in a WPF application. It handles two distinct categories of channel codes—ISO and User—providing separate views for each via a `ViewMode` toggle. The ViewModel supports CRUD operations, multi-selection, copy/paste functionality, sorting, filtering, and event-driven communication with other application components. It implements `IChannelCodesListViewModel` and follows the MVVM pattern with `INotifyPropertyChanged` support. --- ## 2. Public Interface ### Properties | Property | Type | Description | |----------|------|-------------| | `View` | `IChannelCodesListView` | The associated view instance; DataContext is set to this ViewModel in constructor. | | `NotificationRequest` | `InteractionRequest` | Used to raise notification dialogs. | | `ConfirmationRequest` | `InteractionRequest` | Used to raise confirmation dialogs. | | `AllISOChannelCodes` | `List` | Complete list of ISO channel codes (unfiltered). | | `AllUserChannelCodes` | `List` | Complete list of User channel codes (unfiltered). | | `ISOChannelCodes` | `ObservableCollection` | Filtered ISO channel codes bound to the view. | | `UserChannelCodes` | `ObservableCollection` | Filtered User channel codes bound to the view. | | `SelectedCodes` | `IChannelCode[]` | Returns selected codes based on current `ViewMode`. | | `Page` | `object` | Page identifier used in event payloads. | | `IsDirty` | `bool` | Indicates unsaved changes. | | `IsBusy` | `bool` | Controls busy indicator state. | | `IsMenuIncluded` | `bool` | Controls menu inclusion. | | `IsNavigationIncluded` | `bool` | Controls navigation inclusion. | | `IsReadOnly` | `bool` | When `true`, disables editing (FB14098). | | `ViewMode` | `ViewModes` | Current view mode (`ISO` or `User`). | | `ISOVisibility` | `Visibility` | Returns `Visible` when `ViewMode.ISO`, else `Collapsed`. | | `UserVisibility` | `Visibility` | Returns `Visible` when `ViewMode.User`, else `Collapsed`. | | `ChannelCodesFunc` | `Func>` | Returns `ChannelCode.ChannelCodes`. | | `ShowISOStringBuilder` | `bool` | Controls ISO string builder visibility. | | `UniqueISOCodesRequired` | `bool` | Indicates whether unique ISO codes are required. | | `ShowChannelCodeLookupHelper` | `bool` | Controls channel code lookup helper visibility. | ### Methods | Method | Signature | Description | |--------|-----------|-------------| | `OnPropertyChanged` | `void OnPropertyChanged(string propertyName)` | Raises `PropertyChanged` event. | | `Remove` | `void Remove(IChannelCode channel)` | Removes a channel code; routes to `RemoveISOChannel` or `RemoveUserChannel` based on `CodeType`. Publishes `PageModifiedEvent`. | | `MarkModified` | `void MarkModified(IChannelCode channel)` | Marks a channel as modified; adds a new blank row if modifying the last item. Publishes `PageModifiedEvent`. | | `Unset` | `void Unset()` | Clears all channel code collections. | | `SetPage` | `void SetPage(object page)` | Sets the `Page` identifier for event routing. | | `OnSetActive` | `void OnSetActive()` | Loads existing channel codes from database, populates collections, sorts and filters. | | `Cleanup` | `void Cleanup()` | Empty cleanup method. | | `CleanupAsync` | `Task CleanupAsync()` | Returns `Task.CompletedTask`. | | `Initialize` | `void Initialize()` / `void Initialize(object parameter)` / `void Initialize(object parameter, object model)` | Empty initialization methods. | | `InitializeAsync` | `Task InitializeAsync()` / `Task InitializeAsync(object parameter)` | Return `Task.CompletedTask`. | | `Activated` | `void Activated()` | Empty activation method. | | `ReportErrors` | `void ReportErrors(string[] errors)` | Publishes `PageErrorEvent` with errors and `Page`. | | `Save` | `bool Save()` | Persists all changes using a 4-step process: delete removed codes, update existing, insert new, update IDs. Returns `true` on success. | | `ValidateISO` | `void ValidateISO(ref List errors, ref List warnings)` | Validates ISO channel codes. | | `ValidateUser` | `void ValidateUser(ref List errors, ref List warnings)` | Validates User channel codes. | | `Validate` | `bool Validate(bool bDisplayWindow)` | Validates all codes; optionally displays errors/warnings. Returns `true` if no errors. | | `CopySelected` | `void CopySelected()` | Copies selected codes; inserts copies before the last (blank) row. | | `DeleteSelected` | `void DeleteSelected()` | Deletes selected codes; ensures a blank row remains at the end. | | `SetISOSelection` | `void SetISOSelection(IChannelCode[] channelCodes)` | Sets ISO selection; publishes `PageSelectionChanged` if `ViewMode.ISO`. | | `SetUserSelection` | `void SetUserSelection(IChannelCode[] channelCodes)` | Sets User selection; publishes `PageSelectionChanged` if `ViewMode.User`. | | `Filter` | `void Filter(object columnTag, string searchTerm)` | Filters codes based on column tag (`"ISOCode"`, `"ISOChannelName"`, `"UserCode"`, `"UserChannelName"`). | | `Sort` | `void Sort(object columnTag, bool bColumnClick)` | Sorts codes by column; toggles direction on repeated clicks. | ### Nested Types | Type | Description | |------|-------------| | `enum Fields` | `IsoCode`, `IsoChannelName`, `UserCode`, `UserChannelName` — used for sorting/filtering. | | `enum ViewModes` | `ISO`, `User` — toggles between channel code types. | | `class ChannelComparer : IComparer` | Custom comparer using `NaturalStringComparer`; handles null/blank items by sorting them last. | --- ## 3. Invariants 1. **Blank Row Guarantee**: The last item in both `AllISOChannelCodes` and `AllUserChannelCodes` is always a blank `ChannelCode` instance (both `Code` and `Name` are empty). This serves as the "new row" for data entry. 2. **ISO Code Length**: ISO codes are silently truncated to 16 characters maximum in `ParseText()`. 3. **Blank Item Sorting**: Items with both `Code` and `Name` empty are always sorted to the end of the list (see `ChannelComparer.Compare`). 4. **Selection Handling**: Empty selection arrays are ignored and not assigned (FB 18906 guard in `SetISOSelection` and `SetUserSelection`). 5. **Save Operation Ordering**: The `Save()` method executes in strict order: (1) delete removed codes, (2) update existing codes, (3) insert new codes. This prevents primary key conflicts. 6. **Event Subscription Pattern**: The static `_bAddListeners` flag ensures event listeners are only registered on the second constructor invocation (see Gotchas). --- ## 4. Dependencies ### External Dependencies (Imports) | Namespace | Purpose | |-----------|---------| | `ChannelCodes.Model` | `ChannelCode` model, `ChannelCodeType` lookup | | `DTS.Common.Enums` | General enumerations | | `DTS.Common.Enums.Channels` | `ChannelEnumsAndConstants` | | `DTS.Common.Events` | `PageModifiedEvent`, `PageErrorEvent`, `PageSelectionChanged` | | `DTS.Common.Events.ChannelCodes` | `RaiseNotification`, `BusyIndicatorChangeNotification`, `TextPastedEvent`, `ChannelCodeCommittedEvent` | | `DTS.Common.Interface.Channels.ChannelCodes` | `IChannelCode`, `IChannelCodesListView`, `IChannelCodesListViewModel` | | `DTS.Common.Utilities.Logging` | `APILogger` | | `DTS.Common.Interactivity` | `InteractionRequest`, `Notification`, `Confirmation` | | `Prism.Events` | `IEventAggregator`, `ThreadOption` | | `Prism.Regions` | `IRegionManager` | | `Unity` | `IUnityContainer` | ### Event Subscriptions | Event | Thread Option | Keep Subscriber Reference | |-------|---------------|---------------------------| | `RaiseNotification` | Default | Default | | `BusyIndicatorChangeNotification` | `PublisherThread` | `true` | | `TextPastedEvent` | Default | Default | | `ChannelCodeCommittedEvent` | `PublisherThread` | `true` | ### Event Publications | Event | Payload | |-------|---------| | `PageModifiedEvent` | `PageModifiedArg` with status (`Modified`/`Saved`) and `Page` | | `PageErrorEvent` | `PageErrorArg` with error messages and `Page` | | `PageSelectionChanged` | `PageSelectionChangedArg` with selection count and `Page` | --- ## 5. Gotchas 1. **Static Listener Flag Hack**: The constructor uses a static `_bAddListeners` field to prevent duplicate event subscriptions. The comment explicitly states: *"this is a hack, the app startup is calling this, then the app itself, we only want the app itself to be handling the listeners."* This implies the constructor is invoked twice during application lifecycle, and only the second invocation should register `TextPastedEvent` and `ChannelCodeCommittedEvent` handlers. 2. **Silent ISO Code Truncation**: In `ParseText()`, ISO codes exceeding 16 characters are silently truncated without warning or error. Users are not notified of this modification. 3. **Paste Behavior Depends on Tag**: The `PasteIso` and `PasteUser` methods interpret the `tag` parameter to determine whether pasted text should populate the `Code` or `Name` field when single-column data is pasted. The tag must equal `"ISOCode"` or `"UserCode"` (string comparison) to populate the code field. 4. **Filter Uses Case-Insensitive Contains**: Filtering uses `IndexOf(term, StringComparison.CurrentCultureIgnoreCase)` which performs a substring match, not an exact or startswith match. 5. **Save Returns False Only on Exception**: The `Save()` method returns `false` only if an exception is caught. Validation errors do not prevent save; callers should call `Validate()` before `Save()`. 6. **Remove vs DeleteSelected Behavior Difference**: `Remove()` does not automatically add a blank row if removing a non-last item, but `DeleteSelected()` and `RemoveISOChannel`/`RemoveUserChannel` do add a blank row when removing the last item. This ensures the blank row invariant is maintained.