--- source_files: - DataPRO/Modules/ISO/ExtraProperties/ViewModel/ExtraPropertiesListViewModel.cs generated_at: "2026-04-16T04:38:41.881863+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "d7dc80e73ad032ff" --- # ViewModel ## **ExtraPropertiesListViewModel Documentation** --- ### **1. Purpose** `ExtraPropertiesListViewModel` is a Prism-based ViewModel responsible for managing a list of key-value extra properties associated with a data page in the ISO module of the DataPRO application. It provides UI-agnostic data binding, sorting, filtering, clipboard operations (copy/paste), validation, and event-driven integration with the broader application via `IEventAggregator`. It serves as the intermediary between the `IExtraPropertiesListView` UI and the underlying `ExtraPropertyModel` data entities, enabling users to edit, sort, filter, and validate property lists (e.g., metadata, tags, or custom attributes) in a tabular UI. --- ### **2. Public Interface** All members are defined in `ExtraPropertiesListViewModel`, which implements `IExtraPropertiesListViewModel` (inferred from interface usage). Only explicitly declared public or interface-implementing members are documented. #### **Properties** | Property | Type | Description | |----------|------|-------------| | `View` | `IExtraPropertiesListView` | Reference to the associated view; set externally. | | `NotificationRequest` | `InteractionRequest` | Prism MVVM Toolkit interaction request for showing non-blocking notifications. | | `ConfirmationRequest` | `InteractionRequest` | Prism MVVM Toolkit interaction request for showing confirmation dialogs. | | `IsBusy` | `bool` | Gets/sets busy state; raises `PropertyChanged` for `"IsBusy"`. | | `IsMenuIncluded` | `bool` | Gets/sets whether a context menu is included in the view; raises `PropertyChanged` for `"IsMenuIncluded"`. | | `IsNavigationIncluded` | `bool` | Gets/sets whether navigation controls are included; raises `PropertyChanged` for `"IsNavigationIncluded"`. | | `IsReadOnly` | `bool` | Gets/sets read-only mode (prevents editing); raises `PropertyChanged` for `"IsReadOnly"`. | | `AllExtraProperties` | `List` | Full list of all extra properties (including empty trailing row). | | `ExtraProperties` | `ObservableCollection` | Filtered/sorted view of `AllExtraProperties`, bound to UI. | | `SelectedProperties` | `IExtraProperty[]` | **Throws `NotImplementedException`** — not implemented. | | `Page` | `IDataPROPage` | The page this ViewModel is associated with (set via `SetPage`). | | `Parent` | `object` | Parent object (set via `SetParent`). | | `IsDirty` | `bool` | Declared but never set; always `false`. | #### **Events** | Event | Type | Description | |-------|------|-------------| | `PropertyChanged` | `PropertyChangedEventHandler` | Standard `INotifyPropertyChanged` event; raised via `OnPropertyChanged`. | #### **Methods** | Method | Signature | Description | |--------|-----------|-------------| | `OnPropertyChanged` | `void OnPropertyChanged(string propertyName)` | Raises `PropertyChanged`; if `propertyName == "ExtraProperties"`, publishes `ExtraPropertiesChangedEvent`. | | `SetPage` | `void SetPage(IDataPROPage page)` | Sets `Page`. | | `SetParent` | `void SetParent(object parent)` | Sets `Parent`. | | `Cleanup` | `void Cleanup()` | No-op. | | `CleanupAsync` | `Task CleanupAsync()` | Returns `Task.CompletedTask`. | | `Initialize` / `InitializeAsync` | Multiple overloads (e.g., `void Initialize()`, `Task InitializeAsync(object parameter)`) | All are no-ops. | | `Activated` | `void Activated()` | No-op. | | `Filter` | `void Filter(object tag, string term)` | Sets search term for `"Key"` or `"Value"` (via `tag` string) and triggers `Filter()`. **Bug**: `"Value"` case incorrectly sets `_searchTermForField[Fields.Key]`. | | `Sort` | `void Sort(object columnTag, bool columnClick)` | Sets sort field (`"Key"` or `"Value"`) and triggers `Sort(...)` + `Filter()`. | | `Validate(ref List errors)` | `bool Validate(ref List errors)` | Calls internal `Validate(ref errors, ref warnings)`, returns `true` if no new errors added. | | `SetExtraProperties` | `void SetExtraProperties(IList properties)` | Replaces `AllExtraProperties` with copies of input properties, appends a new empty `ExtraPropertyModel`, resets sort/filter, and re-applies. | | `CopySelected` | `void CopySelected()` | Copies selected items (`_selectedItems`) into `AllExtraProperties` (just before the trailing empty row), sorts, filters, and publishes `PageModifiedEvent`. | | `DeleteSelected` | `void DeleteSelected()` | Removes selected items from both `ExtraProperties` and `AllExtraProperties`; ensures trailing empty row is preserved; publishes `PageModifiedEvent`. | > **Note**: `SelectedProperties` is declared but throws `NotImplementedException`. Its usage is likely incomplete or deprecated. --- ### **3. Invariants** - **Trailing Empty Row**: `AllExtraProperties` always ends with a single empty `ExtraPropertyModel` instance (key/value both empty/whitespace). This is enforced in: - `SetExtraProperties` (adds one after copying input), - `MarkModified` (adds one if last item modified), - `DeleteSelected` (re-adds one if deleted). - **Filtering Logic**: Empty rows (both key and value whitespace) are *always* included in `ExtraProperties` regardless of search term. - **Sorting Behavior**: Empty rows are *always* sorted to the *bottom* of `AllExtraProperties` (via `PropertyComparer.Compare`). - **Validation Rules**: - Duplicate non-empty keys → error. - Non-empty keys with empty/whitespace values → warning. - **Event Subscription Hack**: `_bAddListeners` static flag ensures only the *second* instantiation of this ViewModel subscribes to `TextPastedEvent`. First instantiation (likely during app startup) skips subscription. --- ### **4. Dependencies** #### **Imports / Dependencies Used** - **Prism Framework**: - `Prism.Events.IEventAggregator`, `Prism.Regions.IRegionManager` - `Prism.Interactivity.InteractionRequest` (`Notification`, `Confirmation`) - **Unity DI Container**: `IUnityContainer` - **DataPRO Common Libraries**: - `DTS.Common.Events.*` (e.g., `TextPastedEvent`, `PageModifiedEvent`, `PageSelectionChanged`) - `DTS.Common.Interface.*` (`IDataPROPage`, `IExtraProperty`, `IExtraPropertiesListView`, `IExtraPropertiesListViewModel`) - `DTS.Common.Utilities.NaturalStringComparer` (used in `PropertyComparer`) - **Model Layer**: - `ExtraProperties.Model.ExtraPropertyModel` (concrete implementation of `IExtraProperty`) - **Resources**: - `ExtraProperties.Resources.StringResources` (for error/warning messages) #### **Consumers (Inferred)** - `ExtraPropertiesListViewModel` is instantiated via Unity DI (due to `[PartCreationPolicy(CreationPolicy.Shared)]` and constructor injection). - Likely consumed by `IExtraPropertiesListView` (WPF view) bound to this ViewModel. - Subscribes to events published by other modules (e.g., `TextPastedEvent` from clipboard operations). - Publishes `PageModifiedEvent`, `ExtraPropertiesChangedEvent`, and `PageSelectionChanged`. --- ### **5. Gotchas** - **`SelectedProperties` is unimplemented**: Throws `NotImplementedException`. Any code expecting this property will crash. - **`Filter` bug**: When filtering by `"Value"`, it incorrectly assigns the term to `_searchTermForField[Fields.Key]` instead of `Fields.Value`. - **`_bAddListeners` hack**: Static boolean `_bAddListeners` controls `TextPastedEvent` subscription. First instantiation *does not* subscribe; only the second (and subsequent) do. This is fragile and may break if instantiation order changes. - **`IsDirty` never set**: Property is declared but never assigned; always `false`. - **`Sort`/`Filter` order**: `Sort` is called *before* `Filter` in `Filter(object, string)` and `Sort(object, bool)`, but `Filter()` is called *after* `Sort(...)` in `Paste`, `CopySelected`, and `DeleteSelected`. This is consistent but non-obvious. - **Paste behavior**: `Paste` modifies `ExtraProperties` *in-place* (by index), but `AllExtraProperties` is not updated until `Sort`/`Filter` are called. This may cause inconsistency if accessed mid-operation. - **Case sensitivity**: Sorting and filtering use `ToUpper()` + `NaturalStringComparer`, which may not match user expectations for case-insensitive comparisons (e.g., `"a"` and `"A"` sort as equal, but `"ä"` vs `"a"` may behave unexpectedly). - **No null checks in `PropertyComparer`**: While `left == right` is handled, `a`/`b` could be `null` after assignment (e.g., if `left`/`right` are non-null but one becomes `null` after `SortAscending` swap logic). `null` checks exist but may not cover all paths. - **`MarkModified` side effect**: Appends a new empty row *only* if the modified item is the last in `ExtraProperties`, but uses `ExtraProperties.Last()` — which is the *filtered* list — not `AllExtraProperties`. This may cause inconsistent behavior if filtering is active. --- *Documentation generated from `ExtraPropertiesListViewModel.cs` (DataPRO/Modules/ISO/ExtraProperties/ViewModel/ExtraPropertiesListViewModel.cs).*