This commit is contained in:
2026-04-17 14:55:32 -04:00
commit bc3ac1d4c9
18017 changed files with 4371742 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
---
source_files:
- DataPRO/Modules/ISO/ExtraProperties/Model/ExtraPropertyModel.cs
generated_at: "2026-04-16T04:39:00.347162+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "f8c9e5793048774d"
---
# Model
### **Purpose**
This module implements the `ExtraPropertyModel` class, a concrete data model for representing key-value pairs (e.g., ISO channel codes and names or user-defined properties) within a WPF-based UI, conforming to the `IExtraProperty` interface. It supports property change notifications via Prisms `BasePropertyChanged`, integrates with Prisms event aggregation system for cross-module communication (e.g., signaling page modifications or paste events), and provides a custom `PasteCommand` to handle clipboard paste operations with special handling for multi-line or multi-field text. Its role is to serve as the view-model backing for UI controls that edit extra properties in ISO data contexts.
---
### **Public Interface**
#### **`ExtraPropertyModel` class**
- **`public ExtraPropertyModel(IExtraProperty extraProperty)`**
Initializes a new instance by copying `Key` and `Value` from an existing `IExtraProperty` instance. Registers paste commands during construction.
- **`public ExtraPropertyModel()`**
Default constructor. Registers paste commands. `Key` and `Value` are initialized to `null`.
- **`public ExtraPropertyModel(KeyValuePair<string, string> extraProperty)`**
Initializes a new instance using a `KeyValuePair<string, string>`. Registers paste commands.
- **`public UIItemStatus ItemStatus { get; set; }`**
Gets or sets the UI status of the item (e.g., `None`, `Added`, `Modified`, `Deleted`). Backed by `SetProperty` for change notifications.
- **`public string Key { get; set; }`**
Gets or sets the code (e.g., ISO code or user code). Backed by `SetProperty`.
- **`public string Value { get; set; }`**
Gets or sets the associated name (e.g., ISO channel name or user channel name). Backed by `SetProperty`.
- **`public ICommand PasteCommand { get; set; }`**
Exposes the command bound to paste operations. Initialized to a `PasteCommandClass` instance with `PASTE_ID = "ExtraProperty"`.
- **`public const string PASTE_ID = "ExtraProperty"`**
Constant identifier used to distinguish paste operations originating from this model.
#### **`PasteCommandClass` class**
- **`public string Id { get; }`**
Gets the command ID passed during construction (e.g., `"ExtraProperty"`).
- **`public bool CanExecute(object parameter)`**
Always returns `true`.
- **`public void Execute(object parameter)`**
Handles paste logic:
- Validates `parameter` is a `TextBox`.
- Attempts to resolve `IExtraProperty` from `TextBox.DataContext`, falling back to `ChannelCodeBuilder` or `ChannelNameBuilder` contexts.
- Checks clipboard contains text.
- If clipboard text has exactly one line and contains no delimiters (`','`, `';'`, `'\t'`), publishes `PageModifiedEvent` and exits early (single-field paste is handled by `TextChanged`).
- Otherwise, clears `Key`/`Value` on the target `IExtraProperty` (a no-op assignment to trigger change tracking), then publishes `TextPastedEvent` with the raw text, target property, command ID, and `TextBox.Tag`.
- On exception, publishes `PageErrorEvent` with the exception message.
- **`public event EventHandler CanExecuteChanged`**
Required by `ICommand`, but never raised (no-op).
- **`public PasteCommandClass(string id)`**
Constructor storing `id` in `Id`.
---
### **Invariants**
- `Key` and `Value` may be `null`; no validation is enforced on their values.
- `ItemStatus` defaults to `UIItemStatus.None` and must be explicitly set by consumers.
- The `PasteCommand` is registered globally via `CommandManager.RegisterClassCommandBinding`, meaning it applies to *all* instances of `ExtraPropertyModel` and its subclasses.
- The `Paste` method (private) is never implemented — it is a stub. Actual paste behavior resides entirely in `PasteCommandClass.Execute`.
- `CanExecuteChanged` is never raised by `PasteCommandClass`, so WPF command bindings may not update UI state (e.g., button enablement) dynamically.
---
### **Dependencies**
- **Imports/Usings**:
- `DTS.Common.ISO.ExtraProperties` → Provides `IExtraProperty` interface.
- `DTS.Common.Controls` → Provides `ChannelCodeBuilder`, `ChannelNameBuilder` types.
- `DTS.Common.Enums` → Provides `UIItemStatus`.
- `DTS.Common.Events` → Provides `PageModifiedEvent`, `TextPastedEvent`, `PageErrorEvent`, and their argument types.
- `Prism.Ioc` → Provides `ContainerLocator` (for resolving `IEventAggregator`).
- `Prism.Events` → Provides `IEventAggregator`.
- **Depended upon**:
- `IExtraProperty` interface (consumed via constructor and paste logic).
- `PageModifiedEvent`, `TextPastedEvent`, `PageErrorEvent` (published by `PasteCommandClass`).
- `ContainerLocator.Container` (runtime dependency for event aggregation).
---
### **Gotchas**
- **`Paste` method is empty**: The private `Paste(object sender, ExecutedRoutedEventArgs e)` method is declared but has no implementation; all logic resides in `PasteCommandClass.Execute`. This is likely intentional (to satisfy `CommandBinding` requirements), but may confuse readers.
- **`CanExecuteChanged` is never raised**: `PasteCommandClass` implements `ICommand` but does not notify subscribers of `CanExecute` state changes, potentially causing stale UI (e.g., disabled paste buttons).
- **Ambiguous paste behavior**:
- Single-line, non-delimited text triggers `PageModifiedEvent` but *not* `TextPastedEvent`, while multi-line or delimited text triggers `TextPastedEvent` but *not* `PageModifiedEvent`. This asymmetry may lead to inconsistent state tracking.
- The "wipe out built-in effect" assignment (`extraProperty.Key = extraProperty.Key`) is a no-op and serves no functional purpose beyond triggering change notifications if the property setter is implemented non-trivially elsewhere.
- **Clipboard access without safety checks**: `Clipboard.GetText()` may throw exceptions (e.g., in sandboxed environments), but only generic `Exception` handling is present.
- **No null-safety for `tb.Tag`**: The `tb.Tag` value is passed directly in `TextPastedArgs` without null checks.
- **Tight coupling to Prism**: Reliance on `ContainerLocator.Container` for `IEventAggregator` makes unit testing difficult without Prism infrastructure.
- **No cleanup in constructors**: While the finalizer attempts to null `_key`/`_value`, this is unreliable and unnecessary in modern .NET (where `IDisposable` would be preferred if unmanaged resources were involved — none are evident here).

