Files

188 lines
11 KiB
Markdown
Raw Permalink Normal View History

2026-04-17 14:55:32 -04:00
---
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 Prisms 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<Notification> NotificationRequest`**, **`InteractionRequest<Confirmation> 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<T>`)
- **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 Prisms `INotifyPropertyChanged` may handle thread marshaling in some cases, this pattern is fragile and may cause cross-thread exceptions if the UI framework is strict.