diff --git a/CITATION.cff b/CITATION.cff index 90b37c64..6ffa2109 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,8 +2,8 @@ cff-version: 1.2.0 message: "If you use this software, please cite it as below." type: software title: "WPF Framework" -version: "1.0.2" -date-released: "2026-07-10" +version: "1.0.3" +date-released: "2026-07-14" license: 0BSD repository-code: "https://github.com/USACE-RMC/WPF-Framework" url: "https://github.com/USACE-RMC/WPF-Framework" diff --git a/Directory.Build.props b/Directory.Build.props index c1985c6c..77c75074 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.2 - 1.0.2.0 - 1.0.2.0 + 1.0.3 + 1.0.3.0 + 1.0.3.0 false false diff --git a/README.md b/README.md index 227bf453..70985220 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.2 +.\scripts\pack-wpf-framework.ps1 -Configuration Release -Version 1.0.3 ``` This creates and validates the following NuGet packages in `artifacts/packages/`: diff --git a/codemeta.json b/codemeta.json index 6bf12afa..36c0cede 100644 --- a/codemeta.json +++ b/codemeta.json @@ -3,9 +3,9 @@ "@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.2", + "version": "1.0.3", "dateCreated": "2025-12-29", - "dateModified": "2026-07-10", + "dateModified": "2026-07-14", "license": "https://spdx.org/licenses/0BSD", "codeRepository": "https://github.com/USACE-RMC/WPF-Framework", "issueTracker": "https://github.com/USACE-RMC/WPF-Framework/issues", diff --git a/docs/getting-started.md b/docs/getting-started.md index 2531b3ac..9896704c 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 9b667100..08078b0c 100644 --- a/scripts/pack-wpf-framework.ps1 +++ b/scripts/pack-wpf-framework.ps1 @@ -76,11 +76,33 @@ $expectedEntries = @{ "lib/net10.0-windows7.0/Xceed.Wpf.AvalonDock.Themes.VS2013.dll") } +$expectedVersionedEntries = @{ + "RMC.Wpf.Framework.Core" = @( + "lib/net10.0-windows7.0/FrameworkInterfaces.dll", + "lib/net10.0-windows7.0/Themes.dll") + "RMC.Wpf.Framework.Models" = @( + "lib/net10.0-windows7.0/DAG.dll", + "lib/net10.0-windows7.0/DatabaseManager.dll", + "lib/net10.0-windows7.0/ExpressionParser.dll") + "RMC.Wpf.Framework.Support" = @( + "lib/net10.0-windows7.0/SoftwareUpdate.dll", + "contentFiles/any/net10.0-windows7.0/SoftwareUpdate.Updater.exe", + "contentFiles/any/net10.0-windows7.0/SoftwareUpdate.Updater.dll") + "RMC.Wpf.Framework.Controls" = @( + "lib/net10.0-windows7.0/GenericControls.dll", + "lib/net10.0-windows7.0/FrameworkUI.dll", + "lib/net10.0-windows7.0/DAGControls.dll", + "lib/net10.0-windows7.0/DatabaseControls.dll", + "lib/net10.0-windows7.0/ExpressionParserControls.dll", + "lib/net10.0-windows7.0/NumericControls.dll", + "lib/net10.0-windows7.0/OxyPlotControls.dll") +} + $expectedReleaseNotes = @{ - "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." + "RMC.Wpf.Framework.Core" = "Coordinated WPF Framework 1.0.3 release; no package-specific functional changes." + "RMC.Wpf.Framework.Models" = "Coordinated WPF Framework 1.0.3 release; no package-specific functional changes." + "RMC.Wpf.Framework.Support" = "Coordinated WPF Framework 1.0.3 release; no package-specific functional changes." + "RMC.Wpf.Framework.Controls" = "Version 1.0.3 defaults the DatabaseControls TableViewer Export Table dialog to CSV while retaining DBF, Excel, and SQLite options." } foreach ($packageId in $expectedEntries.Keys) { @@ -134,35 +156,37 @@ foreach ($packageId in $expectedEntries.Keys) { throw "$($package.Name) has unexpected NuGet release notes." } - if ($packageId -eq "RMC.Wpf.Framework.Support") { - $numericVersion = ($packageVersion -split '-')[0] - $expectedFileVersion = "$numericVersion.0" - $payloadBinaryEntries = @( - "contentFiles/any/net10.0-windows7.0/SoftwareUpdate.Updater.exe", - "contentFiles/any/net10.0-windows7.0/SoftwareUpdate.Updater.dll") - $inspectionDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "wpf-framework-package-$([Guid]::NewGuid().ToString('N'))" - New-Item -ItemType Directory -Path $inspectionDirectory | Out-Null - - try { - foreach ($payloadEntryName in $payloadBinaryEntries) { - $payloadEntry = $zip.GetEntry($payloadEntryName) - $payloadPath = Join-Path $inspectionDirectory ([System.IO.Path]::GetFileName($payloadEntryName)) - [System.IO.Compression.ZipFileExtensions]::ExtractToFile($payloadEntry, $payloadPath, $false) - $versionInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($payloadPath) - - if ($versionInfo.FileVersion -ne $expectedFileVersion) { - throw "$($package.Name) payload $payloadEntryName has file version $($versionInfo.FileVersion) instead of $expectedFileVersion." - } + $numericVersion = ($packageVersion -split '-')[0] + $expectedBinaryVersion = "$numericVersion.0" + $inspectionDirectory = Join-Path ([System.IO.Path]::GetTempPath()) "wpf-framework-package-$([Guid]::NewGuid().ToString('N'))" + New-Item -ItemType Directory -Path $inspectionDirectory | Out-Null - if ([string]::IsNullOrWhiteSpace($versionInfo.ProductVersion) -or - -not $versionInfo.ProductVersion.StartsWith($packageVersion, [System.StringComparison]::OrdinalIgnoreCase)) { - throw "$($package.Name) payload $payloadEntryName has product version $($versionInfo.ProductVersion) instead of $packageVersion." + try { + foreach ($payloadEntryName in $expectedVersionedEntries[$packageId]) { + $payloadEntry = $zip.GetEntry($payloadEntryName) + $payloadPath = Join-Path $inspectionDirectory ([System.IO.Path]::GetFileName($payloadEntryName)) + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($payloadEntry, $payloadPath, $false) + $versionInfo = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($payloadPath) + + if ($versionInfo.FileVersion -ne $expectedBinaryVersion) { + throw "$($package.Name) payload $payloadEntryName has file version $($versionInfo.FileVersion) instead of $expectedBinaryVersion." + } + + if ([string]::IsNullOrWhiteSpace($versionInfo.ProductVersion) -or + -not $versionInfo.ProductVersion.StartsWith($packageVersion, [System.StringComparison]::OrdinalIgnoreCase)) { + throw "$($package.Name) payload $payloadEntryName has product version $($versionInfo.ProductVersion) instead of $packageVersion." + } + + if ([System.IO.Path]::GetExtension($payloadPath).Equals(".dll", [System.StringComparison]::OrdinalIgnoreCase)) { + $assemblyVersion = [System.Reflection.AssemblyName]::GetAssemblyName($payloadPath).Version.ToString() + if ($assemblyVersion -ne $expectedBinaryVersion) { + throw "$($package.Name) payload $payloadEntryName has assembly version $assemblyVersion instead of $expectedBinaryVersion." } } } - finally { - Remove-Item -LiteralPath $inspectionDirectory -Recurse -Force -ErrorAction SilentlyContinue - } + } + finally { + Remove-Item -LiteralPath $inspectionDirectory -Recurse -Force -ErrorAction SilentlyContinue } } finally { diff --git a/src/DAGControls/Properties/AssemblyInfo.cs b/src/DAGControls/Properties/AssemblyInfo.cs index 6561bfcd..cb9a9684 100644 --- a/src/DAGControls/Properties/AssemblyInfo.cs +++ b/src/DAGControls/Properties/AssemblyInfo.cs @@ -50,5 +50,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.0.3.0")] +[assembly: AssemblyFileVersion("1.0.3.0")] diff --git a/src/DatabaseControls/TableViewer.xaml.cs b/src/DatabaseControls/TableViewer.xaml.cs index f53676f8..3e4110f6 100644 --- a/src/DatabaseControls/TableViewer.xaml.cs +++ b/src/DatabaseControls/TableViewer.xaml.cs @@ -4369,7 +4369,7 @@ private void ExportTable() string filters = "comma delimited(*.csv) |*.csv|database(*.dbf) |*.dbf|Excel(*.xlsx) |*.xlsx|Sqlite(*.sqlite) |*.sqlite"; try { - var saveFileBrowser = new SaveFileDialog { Filter = filters, FilterIndex = 3 }; + var saveFileBrowser = new SaveFileDialog { Filter = filters, FilterIndex = 1 }; if (saveFileBrowser.ShowDialog() == true) { switch (System.IO.Path.GetExtension(saveFileBrowser.FileName)) diff --git a/src/OxyPlotControls/Properties/AssemblyInfo.cs b/src/OxyPlotControls/Properties/AssemblyInfo.cs index 61d2814c..99132de8 100644 --- a/src/OxyPlotControls/Properties/AssemblyInfo.cs +++ b/src/OxyPlotControls/Properties/AssemblyInfo.cs @@ -28,8 +28,8 @@ // Build Number // Revision // -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.0.3.0")] +[assembly: AssemblyFileVersion("1.0.3.0")] [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located diff --git a/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.csproj b/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.csproj index cad8a745..7f06e0dd 100644 --- a/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.csproj +++ b/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.csproj @@ -3,7 +3,7 @@ net10.0-windows true false - 1.0.2 + 1.0.3 RMC.Wpf.Framework.Controls.nuspec $(MSBuildProjectDirectory) configuration=$(Configuration);version=$(Version);rmcNumericsPackageDependencyVersion=$(RmcNumericsPackageDependencyVersion) diff --git a/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.nuspec b/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.nuspec index 675f7e20..b3db213f 100644 --- a/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.nuspec +++ b/src/Packaging/RMC.Wpf.Framework.Controls/RMC.Wpf.Framework.Controls.nuspec @@ -10,7 +10,7 @@ README.md WPF controls, framework UI, DAG controls, database controls, numeric controls, OxyPlot controls, and AvalonDock assemblies for the RMC WPF Framework. - 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. + Version 1.0.3 defaults the DatabaseControls TableViewer Export Table dialog to CSV while retaining DBF, Excel, and SQLite options. RMC WPF framework controls AvalonDock OxyPlot numeric database DAG diff --git a/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.csproj b/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.csproj index 1f96a19f..9b19b29a 100644 --- a/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.csproj +++ b/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.csproj @@ -3,7 +3,7 @@ net10.0-windows true false - 1.0.2 + 1.0.3 RMC.Wpf.Framework.Core.nuspec $(MSBuildProjectDirectory) configuration=$(Configuration);version=$(Version) diff --git a/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.nuspec b/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.nuspec index c3b56d3a..49da43b9 100644 --- a/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.nuspec +++ b/src/Packaging/RMC.Wpf.Framework.Core/RMC.Wpf.Framework.Core.nuspec @@ -10,7 +10,7 @@ README.md Core interfaces, project model contracts, undo/redo infrastructure, messaging, and WPF themes for the RMC WPF Framework. - Coordinated WPF Framework 1.0.2 release; no package-specific functional changes. + Coordinated WPF Framework 1.0.3 release; no package-specific functional changes. RMC WPF framework themes undo-redo diff --git a/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.csproj b/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.csproj index a4038bc9..24742ea2 100644 --- a/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.csproj +++ b/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.csproj @@ -3,7 +3,7 @@ net10.0-windows true false - 1.0.2 + 1.0.3 RMC.Wpf.Framework.Models.nuspec $(MSBuildProjectDirectory) configuration=$(Configuration);version=$(Version);closedXmlVersion=$(ClosedXmlVersion);documentFormatOpenXmlVersion=$(DocumentFormatOpenXmlVersion);excelNumberFormatVersion=$(ExcelNumberFormatVersion);fastMemberVersion=$(FastMemberVersion);sourceGearSqlite3Version=$(SourceGearSqlite3Version);systemDataSQLiteVersion=$(SystemDataSQLiteVersion) diff --git a/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.nuspec b/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.nuspec index 5ff8f9d3..69be73ba 100644 --- a/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.nuspec +++ b/src/Packaging/RMC.Wpf.Framework.Models/RMC.Wpf.Framework.Models.nuspec @@ -10,7 +10,7 @@ README.md Model, graph, database, expression-parser, and vendored OxyPlot assemblies for the RMC WPF Framework. - Coordinated WPF Framework 1.0.2 release; no package-specific functional changes. + Coordinated WPF Framework 1.0.3 release; no package-specific functional changes. RMC WPF framework database expression-parser DAG OxyPlot diff --git a/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.csproj b/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.csproj index 4bd69ee1..73bb61e1 100644 --- a/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.csproj +++ b/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.csproj @@ -3,7 +3,7 @@ net10.0-windows true false - 1.0.2 + 1.0.3 $(NoWarn);NU5100 RMC.Wpf.Framework.Support.nuspec diff --git a/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.nuspec b/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.nuspec index 101bde80..3dfdff43 100644 --- a/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.nuspec +++ b/src/Packaging/RMC.Wpf.Framework.Support/RMC.Wpf.Framework.Support.nuspec @@ -10,7 +10,7 @@ README.md Software update services and deployable updater helper for the RMC WPF Framework. - Version 1.0.2 carries forward protected, transactional self-updates and ships a version-aligned updater payload. + Coordinated WPF Framework 1.0.3 release; no package-specific functional changes. RMC WPF framework software-update updater diff --git a/src/SoftwareUpdate.Updater/SoftwareUpdate.Updater.csproj b/src/SoftwareUpdate.Updater/SoftwareUpdate.Updater.csproj index 656d6e65..e9f05f1d 100644 --- a/src/SoftwareUpdate.Updater/SoftwareUpdate.Updater.csproj +++ b/src/SoftwareUpdate.Updater/SoftwareUpdate.Updater.csproj @@ -8,9 +8,9 @@ enable enable latest - 1.0.2 - 1.0.2.0 - 1.0.2.0 + 1.0.3 + 1.0.3.0 + 1.0.3.0 diff --git a/tests/Themes.Tests/Core/TableViewerExportTests.cs b/tests/Themes.Tests/Core/TableViewerExportTests.cs new file mode 100644 index 00000000..8165794e --- /dev/null +++ b/tests/Themes.Tests/Core/TableViewerExportTests.cs @@ -0,0 +1,86 @@ +using System.IO; +using Xunit; + +namespace Themes.Tests.Core; + +/// +/// Regression tests for TableViewer export dialog defaults. +/// +public class TableViewerExportTests +{ + /// + /// Verifies the Export Table dialog defaults to comma-delimited output. + /// + [Fact] + public void ExportTable_DefaultsToCsv() + { + string source = File.ReadAllText(ResolveRepoPath("src/DatabaseControls/TableViewer.xaml.cs")); + string exportTable = ExtractMethod(source, "private void ExportTable()"); + + Assert.Contains("string filters = \"comma delimited(*.csv) |*.csv|", exportTable); + Assert.Contains("new SaveFileDialog { Filter = filters, FilterIndex = 1 }", exportTable); + Assert.DoesNotContain("FilterIndex = 3", exportTable); + } + + /// + /// Extracts a method body from a C# source file. + /// + /// The source text. + /// The method signature prefix. + /// The method body, including the signature and braces. + private static string ExtractMethod(string source, string signature) + { + int start = source.IndexOf(signature, StringComparison.Ordinal); + Assert.True(start >= 0, signature + " should exist."); + + int bodyStart = source.IndexOf('{', start); + Assert.True(bodyStart >= start, signature + " should have a body."); + + int depth = 0; + for (int i = bodyStart; i < source.Length; i++) + { + if (source[i] == '{') + { + depth++; + } + else if (source[i] == '}') + { + depth--; + if (depth == 0) + { + return source.Substring(start, i - start + 1); + } + } + } + + throw new InvalidOperationException(signature + " body was not closed."); + } + + /// + /// Resolves a repository-relative path from the test output directory. + /// + /// The repository-relative path. + /// The absolute path. + private static string ResolveRepoPath(string repoRelativePath) + { + string current = AppContext.BaseDirectory; + while (!string.IsNullOrEmpty(current)) + { + if (File.Exists(Path.Combine(current, "WPF-Framework.sln"))) + { + return Path.Combine(current, repoRelativePath.Replace('/', Path.DirectorySeparatorChar)); + } + + string? parent = Path.GetDirectoryName(current); + if (parent == current) + { + break; + } + + current = parent!; + } + + throw new InvalidOperationException( + $"Could not locate repository root (looking for WPF-Framework.sln) starting from {AppContext.BaseDirectory}"); + } +}