View File

@@ -0,0 +1,38 @@
---
source_files:
- DataPRO/Modules/ISO/ExtraProperties/Properties/AssemblyInfo.cs
generated_at: "2026-04-16T04:38:46.680275+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "928af9411c9cbe1b"
---
# Properties
## 1. Purpose
This module (`ExtraProperties`) is an assembly-level metadata configuration file for a proprietary .NET component within the `DataPRO/Modules/ISO/ExtraProperties` path. It defines standard assembly attributes (title, version, culture, COM visibility) but contains no application logic—its sole purpose is to declare metadata for the compiled assembly, primarily for versioning, identification, and COM interop control. It serves as infrastructure for build/deployment and runtime reflection, not for business logic.
## 2. Public Interface
**No public types, functions, classes, or methods are defined in this file.**
This file (`AssemblyInfo.cs`) contains only assembly-level attributes and does not declare any executable code or public API surface.
## 3. Invariants
- The assembly is **not visible to COM** (`ComVisible(false)`), meaning its types cannot be accessed via COM interop unless explicitly exposed elsewhere.
- The assembly version is fixed at `1.0.0.0` for both `AssemblyVersion` and `AssemblyFileVersion`.
- The `Guid` attribute (`cc693402-5f7c-4aaf-9dbe-5382b204c62b`) is reserved for the typelib ID if the assembly is ever exposed to COM.
- All attributes are applied at assembly scope (via `[assembly: ...]` syntax) and must be declared at the top level of the file (no per-type or nested attributes).
## 4. Dependencies
- **Depends on**:
- `System.Reflection` (for `AssemblyTitle`, `AssemblyVersion`, etc.)
- `System.Runtime.InteropServices` (for `ComVisible`, `Guid`)
- `System.Runtime.CompilerServices` (imported but unused in this file)
- **Depended on by**:
- Unknown from this file alone. As a metadata-only assembly, it is consumed implicitly by the .NET runtime and build tools (e.g., for strong naming, version resolution, reflection), but no explicit consumer is declared here.
## 5. Gotchas
- **No business logic**: This file is purely declarative; developers should not expect any runtime behavior or state management here.
- **COM visibility**: Though `ComVisible(false)` is set, if *any* types in this assembly *are* intended for COM exposure, they must be explicitly marked with `[ComVisible(true)]` at the type level—otherwise, they remain inaccessible to COM.
- **Versioning rigidity**: `AssemblyVersion("1.0.0.0")` is hardcoded with no wildcard (`*`), meaning build/revision numbers cannot auto-increment (unlike the commented-out `1.0.*` example).
- **Empty metadata fields**: `AssemblyDescription`, `AssemblyConfiguration`, `AssemblyCompany`, and `AssemblyTrademark` are all empty strings—this may impact tooling or installer behavior that relies on these fields.
- **None identified from source alone** regarding logic-specific quirks, as no logic exists in this file.

