diff --git a/CITATION.cff b/CITATION.cff
index 565733bf..90b37c64 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -2,7 +2,7 @@ cff-version: 1.2.0
message: "If you use this software, please cite it as below."
type: software
title: "WPF Framework"
-version: "1.0.1"
+version: "1.0.2"
date-released: "2026-07-10"
license: 0BSD
repository-code: "https://github.com/USACE-RMC/WPF-Framework"
diff --git a/Directory.Build.props b/Directory.Build.props
index 1579c77b..c1985c6c 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -14,9 +14,9 @@
https://github.com/USACE-RMC/WPF-Framework
https://github.com/USACE-RMC/WPF-Framework
git
- 1.0.1
- 1.0.1.0
- 1.0.1.0
+ 1.0.2
+ 1.0.2.0
+ 1.0.2.0
false
false
diff --git a/README.md b/README.md
index ede7edeb..227bf453 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ dotnet test WPF-Framework.sln
### Build Packages
```bash
-.\scripts\pack-wpf-framework.ps1 -Configuration Release -Version 1.0.1
+.\scripts\pack-wpf-framework.ps1 -Configuration Release -Version 1.0.2
```
This creates and validates the following NuGet packages in `artifacts/packages/`:
diff --git a/codemeta.json b/codemeta.json
index dc43e75f..6bf12afa 100644
--- a/codemeta.json
+++ b/codemeta.json
@@ -3,7 +3,7 @@
"@type": "SoftwareSourceCode",
"name": "WPF Framework",
"description": "A free and open-source .NET 10 application framework for building Windows desktop project management applications. Provides a docking application shell with theming, project explorer, undo/redo, and specialized controls for charting, numeric input, databases, expression parsing, and directed acyclic graphs.",
- "version": "1.0.1",
+ "version": "1.0.2",
"dateCreated": "2025-12-29",
"dateModified": "2026-07-10",
"license": "https://spdx.org/licenses/0BSD",
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 31d7b06f..2531b3ac 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -30,7 +30,7 @@ For NuGet consumption, reference the Controls bundle. It brings in Core, Models,
```xml
-
+
```
diff --git a/scripts/pack-wpf-framework.ps1 b/scripts/pack-wpf-framework.ps1
index 2055bbf1..9b667100 100644
--- a/scripts/pack-wpf-framework.ps1
+++ b/scripts/pack-wpf-framework.ps1
@@ -77,10 +77,10 @@ $expectedEntries = @{
}
$expectedReleaseNotes = @{
- "RMC.Wpf.Framework.Core" = "Coordinated WPF Framework 1.0.1 release; no package-specific functional changes."
- "RMC.Wpf.Framework.Models" = "Coordinated WPF Framework 1.0.1 release; no package-specific functional changes."
- "RMC.Wpf.Framework.Support" = "Version 1.0.1 protects settings and runtime data during updates, supports transactional rollback, checksum enforcement, and safe replacement of the installed updater payload."
- "RMC.Wpf.Framework.Controls" = "Version 1.0.1 improves irregular time-series ordering validation and defaults new OxyPlot line annotations to editable text."
+ "RMC.Wpf.Framework.Core" = "Coordinated WPF Framework 1.0.2 release; no package-specific functional changes."
+ "RMC.Wpf.Framework.Models" = "Coordinated WPF Framework 1.0.2 release; no package-specific functional changes."
+ "RMC.Wpf.Framework.Support" = "Version 1.0.2 carries forward protected, transactional self-updates and ships a version-aligned updater payload."
+ "RMC.Wpf.Framework.Controls" = "Version 1.0.2 improves time-series table validation performance, preserves ordering checks after edits, and avoids repeated full-table scans while rebuilding large series."
}
foreach ($packageId in $expectedEntries.Keys) {
diff --git a/src/GenericControls/DataGrid/ValidationDataGrid.cs b/src/GenericControls/DataGrid/ValidationDataGrid.cs
index 4a91f944..83b3f534 100644
--- a/src/GenericControls/DataGrid/ValidationDataGrid.cs
+++ b/src/GenericControls/DataGrid/ValidationDataGrid.cs
@@ -27,6 +27,39 @@ namespace GenericControls
public sealed class ValidationDataGrid : CopyPasteDataGrid
{
+ ///
+ /// Restores validation when a suspension scope is disposed.
+ ///
+ private sealed class ValidationSuspension : IDisposable
+ {
+ private ValidationDataGrid _owner;
+ private readonly bool _validateOnResume;
+
+ ///
+ /// Initializes a new validation suspension scope.
+ ///
+ /// The grid whose validation is suspended.
+ /// Whether to request validation after the outermost scope is disposed.
+ public ValidationSuspension(ValidationDataGrid owner, bool validateOnResume)
+ {
+ _owner = owner;
+ _validateOnResume = validateOnResume;
+ }
+
+ ///
+ /// Releases this suspension scope and optionally requests deferred validation.
+ ///
+ public void Dispose()
+ {
+ ValidationDataGrid owner = _owner;
+ if (owner == null)
+ return;
+
+ _owner = null;
+ owner.ResumeValidation(_validateOnResume);
+ }
+ }
+
#region Construction
///
/// Initializes a new instance of the class.
@@ -68,6 +101,10 @@ public ValidationDataGrid()
private Dictionary _propertyTypes = new Dictionary();
+ private int _validationSuspensionCount;
+ private bool _operationValidationSuppressed;
+ private bool _validationRequestedOnResume;
+
///
/// Identifies the dependency property.
@@ -113,7 +150,7 @@ public Brush ErrorCellBackgroundBrush
///
/// Gets a value indicating whether validation should be suppressed.
///
- public bool SuppressValidation { get; private set; } = false;
+ public bool SuppressValidation => _operationValidationSuppressed || _validationSuspensionCount > 0;
///
@@ -125,6 +162,51 @@ public Brush ErrorCellBackgroundBrush
#region Methods
+ ///
+ /// Suspends row validation until the returned scope is disposed.
+ ///
+ /// Whether to validate once after the outermost suspension scope is disposed.
+ /// An exception-safe scope that restores validation when disposed.
+ ///
+ /// Scopes may be nested. Validation requested while suspended is deferred until both
+ /// scoped and grid-operation suppression have ended.
+ ///
+ public IDisposable SuspendValidation(bool validateOnResume = true)
+ {
+ _validationSuspensionCount++;
+ return new ValidationSuspension(this, validateOnResume);
+ }
+
+ ///
+ /// Releases one validation suspension and runs any requested validation after the outermost scope.
+ ///
+ /// Whether this scope requests validation on resume.
+ private void ResumeValidation(bool validateOnResume)
+ {
+ if (_validationSuspensionCount <= 0)
+ return;
+
+ if (validateOnResume)
+ {
+ _validationRequestedOnResume = true;
+ }
+
+ _validationSuspensionCount--;
+ TryRunDeferredValidation();
+ }
+
+ ///
+ /// Runs a deferred validation request when no suppression remains active.
+ ///
+ private void TryRunDeferredValidation()
+ {
+ if (SuppressValidation || !_validationRequestedOnResume)
+ return;
+
+ _validationRequestedOnResume = false;
+ ValidateTable();
+ }
+
///
/// Determines whether the BeginEdit command can execute.
///
@@ -450,7 +532,10 @@ private void Grid_RowsAdded(int startrow, int numrows)
public void ValidateTable()
{
if (SuppressValidation == true)
+ {
+ _validationRequestedOnResume = true;
return;
+ }
if (ItemsSource == null)
return;
IList