--- source_files: - DataPRO/Modules/Groups/GroupImport/ViewModel/GroupImportViewModel.cs generated_at: "2026-04-16T04:45:51.906727+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "f514e88931e0b70e" --- # GroupImportViewModel Documentation ## 1. Purpose `GroupImportViewModel` is the central view model for the group and channel import functionality in the DTS application. It orchestrates the end-to-end workflow of importing data from `.grp` files—parsing file contents, validating sensor and field data, allowing user review and selection of groups/channels for import, and committing validated data to the database via background processing. It serves as the data context for three distinct views (`IGroupImportOptionsView`, `IGroupImportPreviewView`, `IGroupImportImportView`) and integrates with Prism’s region management, event aggregation, and Unity DI container to coordinate UI state and external service calls. ## 2. Public Interface ### Constructor ```csharp public GroupImportViewModel( IGroupImportOptionsView optionsView, IGroupImportPreviewView previewView, IGroupImportImportView importView, IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer) ``` Initializes the view model, assigns views and their `DataContext`, sets up interaction requests (`NotificationRequest`, `ConfirmationRequest`), and subscribes to `RaiseNotification` and `BusyIndicatorChangeNotification` events. ### Methods - **`void ParseSourceFiles(string userTags)`** Reads and parses all `.grp` files listed in `SourceFiles`. For each file: - Extracts group name from file name (strips extension). - Assigns `userTags` to `GroupTags` and `ImportingUserTags`. - Parses each non-empty line using `Parse()` (supports escaped commas via parentheses). - Validates fields: expects 5 fields per line (`SensorSerialNumber`, `DisplayName`, `ISOCode`, `Invert`, `FullScale`). - Reports errors for missing sensors (unless empty), invalid invert/full-scale values, wrong field count, or empty files. - Populates `Groups` and calls `ProcessChannels()`. - **`void Import()`** Starts the import process on a background thread via `ThreadPool.QueueUserWorkItem(ImportFunc)`. - **`private void ImportFunc(object o)`** Background method that: - Fetches default channel settings via `DbOperations.GetChannelSettingDefaults()`. - Updates UI progress state (`ImportProgressValue`, `ImportProgressBarVisibility`, `ImportProgressColor`, `DisableUI`). - Iterates over included groups and their channels: - Skips channels with critical errors (`FileEmpty`, `InvalidSensorInput`, `SensorNotFound`). - For non-critical errors, uses `null` for invalid `FullScale` or `Invert`. - Calls `CreateGroup`, `AddChannel`, and finally `CommitGroups`. - Restores UI state on completion (though commented-out lines suggest UI reset is incomplete). - **`void SetStatus(string message, Color color)`** Updates `ImportProgressText`, `ImportProgressColor`, `ImportProgressBarVisibility` (collapsed), and invokes `EnableUI`. - **`void Reset()`** Clears all imported data: `SourceFiles`, `Channels`, `Groups`, progress state, and resets `ImportProgressValue` to 0. - **`void CheckGroupName()`** Validates group names: - Flags groups with duplicate names in the import set. - Flags groups whose name already exists in the application *and* `Overwrite` is `false`. - Sets `GroupNameHasError = true` on invalid groups. - **`private static string[] Parse(string line)`** Parses a single `.grp` line, respecting parenthetical escaping (e.g., `"this(,)is"` → one field `"this(,)is"`). - **`private void ProcessChannels()`** Flattens all channels from `Groups` into the `Channels` array. - **`void InvalidateChannels()`** Raises `PropertyChanged` for `IncompleteChannels` and `CompleteChannels` computed properties. - **`void Cleanup()` / `Task CleanupAsync()` / `void Initialize()` / `Task InitializeAsync()` / `void Activated()`** No-op stubs; likely required by Prism interfaces (`INavigationAware`, `IInitializeAsync`, etc.) but not implemented. ### Properties - **`IGroupImportOptionsView ImportOptionsView`**, **`IGroupImportPreviewView ImportPreviewView`**, **`IGroupImportImportView ImportView`** References to the three associated views. - **`string[] SourceFiles`** Array of `.grp` file paths to parse. - **`GroupGRPImportGroup[] Groups`** Parsed groups with their channels and errors. - **`GroupGRPImportChannel[] Channels`** Flattened list of all channels from `Groups`. - **`GroupGRPImportChannel[] IncompleteChannels`** Channels in included groups that have *non-recoverable* errors (e.g., `SensorNotFound`, `InvalidSensorInput`). These will *not* be imported. - **`GroupGRPImportChannel[] CompleteChannels`** Channels in included groups that either have no errors *or* have recoverable errors (`InvalidFullScaleInput`, `InvalidInvertInput`). These *will* be imported (with `null` for invalid fields). - **`string ImportProgressText`**, **`Color ImportProgressColor`**, **`Visibility ImportProgressBarVisibility`**, **`double ImportProgressValue`** UI state for the import progress bar. - **`bool IsBusy`** Bound to busy indicator; set via `OnBusyIndicatorNotification`. - **`InteractionRequest NotificationRequest`**, **`InteractionRequest ConfirmationRequest`** Prism interaction requests for showing notifications and confirmation dialogs. - **`bool IsDirty`** Read-only; always `false` (never set to `true` in source). - **`bool IsMenuIncluded`**, **`bool IsNavigationIncluded`** UI state flags (bound to menu/navigation visibility); no logic sets them beyond property change notifications. - **`string HeaderInfo`** Returns `"MainRegion"`. ### Commands - **`DelegateCommand ImportBrowseCommand`** Opens a file dialog (`OpenFileDialog`) with `Multiselect=true`, `Filter` from `StringResources.ImportFileFilter`, sets `SourceFiles`, `BrowseOk=true`, and navigates to `Steps.Preview` via `SwitchNavSteps`. ### Delegates (Settable Properties) - **`CheckGroupExistsDelegate CheckGroupExists`** Function to check if a group exists in the application. - **`CheckSensorExistsDelegate CheckSensorExists`** Function to check if a sensor exists. - **`CreateGroupDelegate CreateGroup`** Action to create a group. - **`AddChannelToGroupDelegate AddChannel`** Action to add a channel to a group. - **`CommitGroupsDelegate CommitGroups`** Action to commit groups to the database. - **`Disable_UIDelegate DisableUI`**, **`Enable_UIDelegate EnableUI`** Actions to disable/enable the UI during import. - **`SwitchNavStepsDelegate SwitchNavSteps`** Action to navigate between import steps (e.g., `Steps.Preview`). - **`FileUtils.LogDelegate Logger`** Optional logging delegate; invoked on exceptions. ## 3. Invariants - **File Parsing**: Each `.grp` line must contain 5 comma-separated fields (or be empty/whitespace). Lines with fewer or more fields are marked with `InvalidSensorInput`. - **Sensor Validation**: A channel is flagged `SensorNotFound` if `CheckSensorExists` is non-null, the sensor serial number is non-empty, and `CheckSensorExists(sensorSerialNumber)` returns `false`. Empty sensor serial numbers are *allowed* (per comment referencing FB13753). - **Invert Field**: Accepts `"yes"/"no"` (case-insensitive) or `bool.TryParse`-compatible strings. Invalid values trigger `InvalidInvertInput`. - **FullScale Field**: Must parse as `double`; otherwise, `InvalidFullScaleInput`. - **Group Name Uniqueness**: Within the import set, duplicate group names are flagged. Across the import set and application, group names must be unique *unless* `Overwrite=true`. - **Import Progress State**: During `ImportFunc`, `ImportProgressBarVisibility` is set to `Visible`, `DisableUI` is called, and progress is tracked. Completion logic is partially commented out, so UI may not fully reset. - **Channel Inclusion Logic**: Only channels from groups where `Included=true` are considered for import. Channels with critical errors (`FileEmpty`, `InvalidSensorInput`, `SensorNotFound`) are skipped entirely; others may be imported with `null` for invalid fields. ## 4. Dependencies ### Imports/References - **DTS.Common**: - `Events` (`IEventAggregator`, `RaiseNotification`, `BusyIndicatorChangeNotification`) - `Utils` (`FileUtils.LogDelegate`) - `Classes.Groups` (`GroupGRPImportGroup`, `GroupGRPImportChannel`, `GroupGRPImportError`) - `Enums.Groups` (`GroupImportEnums.Steps`, `GroupGRPImportError.Errors`) - `Interface.Groups` (`IGroupImportOptionsView`, `IGroupImportPreviewView`, `IGroupImportImportView`, `IGroupImportViewModel`) - `Storage` (`DbOperations`) - `Interactivity` (`InteractionRequest`) - **Prism**: - `Regions` (`IRegionManager`) - `Events` (`IEventAggregator`) - `DelegateCommand` - **Unity** (`IUnityContainer`) - **System.Windows** (`Visibility`, `Color`) - **System.Windows.Forms** (`OpenFileDialog`) - **StringResources** (resource strings for UI messages) ### Dependencies on External Services - `DbOperations.GetChannelSettingDefaults()` — for default channel settings. - `CheckSensorExists`, `CheckGroupExists`, `CreateGroup`, `AddChannel`, `CommitGroups` — injected delegates for database operations. - `SwitchNavSteps` — navigation delegate (likely from a parent view model or shell). ## 5. Gotchas - **Incomplete UI Reset After Import**: `ImportFunc` sets `ImportProgressBarVisibility = Visibility.Visible` and `DisableUI`, but the corresponding reset (`Visibility.Collapsed`, `EnableUI`) is commented out. This may leave the UI in a disabled state if import completes without error. - **Empty Sensor Serial Numbers Are Allowed**: Despite `CheckSensorExists` validation, empty serial numbers bypass the `SensorNotFound` check (FB13753). - **Partial Error Tolerance**: Channels with `InvalidFullScaleInput` or `InvalidInvertInput` are included in `CompleteChannels` and imported, but with `null` for the invalid field. This may cause downstream issues if the database or business logic does not handle `null` gracefully. - **No Validation of Group Name Format**: Only existence and duplication are checked; no format validation (e.g., reserved characters, length limits) is implemented. - **`Parse()` Escaping Logic**: Parentheses are treated as literal characters *only* when nested (via `leftParenCount`). A single `(` or `)` without matching pairs is still treated as a delimiter if not balanced. - **`IsDirty` Never Set**: The `IsDirty` property is declared but never updated, so it always returns `false`. - **`Initialize*` and `Cleanup*` Methods Are No-ops**: These likely exist to satisfy Prism interfaces but contain no logic. - **`Channels` Property Triggers `InvalidateChannels()`**: Setting `Channels` raises property change notifications for `IncompleteChannels` and `CompleteChannels`, but these are computed properties—modifying `Channels` directly may not reflect changes in the UI if `Groups` is not updated. - **Thread Safety**: `ImportFunc` runs on a background thread but updates UI properties directly (e.g., `ImportProgressValue`). While Prism’s `INotifyPropertyChanged` may handle thread marshaling in some cases, this pattern is fragile and may cause cross-thread exceptions if the UI framework is strict.