View File

@@ -0,0 +1,53 @@
---
source_files:
- DataPRO/Modules/ISO/ExtraProperties/Resources/TranslateExtension.cs
- DataPRO/Modules/ISO/ExtraProperties/Resources/StringResources.Designer.cs
generated_at: "2026-04-16T04:38:39.050309+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "d990e5734e630810"
---
# Resources
## 1. Purpose
`TranslateExtension` is a WPF `MarkupExtension` that enables declarative localization of UI strings in XAML by resolving resource keys to localized string values at runtime. It acts as a bridge between XAML markup and the applications strongly-typed resource system (`StringResources`), allowing developers to bind localized text directly in XAML using `{Translate key}` syntax. Its role is to centralize and simplify string localization, avoiding hardcoded strings and supporting culture-specific display via the underlying `ResourceManager`.
## 2. Public Interface
- **`TranslateExtension(string key)`**
Constructor. Initializes the extension with a resource key (`_key`) to look up. Throws no exceptions on invalid input; invalid keys are handled in `ProvideValue`.
- **`object ProvideValue(IServiceProvider serviceProvider)`**
Overrides `MarkupExtension.ProvideValue`. Performs the resource lookup:
- If `_key` is `null` or empty → returns `"#stringnotfound#"`.
- Otherwise, calls `StringResources.ResourceManager.GetString(_key)`.
- If a matching string is found → returns it.
- If not found → returns `"#stringnotfound# " + _key` (note the trailing space).
*Note:* The `IServiceProvider` parameter is unused; the method does not interact with the WPF service provider beyond satisfying the interface contract.
## 3. Invariants
- `_key` is immutable after construction (stored in a `readonly` field).
- The returned value is always a `string`.
- A missing or empty key **always** yields `"#stringnotfound#"`.
- A key with no corresponding resource entry **always** yields `"#stringnotfound# <key>"` (with a space before the key).
- No validation is performed on `_key` beyond null/empty checks; invalid keys (e.g., containing special characters) are passed directly to `ResourceManager.GetString`, which may return `null`.
- Thread-safety: Relies on `StringResources.ResourceManager`, which is documented as thread-safe for concurrent reads (standard `ResourceManager` behavior).
## 4. Dependencies
- **Depends on:**
- `System.Windows.Markup` (for `MarkupExtension` and `MarkupExtensionReturnType`).
- `ExtraProperties.Resources.StringResources` (strongly-typed resource class).
- `System.Resources.ResourceManager` (via `StringResources.ResourceManager`).
- **Depended on by:**
- XAML files in the `ExtraProperties` module (inferred from usage as a `MarkupExtension`).
- No direct programmatic callers are visible in the provided source; usage is exclusively via XAML markup.
## 5. Gotchas
- **Hardcoded placeholder:** The `"#stringnotfound#"` string is a literal constant—no external configuration or localization for this fallback exists.
- **No culture override support:** The extension does not expose or respect `StringResources.Culture`; it uses the current UI culture implicitly via `ResourceManager.GetString(string)` (which respects `Thread.CurrentThread.CurrentUICulture`).
- **Inconsistent error formatting:** Missing keys yield `"#stringnotfound# <key>"`, which may be visually jarring in UI (e.g., `"#stringnotfound# MyKey"`). This is likely intentional for debugging but could confuse end users.
- **No caching of resolved values:** Each `ProvideValue` call performs a fresh `ResourceManager.GetString` lookup. While `ResourceManager` caches internally, repeated use in dynamic UI (e.g., data templates) may incur minor overhead.
- **Auto-generated resource class:** `StringResources` is auto-generated; manual edits to `StringResources.Designer.cs` will be overwritten. Resource keys must match those in the corresponding `.resx` file(s).
- **No null-safety for `ResourceManager.GetString`:** The null-coalescing operator (`??`) handles `null` results, but if `ResourceManager` itself is misconfigured (e.g., wrong base name), `GetString` may throw—though this is unlikely in practice.
- **No support for parameterized strings:** The extension only resolves *static* keys. It does not support string formatting (e.g., `InvalidLine` expects `{0}` placeholders to be filled elsewhere, not via this extension).

View File

@@ -0,0 +1,79 @@
---
source_files:
- DataPRO/Modules/ISO/ExtraProperties/View/ExtraPropertiesListView.xaml.cs
generated_at: "2026-04-16T04:39:06.939358+00:00"
model: "Qwen/Qwen3-Coder-Next-FP8"
schema_version: 1
sha256: "35a6fb72ede64411"
---
# View
## Documentation: `ExtraPropertiesListView` Module
---
### **1. Purpose**
`ExtraPropertiesListView` is a WPF user control implementing the `IExtraPropertiesListView` interface to render and interact with a list of extra properties (key-value pairs) in the ISO module. It serves as the view layer in a MVVM pattern, handling UI events (sorting, filtering, selection, editing) and delegating logic to its bound `IExtraPropertiesListViewModel`. The control supports interactive column sorting (via header clicks or text clicks), filtering by column tag, real-time validation and modification tracking during inline editing of property keys and values, and selection synchronization with the view model.
---
### **2. Public Interface**
The class itself is `public partial class ExtraPropertiesListView : IExtraPropertiesListView`. It exposes no *public* methods or properties beyond those defined by the interface `IExtraPropertiesListView` (not shown in source), but its *event handlers* are the primary public-facing behavior surface. All handlers are private `RoutedEventHandler` or `KeyEventHandler` methods wired via XAML (not visible here, but implied by naming and usage).
| Member | Signature | Behavior |
|--------|-----------|----------|
| `ExtraPropertiesListView()` | `public ExtraPropertiesListView()` | Constructor; calls `InitializeComponent()` to load the associated XAML. |
| `GridViewColumnHeaderSearchable_OnSearch` | `private void GridViewColumnHeaderSearchable_OnSearch(object sender, RoutedEventArgs e)` | Extracts `searchTerm` from `e.OriginalSource` and `columnTag` from the `Tag` of the `GridViewColumnHeaderSearchable` sender, then invokes `viewModel.Filter(columnTag, searchTerm)`. |
| `GridViewColumnHeader_OnClick` | `private void GridViewColumnHeader_OnClick(object sender, RoutedEventArgs e)` | Extracts `columnTag` from the `Tag` of the `GridViewColumnHeaderSearchable` sender (or via `Utils.FindChild`), then invokes `vm?.Sort(columnTag, true)`. |
| `ExtraPropertyKeyTextBox_KeyDown` | `private void ExtraPropertyKeyTextBox_KeyDown(object sender, KeyEventArgs e)` | If `DataContext` is `ExtraPropertiesListViewModel` and the `TextBox`s `DataContext` is `IExtraProperty`, calls `vm.MarkModified(iep)`. |
| `ExtraPropertyKeyTextBox_TextChanged` | `private void ExtraPropertyKeyTextBox_TextChanged(object sender, TextChangedEventArgs e)` | Same context checks as above; calls `vm.MarkModified(iep)` and then `vm.Validate(ref notUsed1, ref notUsed2)` (with unused `List<string>` parameters). |
| `ExtraPropertyValueTextBox_KeyDown` | `private void ExtraPropertyValueTextBox_KeyDown(object sender, KeyEventArgs e)` | Same as `ExtraPropertyKeyTextBox_KeyDown`; calls `vm.MarkModified(iep)`. |
| `ExtraPropertyValueTextBox_TextChanged` | `private void ExtraPropertyValueTextBox_TextChanged(object sender, TextChangedEventArgs e)` | Same as `ExtraPropertyKeyTextBox_TextChanged`; calls `vm.MarkModified(iep)` and `vm.Validate(...)`. |
| `ExtraProperties_SelectionChanged` | `private void ExtraProperties_SelectionChanged(object sender, SelectionChangedEventArgs e)` | Collects selected `IExtraProperty` items from the `ListView`, then calls `vm.SetSelection(...)` with the array. |
| `ExtraPropertiesListView_PreviewMouseLeftButtonUp` | `private void ExtraPropertiesListView_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)` | Performs hit-testing to detect clicks on column headers (`TextBlock`, `KeyColumnHeader`, or `ValueColumnHeader`) or descendants; if detected, calls `vm?.Sort(tag, true)` for the corresponding column. Also returns early if the click is on a `ScrollViewer`. |
> **Note**: Column header tags (`KeyColumnHeader.Tag`, `ValueColumnHeader.Tag`) are used as sort keys. Their values are assumed to be strings (e.g., `"Key"`, `"Value"`), inferred from usage in `PreviewMouseLeftButtonUp`.
---
### **3. Invariants**
- **DataContext Contract**: The `DataContext` must implement `IExtraPropertiesListViewModel` (or be `ExtraPropertiesListViewModel`, a concrete implementation). All event handlers cast `DataContext` to this interface or concrete type before use.
- **Item Type Contract**: Selected items in the `ListView` and `TextBox.DataContext` items must be of type `IExtraProperty`. Non-conforming items are silently skipped in `ExtraProperties_SelectionChanged`.
- **Validation Side Effect**: Every `TextChanged` event on key/value `TextBox`es triggers `vm.Validate(...)`, regardless of whether the text actually changed meaningfully.
- **Sorting Trigger**: Sorting is *always* invoked with `ascending: true` (hardcoded), regardless of previous sort state or direction.
- **Hit-Test Robustness**: The `PreviewMouseLeftButtonUp` handler performs multiple hit-tests (visual tree + input hit-test) to reliably detect clicks on headers despite nested visual elements (e.g., `Border`, `Rectangle`).
---
### **4. Dependencies**
#### **External Dependencies (from imports)**:
- `System.Collections.Generic`, `System.Windows.*`: Standard WPF and .NET types.
- `DTS.Common.Controls`: Contains `GridViewColumnHeaderSearchable` (custom control used for searchable column headers).
- `DTS.Common.Interface.ISO.ExtraProperties`: Defines `IExtraPropertiesListView`, `IExtraPropertiesListViewModel`, and `IExtraProperty`.
- `DTS.Common.Utils`: Provides `Utils.FindChild<T>` (used in `GridViewColumnHeader_OnClick` to locate `Tag` from nested elements).
#### **Internal Dependencies**:
- `ExtraProperties.Resources.StringResources`: Used for localized strings `"Key"` and `"Value"` (in `PreviewMouseLeftButtonUp`).
- `ExtraPropertiesListView.xaml`: The associated XAML file (not shown), which defines `ExtraPropertiesListViewLV`, `KeyColumnHeader`, `ValueColumnHeader`, and event bindings.
#### **Depended Upon By**:
- The module likely binds this view to an `ExtraPropertiesListViewModel` instance (or similar) via `DataContext`. No direct callers are visible, but the interface `IExtraPropertiesListView` implies integration into a larger ISO module UI.
---
### **5. Gotchas**
- **Unused `Validate` Parameters**: Both `TextChanged` handlers call `vm.Validate(ref notUsed1, ref notUsed2)` with newly allocated, unused `List<string>` variables. This suggests either incomplete validation reporting or legacy code where validation errors are ignored by the view.
- **Hardcoded Sort Direction**: `vm.Sort(..., true)` is called unconditionally with `ascending: true`. No logic exists to toggle sort direction (e.g., on repeated clicks), which may confuse users expecting standard UI behavior.
- **Redundant Hit-Testing**: The `PreviewMouseLeftButtonUp` handler performs two hit-tests (`VisualTreeHelper.HitTest` and `InputHitTest`) and checks multiple element types (`TextBlock`, `UIElement`, `ScrollViewer`, `KeyColumnHeader`, `ValueColumnHeader`). This complexity hints at prior instability in click detection.
- **Assumed `Tag` Type**: `columnTag` is used directly as a sort key in `vm.Sort(columnTag, ...)`. Its type is inferred from usage (likely `string`), but not enforced or documented in the source.
- **No Null Safety for `vm`**: Multiple calls use `vm?.Sort(...)` or `vm?.Filter(...)`, but `vm.MarkModified(...)` and `vm.SetSelection(...)` do *not* use null-conditional operators. If `DataContext` is null or wrong type, these will throw.
- **`KeyDown` vs `TextChanged` Duplication**: Both `KeyDown` and `TextChanged` handlers call `MarkModified` and `Validate`. This may cause redundant work or validation flicker if both events fire for a single edit (e.g., typing + Enter).
> **None identified from source alone.**
*(Note: While several potential issues are noted above, they are inferred from code structure and behavior—not documented quirks.)*

View File

@@ -0,0 +1,122 @@
---
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<Notification>` | Prism MVVM Toolkit interaction request for showing non-blocking notifications. |
| `ConfirmationRequest` | `InteractionRequest<Confirmation>` | 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<IExtraProperty>` | Full list of all extra properties (including empty trailing row). |
| `ExtraProperties` | `ObservableCollection<IExtraProperty>` | 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<string> errors)` | `bool Validate(ref List<string> errors)` | Calls internal `Validate(ref errors, ref warnings)`, returns `true` if no new errors added. |
| `SetExtraProperties` | `void SetExtraProperties(IList<IExtraProperty> 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).*