├── .editorconfig ├── .gitignore ├── .vscode └── tasks.json ├── ExcelDna.Utilities.sln ├── ExcelDna.Utilities ├── DataTableEx.cs ├── Enums.cs ├── ExcelDna.Utilities.csproj ├── ExcelReferenceEx.cs ├── Name.cs ├── WorkBook.cs ├── WorkSheet.cs ├── XLApp.cs ├── XLConversion.cs ├── XLDBWrapper.cs ├── XLDate.cs ├── XLObjectMapper.cs ├── XLObjectMapping.cs └── globals.cs ├── LICENSE └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{cs,vb}] 2 | 3 | # IDE0044: Add readonly modifier 4 | dotnet_diagnostic.IDE0044.severity = silent 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # Tye 65 | .tye/ 66 | 67 | # StyleCop 68 | StyleCopReport.xml 69 | 70 | # Files built by Visual Studio 71 | *_i.c 72 | *_p.c 73 | *_h.h 74 | *.ilk 75 | *.meta 76 | *.obj 77 | *.iobj 78 | *.pch 79 | *.pdb 80 | *.ipdb 81 | *.pgc 82 | *.pgd 83 | *.rsp 84 | *.sbr 85 | *.tlb 86 | *.tli 87 | *.tlh 88 | *.tmp 89 | *.tmp_proj 90 | *_wpftmp.csproj 91 | *.log 92 | *.vspscc 93 | *.vssscc 94 | .builds 95 | *.pidb 96 | *.svclog 97 | *.scc 98 | 99 | # Chutzpah Test files 100 | _Chutzpah* 101 | 102 | # Visual C++ cache files 103 | ipch/ 104 | *.aps 105 | *.ncb 106 | *.opendb 107 | *.opensdf 108 | *.sdf 109 | *.cachefile 110 | *.VC.db 111 | *.VC.VC.opendb 112 | 113 | # Visual Studio profiler 114 | *.psess 115 | *.vsp 116 | *.vspx 117 | *.sap 118 | 119 | # Visual Studio Trace Files 120 | *.e2e 121 | 122 | # TFS 2012 Local Workspace 123 | $tf/ 124 | 125 | # Guidance Automation Toolkit 126 | *.gpState 127 | 128 | # ReSharper is a .NET coding add-in 129 | _ReSharper*/ 130 | *.[Rr]e[Ss]harper 131 | *.DotSettings.user 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # AxoCover is a Code Coverage Tool 140 | .axoCover/* 141 | !.axoCover/settings.json 142 | 143 | # Coverlet is a free, cross platform Code Coverage Tool 144 | coverage*[.json, .xml, .info] 145 | 146 | # Visual Studio code coverage results 147 | *.coverage 148 | *.coveragexml 149 | 150 | # NCrunch 151 | _NCrunch_* 152 | .*crunch*.local.xml 153 | nCrunchTemp_* 154 | 155 | # MightyMoose 156 | *.mm.* 157 | AutoTest.Net/ 158 | 159 | # Web workbench (sass) 160 | .sass-cache/ 161 | 162 | # Installshield output folder 163 | [Ee]xpress/ 164 | 165 | # DocProject is a documentation generator add-in 166 | DocProject/buildhelp/ 167 | DocProject/Help/*.HxT 168 | DocProject/Help/*.HxC 169 | DocProject/Help/*.hhc 170 | DocProject/Help/*.hhk 171 | DocProject/Help/*.hhp 172 | DocProject/Help/Html2 173 | DocProject/Help/html 174 | 175 | # Click-Once directory 176 | publish/ 177 | 178 | # Publish Web Output 179 | *.[Pp]ublish.xml 180 | *.azurePubxml 181 | # Note: Comment the next line if you want to checkin your web deploy settings, 182 | # but database connection strings (with potential passwords) will be unencrypted 183 | *.pubxml 184 | *.publishproj 185 | 186 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 187 | # checkin your Azure Web App publish settings, but sensitive information contained 188 | # in these scripts will be unencrypted 189 | PublishScripts/ 190 | 191 | # NuGet Packages 192 | *.nupkg 193 | # NuGet Symbol Packages 194 | *.snupkg 195 | # The packages folder can be ignored because of Package Restore 196 | **/[Pp]ackages/* 197 | # except build/, which is used as an MSBuild target. 198 | !**/[Pp]ackages/build/ 199 | # Uncomment if necessary however generally it will be regenerated when needed 200 | #!**/[Pp]ackages/repositories.config 201 | # NuGet v3's project.json files produces more ignorable files 202 | *.nuget.props 203 | *.nuget.targets 204 | 205 | # Microsoft Azure Build Output 206 | csx/ 207 | *.build.csdef 208 | 209 | # Microsoft Azure Emulator 210 | ecf/ 211 | rcf/ 212 | 213 | # Windows Store app package directories and files 214 | AppPackages/ 215 | BundleArtifacts/ 216 | Package.StoreAssociation.xml 217 | _pkginfo.txt 218 | *.appx 219 | *.appxbundle 220 | *.appxupload 221 | 222 | # Visual Studio cache files 223 | # files ending in .cache can be ignored 224 | *.[Cc]ache 225 | # but keep track of directories ending in .cache 226 | !?*.[Cc]ache/ 227 | 228 | # Others 229 | ClientBin/ 230 | ~$* 231 | *~ 232 | *.dbmdl 233 | *.dbproj.schemaview 234 | *.jfm 235 | *.pfx 236 | *.publishsettings 237 | orleans.codegen.cs 238 | 239 | # Including strong name files can present a security risk 240 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 241 | #*.snk 242 | 243 | # Since there are multiple workflows, uncomment next line to ignore bower_components 244 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 245 | #bower_components/ 246 | 247 | # RIA/Silverlight projects 248 | Generated_Code/ 249 | 250 | # Backup & report files from converting an old project file 251 | # to a newer Visual Studio version. Backup files are not needed, 252 | # because we have git ;-) 253 | _UpgradeReport_Files/ 254 | Backup*/ 255 | UpgradeLog*.XML 256 | UpgradeLog*.htm 257 | ServiceFabricBackup/ 258 | *.rptproj.bak 259 | 260 | # SQL Server files 261 | *.mdf 262 | *.ldf 263 | *.ndf 264 | 265 | # Business Intelligence projects 266 | *.rdl.data 267 | *.bim.layout 268 | *.bim_*.settings 269 | *.rptproj.rsuser 270 | *- [Bb]ackup.rdl 271 | *- [Bb]ackup ([0-9]).rdl 272 | *- [Bb]ackup ([0-9][0-9]).rdl 273 | 274 | # Microsoft Fakes 275 | FakesAssemblies/ 276 | 277 | # GhostDoc plugin setting file 278 | *.GhostDoc.xml 279 | 280 | # Node.js Tools for Visual Studio 281 | .ntvs_analysis.dat 282 | node_modules/ 283 | 284 | # Visual Studio 6 build log 285 | *.plg 286 | 287 | # Visual Studio 6 workspace options file 288 | *.opt 289 | 290 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 291 | *.vbw 292 | 293 | # Visual Studio LightSwitch build output 294 | **/*.HTMLClient/GeneratedArtifacts 295 | **/*.DesktopClient/GeneratedArtifacts 296 | **/*.DesktopClient/ModelManifest.xml 297 | **/*.Server/GeneratedArtifacts 298 | **/*.Server/ModelManifest.xml 299 | _Pvt_Extensions 300 | 301 | # Paket dependency manager 302 | .paket/paket.exe 303 | paket-files/ 304 | 305 | # FAKE - F# Make 306 | .fake/ 307 | 308 | # Ionide - VsCode extension for F# Support 309 | .ionide/ 310 | 311 | # CodeRush personal settings 312 | .cr/personal 313 | 314 | # Python Tools for Visual Studio (PTVS) 315 | __pycache__/ 316 | *.pyc 317 | 318 | # Cake - Uncomment if you are using it 319 | # tools/** 320 | # !tools/packages.config 321 | 322 | # Tabs Studio 323 | *.tss 324 | 325 | # Telerik's JustMock configuration file 326 | *.jmconfig 327 | 328 | # BizTalk build output 329 | *.btp.cs 330 | *.btm.cs 331 | *.odx.cs 332 | *.xsd.cs 333 | 334 | # OpenCover UI analysis results 335 | OpenCover/ 336 | 337 | # Azure Stream Analytics local run output 338 | ASALocalRun/ 339 | 340 | # MSBuild Binary and Structured Log 341 | *.binlog 342 | 343 | # NVidia Nsight GPU debugger configuration file 344 | *.nvuser 345 | 346 | # MFractors (Xamarin productivity tool) working folder 347 | .mfractor/ 348 | 349 | # Local History for Visual Studio 350 | .localhistory/ 351 | 352 | # BeatPulse healthcheck temp database 353 | healthchecksdb 354 | 355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 356 | MigrationBackup/ 357 | 358 | # Ionide (cross platform F# VS Code tools) working folder 359 | .ionide/ 360 | 361 | ## 362 | ## Visual studio for Mac 363 | ## 364 | 365 | 366 | # globs 367 | Makefile.in 368 | *.userprefs 369 | *.usertasks 370 | config.make 371 | config.status 372 | aclocal.m4 373 | install-sh 374 | autom4te.cache/ 375 | *.tar.gz 376 | tarballs/ 377 | test-results/ 378 | 379 | # Mac bundle stuff 380 | *.dmg 381 | *.app 382 | 383 | # content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore 384 | # General 385 | .DS_Store 386 | .AppleDouble 387 | .LSOverride 388 | 389 | # Icon must end with two \r 390 | Icon 391 | 392 | 393 | # Thumbnails 394 | ._* 395 | 396 | # Files that might appear in the root of a volume 397 | .DocumentRevisions-V100 398 | .fseventsd 399 | .Spotlight-V100 400 | .TemporaryItems 401 | .Trashes 402 | .VolumeIcon.icns 403 | .com.apple.timemachine.donotpresent 404 | 405 | # Directories potentially created on remote AFP share 406 | .AppleDB 407 | .AppleDesktop 408 | Network Trash Folder 409 | Temporary Items 410 | .apdisk 411 | 412 | # content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore 413 | # Windows thumbnail cache files 414 | Thumbs.db 415 | ehthumbs.db 416 | ehthumbs_vista.db 417 | 418 | # Dump file 419 | *.stackdump 420 | 421 | # Folder config file 422 | [Dd]esktop.ini 423 | 424 | # Recycle Bin used on file shares 425 | $RECYCLE.BIN/ 426 | 427 | # Windows Installer files 428 | *.cab 429 | *.msi 430 | *.msix 431 | *.msm 432 | *.msp 433 | 434 | # Windows shortcuts 435 | *.lnk 436 | 437 | # JetBrains Rider 438 | .idea/ 439 | *.sln.iml 440 | 441 | ## 442 | ## Visual Studio Code 443 | ## 444 | .vscode/* 445 | !.vscode/settings.json 446 | !.vscode/tasks.json 447 | !.vscode/launch.json 448 | !.vscode/extensions.json 449 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/ExcelDna.Utilities.sln", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /ExcelDna.Utilities.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.6.30114.105 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelDna.Utilities", "ExcelDna.Utilities\ExcelDna.Utilities.csproj", "{FB173F9D-7301-4C0A-A195-47924E9820A8}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Debug|x64.Build.0 = Debug|Any CPU 25 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Debug|x86.Build.0 = Debug|Any CPU 27 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Release|x64.ActiveCfg = Release|Any CPU 30 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Release|x64.Build.0 = Release|Any CPU 31 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Release|x86.ActiveCfg = Release|Any CPU 32 | {FB173F9D-7301-4C0A-A195-47924E9820A8}.Release|x86.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/DataTableEx.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | namespace ExcelDna.Utilities; 26 | 27 | public static class DataTableEx 28 | { 29 | 30 | public static object[,] ToVariant(this DataTable dt, bool header = false) 31 | { 32 | if (dt.Columns.Count == 0) return new object[1, 1] { { 0 } }; 33 | 34 | int n = dt.Rows.Count, cols = dt.Columns.Count; 35 | int rows = header ? n + 1 : n; 36 | int start = header ? 1 : 0; 37 | 38 | 39 | object[,] retval = new object[rows, cols]; 40 | 41 | if (header) 42 | { 43 | for (int i = 0; i < cols; i++) 44 | retval[0, i] = dt.Columns[i].Caption; 45 | } 46 | 47 | for (int i = 0; i < n; i++) 48 | { 49 | for (int j = 0; j < cols; j++) 50 | retval[start, j] = dt.Rows[i][dt.Columns[j].ColumnName]; 51 | start++; 52 | } 53 | 54 | return retval; 55 | 56 | } 57 | 58 | public static string ToString(this DataTable dt, string sep = ",", bool header = false) 59 | { 60 | var sb = new StringBuilder(); 61 | if (dt.Rows.Count == 0) return string.Empty; 62 | 63 | int n = dt.Rows.Count, cols = dt.Columns.Count; 64 | int rows = header ? n + 1 : n; 65 | int start = header ? 1 : 0; 66 | 67 | 68 | if (header) 69 | { 70 | for (int i = 0; i < cols; i++) 71 | sb.Append(dt.Columns[i].Caption + sep); 72 | sb.Length -= sep.Length; 73 | sb.AppendLine(); 74 | } 75 | 76 | for (int i = 0; i < n; i++) 77 | { 78 | for (int j = 0; j < cols; j++) 79 | sb.Append(dt.Rows[i][dt.Columns[j].ColumnName].ToString() + sep); 80 | 81 | sb.Length -= sep.Length; 82 | sb.AppendLine(); 83 | start++; 84 | } 85 | return sb.ToString(); 86 | } 87 | 88 | public static void AddRange(this DataTable dt, object vt) 89 | { 90 | if (vt is not object[,] vtdata) 91 | throw new ArgumentException("vt must be an variant array!"); 92 | 93 | int n = vtdata.GetLength(0); 94 | int k = vtdata.GetLength(1); 95 | 96 | var cols = dt.Columns; 97 | if (cols.Count != k) 98 | throw new ArgumentException("Number of columns does not match the columns in vt!"); 99 | 100 | for (int i = 0; i < n; i++) 101 | { 102 | var row = dt.NewRow(); 103 | for (int j = 0; j < k; j++) 104 | { 105 | row[cols[j].ColumnName] = vtdata[i, j].ConvertTo(cols[j].DataType); 106 | } 107 | dt.Rows.Add(row); 108 | } 109 | } 110 | 111 | public static string GetHeader(this DataTable dt, string sep = ",") 112 | { 113 | StringBuilder sb = new(); 114 | for (int i = 0; i < dt.Columns.Count; i++) 115 | { 116 | sb.Append(dt.Columns[i].Caption + sep); 117 | } 118 | return sb.ToString(0, sb.Length - sep.Length); 119 | } 120 | 121 | public static DataTable CreateDataTable(this object vt, bool header = true) 122 | { 123 | if (vt is not object[,] vtdata) 124 | throw new ArgumentException("vt must be a 2-dimensional variant array!"); 125 | 126 | var dt = new DataTable(); 127 | 128 | int n = vtdata.GetLength(0); 129 | int k = vtdata.GetLength(1); 130 | int start = (header) ? 1 : 0; 131 | 132 | if (header) 133 | { 134 | for (int j = 0; j < k; j++) 135 | { 136 | var col = vtdata[0, j].ToString(); 137 | dt.Columns.Add(new DataColumn(col, vtdata[1, j].GetType())); 138 | } 139 | } 140 | else 141 | { 142 | for (int j = 0; j < k; j++) 143 | { 144 | var col = "col" + (j + 1).ToString(); 145 | dt.Columns.Add(new DataColumn(col, vtdata[0, j].GetType())); 146 | } 147 | } 148 | var cols = dt.Columns; 149 | 150 | for (int i = start; i < n; i++) 151 | { 152 | var row = dt.NewRow(); 153 | for (int j = 0; j < k; j++) 154 | { 155 | row[cols[j].ColumnName] = vtdata[i, j]; 156 | } 157 | dt.Rows.Add(row); 158 | } 159 | return dt; 160 | } 161 | } 162 | 163 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/Enums.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | namespace ExcelDna.Utilities; 27 | 28 | public enum xlCalculation 29 | { 30 | Automatic = 1, 31 | SemiAutomatic = 2, 32 | Manual = 3, 33 | } 34 | 35 | public enum xlPasteType 36 | { 37 | PasteAll = 1, 38 | PasteFormulas = 2, 39 | PasteValues = 3, 40 | PasteFormats = 4, 41 | PasteNotes = 5 42 | } 43 | 44 | public enum xlPasteAction 45 | { 46 | None = 1, 47 | Add = 2, 48 | Substract = 3, 49 | Multiply = 4, 50 | Divide = 5, 51 | } 52 | 53 | public enum xlUpdateLinks 54 | { 55 | Never = 0, 56 | ExternalOnly = 1, 57 | RemoteOnly = 2, 58 | ExternalAndRemote = 3, 59 | } 60 | 61 | public enum xlBorderStyle 62 | { 63 | NoBorder = 0, 64 | ThinLine = 1, 65 | MediumLine = 2, 66 | DashedLine = 3, 67 | DottedLine = 4, 68 | ThickLine = 5, 69 | Doubleline = 6, 70 | HairLine = 7, 71 | } 72 | 73 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/ExcelDna.Utilities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | latest 6 | ExcelDna.Utilities 7 | ExcelDna.Utilities 8 | Utilities that add functionality to ExcelDna such as creating a COM like interface to the C API 9 | LICENSE 10 | https://github.com/smartquant/ExcelDna.Utilities 11 | false 12 | Joachim Loebb 13 | Joachim Loebb 14 | Copyright © 2014-2024 Joachim Loebb 15 | $(Version) 16 | $(Verson) 17 | README.md 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/ExcelReferenceEx.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | using ExcelDna.Integration; 27 | 28 | 29 | namespace ExcelDna.Utilities; 30 | 31 | public static partial class ExcelReferenceEx 32 | { 33 | public static T GetValue(this ExcelReference range) 34 | { 35 | return XLConversion.ConvertTo(range.GetValue()); 36 | } 37 | 38 | public static T[,] ToMatrix(this ExcelReference range) 39 | { 40 | return range.GetValue().ToMatrix(); 41 | } 42 | 43 | public static T[] ToVector(this ExcelReference range) 44 | { 45 | return range.GetValue().ToVector(); 46 | } 47 | 48 | public static void DeleteEntireRows(this ExcelReference range) 49 | { 50 | //Shift_num Result 51 | 52 | //1 Shifts cells left 53 | //2 Shifts cells up 54 | //3 Deletes entire row 55 | //4 Deletes entire column 56 | 57 | static void action() 58 | { 59 | XlCall.Excel(XlCall.xlcEditDelete, 3); 60 | } 61 | 62 | XLApp.ActionOnSelectedRange(range, action); 63 | } 64 | 65 | public static void ClearContents(this ExcelReference range) 66 | { 67 | //int n = range.RowLast - range.RowFirst + 1, k = range.ColumnLast - range.ColumnFirst + 1; 68 | //var _empty = new object[n, k]; 69 | //range.SetValue(_empty); 70 | 71 | static void action() => XlCall.Excel(XlCall.xlcClear, 3); 72 | 73 | XLApp.ActionOnSelectedRange(range, action); 74 | } 75 | 76 | /// 77 | /// Changes the color formatting of a range 78 | /// 79 | /// 80 | /// 0: default color,number from 1 to 56 corresponding to the 56 area background colors in the Patterns tab of the Format Cells dialog box 81 | /// 0: default color,number from 1 to 56 corresponding to the 56 area foreground colors in the Patterns tab of the Foramt Cells dialog box 82 | /// 0: auto pattern, pattern can be from 1 to 18 83 | public static void FormatColor(this ExcelReference range, int backcolor = 0, int forecolor = 0, int pattern = 0) 84 | { 85 | //Should be called within screenupdating = false 86 | 87 | //change the pattern 88 | //PATTERNS(apattern, afore, aback, newui) 89 | void action() => XlCall.Excel(XlCall.xlcPatterns, pattern, forecolor, backcolor, true); 90 | 91 | XLApp.ActionOnSelectedRange(range, action); 92 | } 93 | 94 | public static void FormatBorder(this ExcelReference range, xlBorderStyle outline = xlBorderStyle.NoBorder, 95 | xlBorderStyle left = xlBorderStyle.NoBorder, xlBorderStyle right = xlBorderStyle.NoBorder, 96 | xlBorderStyle top = xlBorderStyle.NoBorder, xlBorderStyle bottom = xlBorderStyle.NoBorder, 97 | int shade = 0, int outline_color = 0, int left_color = 0, int right_color = 0, int top_color = 0, int bottom_color = 0) 98 | { 99 | //BORDER(outline, left, right, top, bottom, shade, outline_color, left_color, right_color, top_color, bottom_color) 100 | 101 | void action() => XlCall.Excel(XlCall.xlcBorder, (int)left, (int)right, (int)top, 102 | (int)bottom, shade, outline_color, left_color, right_color, top_color, bottom_color); 103 | 104 | XLApp.ActionOnSelectedRange(range, action); 105 | } 106 | 107 | public static void FormatNumber(this ExcelReference range, string format) 108 | { 109 | 110 | void action() => XlCall.Excel(XlCall.xlcFormatNumber, format); 111 | 112 | XLApp.ActionOnSelectedRange(range, action); 113 | } 114 | 115 | public static string SheetRef(this ExcelReference range) 116 | { 117 | return (string)XlCall.Excel(XlCall.xlfGetCell, 62, range); 118 | } 119 | 120 | public static void Select(this ExcelReference range) 121 | { 122 | XlCall.Excel(XlCall.xlcFormulaGoto, range); 123 | XlCall.Excel(XlCall.xlcSelect, range, Type.Missing); 124 | } 125 | 126 | public static void Copy(this ExcelReference fromRange, ExcelReference toRange = null) 127 | { 128 | object to_range = (toRange == null) ? Type.Missing : toRange; 129 | XlCall.Excel(XlCall.xlcCopy, fromRange, to_range); 130 | } 131 | 132 | public static string RefersTo(this ExcelReference range) 133 | { 134 | object result = XlCall.Excel(XlCall.xlfGetCell, 6, range); 135 | return (string)result; 136 | } 137 | 138 | public static ExcelReference Resize(this ExcelReference range, int rows, int cols) 139 | { 140 | rows = (rows < 1) ? 1 : rows; 141 | cols = (cols < 1) ? 1 : cols; 142 | return new ExcelReference(range.RowFirst, range.RowFirst + rows - 1, range.ColumnFirst, range.ColumnFirst + cols - 1, range.SheetId); 143 | } 144 | 145 | public static ExcelReference Offset(this ExcelReference range, int rows, int cols) 146 | { 147 | return new ExcelReference(range.RowFirst + rows, range.RowLast + rows, range.ColumnFirst + cols, range.ColumnLast + cols, range.SheetId); 148 | } 149 | 150 | public static ExcelReference AddHeader(this ExcelReference range) 151 | { 152 | return new ExcelReference(range.RowFirst - 1, range.RowLast, range.ColumnFirst, range.ColumnLast, range.SheetId); 153 | } 154 | 155 | public static List ToList(this ExcelReference range, Func factory = null) where T : class 156 | { 157 | var items = new List(); 158 | XLObjectMapper.AddRange(items, range.GetValue(), factory); 159 | return items; 160 | } 161 | 162 | public static DataTable ToDataTable(this ExcelReference range, bool header = true) 163 | { 164 | return DataTableEx.CreateDataTable(range.GetValue(), header); 165 | } 166 | 167 | /// 168 | /// copy a variant matrix into an excel named range and adjust the size of the named range 169 | /// 1.) will copy the formatting of the first row when adding new rows 170 | /// 2.) will copy the formatting of the first row after the named range when removing rows 171 | /// 172 | /// should be object[,] or simple data type 173 | /// 174 | /// This is the local name of the output range (always ex header) 175 | /// if there is a header the named range will start one cell below 176 | /// will not fill the first cell; header will be inside the range if true 177 | public static void Fill(this ExcelReference outRange, object vt, string localName = null, bool header = false, bool ignoreFirstCell = false) 178 | { 179 | var _vt = vt as object[,]; 180 | if (_vt == null) _vt = new object[,] { { vt } }; 181 | 182 | int name_offset = (header && ignoreFirstCell) ? 1 : 0; 183 | int origin_offset = ((header && !ignoreFirstCell) ? -1 : 0); 184 | int header_offset = (header) ? -1 : 0; 185 | int n = _vt.GetLength(0), k = _vt.GetLength(1); 186 | int m = outRange.RowLast - outRange.RowFirst + 1; 187 | 188 | //formatting 189 | bool localRange = !string.IsNullOrEmpty(localName); 190 | bool format = true; 191 | 192 | ExcelReference formatRange = null, newRange = null; 193 | 194 | if (m == 1 && localRange) 195 | { 196 | formatRange = Name.GetRange(string.Format("'{0}'!{1}", outRange.SheetRef(), localName)); 197 | if (formatRange == null) 198 | format = false; 199 | else 200 | m = formatRange.RowLast - formatRange.RowFirst + 1; 201 | } 202 | else if (m == 1) 203 | format = false; 204 | 205 | 206 | bool addRows = n + header_offset > m && format; 207 | bool removeRows = n + header_offset < m && format; 208 | 209 | 210 | int x0 = outRange.RowFirst + origin_offset, y0 = outRange.ColumnFirst; //output origin 211 | int x1 = outRange.RowFirst + name_offset, y1 = outRange.ColumnFirst; //name origin 212 | 213 | bool updating = XLApp.ScreenUpdating; 214 | xlCalculation calcMode = XLApp.Calcuation; 215 | 216 | if (updating) XLApp.ScreenUpdating = false; 217 | 218 | try 219 | { 220 | var fillRange = new ExcelReference(x0, x0 + n - 1, y0, y0 + k - 1, outRange.SheetId); 221 | 222 | if (addRows) 223 | { 224 | formatRange = new ExcelReference(x1, x1, y1, y1 + k - 1, outRange.SheetId); //first row 225 | newRange = new ExcelReference(x1, x1 + n + header_offset - 1, y1, y1 + k - 1, outRange.SheetId); 226 | } 227 | if (removeRows) 228 | { 229 | formatRange = new ExcelReference(x1 + m, x1 + m, y1, y1 + k - 1, outRange.SheetId); //last row + 1 230 | newRange = new ExcelReference(x1 + n + header_offset, x1 + m - 1, y1, y1 + k - 1, outRange.SheetId); 231 | newRange.ClearContents(); 232 | } 233 | 234 | //set the range except the first cell 235 | if (ignoreFirstCell && n > 1) 236 | { 237 | //first row 238 | if (k > 1) 239 | { 240 | object[,] first = new object[1, k - 1]; 241 | for (int i = 0; i < k - 1; i++) 242 | first[0, i] = _vt[0, i + 1]; 243 | fillRange.Offset(0, 1).Resize(1, k - 1).SetValue(first); 244 | } 245 | //all other rows 246 | object[,] rest = new object[n - 1, k]; 247 | for (int i = 1; i < n; i++) 248 | for (int j = 0; j < k; j++) 249 | rest[i - 1, j] = _vt[i, j]; 250 | fillRange.Offset(1, 0).Resize(n - 1, k).SetValue(rest); 251 | } 252 | else if (!ignoreFirstCell) 253 | fillRange.SetValue(_vt); 254 | 255 | 256 | //set name 257 | if (localRange) 258 | { 259 | Action action = () => 260 | { 261 | string sheetref = (string)XlCall.Excel(XlCall.xlSheetNm, outRange); 262 | Worksheet sheet = new(sheetref); 263 | 264 | //re-color 265 | if (addRows || removeRows) 266 | { 267 | formatRange.Select(); 268 | XLApp.Copy(); 269 | newRange.Select(); 270 | XLApp.PasteSpecial(xlPasteType.PasteFormats); 271 | } 272 | 273 | string reference = string.Format("='{4}'!R{0}C{2}:R{1}C{3}", x1 + 1, x1 + n + header_offset, y1 + 1, y1 + k, sheetref); 274 | 275 | //DEFINE.NAME(name_text, refers_to, macro_type, shortcut_text, hidden, category, local) 276 | XlCall.Excel(XlCall.xlcDefineName, localName, reference, Type.Missing, Type.Missing, false, Type.Missing, true); 277 | }; 278 | XLApp.ActionOnSelectedRange(fillRange, action); 279 | } 280 | } 281 | finally 282 | { 283 | if (updating) XLApp.ScreenUpdating = true; 284 | } 285 | 286 | } 287 | 288 | //TODO: these functions should be able to paste chunks of data say 5000 lines per chunk rather than converting everything in one array 289 | 290 | public static void Fill(this ExcelReference outRange, DataTable dt, string localName = null, bool header = false, bool ignoreFirstCell = false) 291 | { 292 | var vt = dt.ToVariant(header); 293 | Fill(outRange, vt, localName, header, ignoreFirstCell); 294 | } 295 | 296 | public static void Fill(this ExcelReference outRange, IEnumerable items, string localName = null, bool header = false, bool ignoreFirstCell = false) where T : class 297 | { 298 | var vt = items.ToVariant(header); 299 | Fill(outRange, vt, localName, header, ignoreFirstCell); 300 | } 301 | } 302 | 303 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/Name.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | using ExcelDna.Integration; 26 | 27 | namespace ExcelDna.Utilities; 28 | 29 | public class Name 30 | { 31 | #region fields 32 | 33 | private Worksheet _worksheet; 34 | private Workbook _workbook; 35 | private string _name; 36 | 37 | #endregion 38 | 39 | #region constructors 40 | 41 | /// 42 | /// Name with worksheet scope 43 | /// 44 | /// 45 | /// 46 | internal Name(Worksheet worksheet, string name) 47 | { 48 | _worksheet = worksheet; 49 | _name = name; 50 | } 51 | 52 | /// 53 | /// Name with workbook scope 54 | /// 55 | /// 56 | /// 57 | internal Name(Workbook workbook, string name) 58 | { 59 | _workbook = workbook; 60 | _name = name; 61 | } 62 | 63 | #endregion 64 | 65 | #region properties 66 | 67 | public string NameLocal 68 | { 69 | get { return _name; } 70 | } 71 | 72 | public string NameRef 73 | { 74 | get 75 | { 76 | return (_workbook != null) ? _workbook.Name + "!" + _name : 77 | _worksheet.SheetRef.Contains(" ") ? 78 | string.Format("'{0}'!{1}", _worksheet.SheetRef, _name) : 79 | string.Format("{0}!{1}", _worksheet.SheetRef, _name); 80 | } 81 | } 82 | 83 | public bool IsLocalScope 84 | { 85 | get { return _worksheet != null; } 86 | } 87 | 88 | public bool IsGlobalScope 89 | { 90 | get { return _workbook != null; } 91 | } 92 | 93 | public string RefersTo 94 | { 95 | get 96 | { 97 | return (string)XlCall.Excel(XlCall.xlfGetName, this.NameRef, Type.Missing); 98 | } 99 | } 100 | 101 | #endregion 102 | 103 | #region static methods 104 | 105 | public static ExcelReference GetRange(string nameRef) 106 | { 107 | object result = XlCall.Excel(XlCall.xlfEvaluate, "=" + nameRef); 108 | 109 | return result as ExcelReference; 110 | } 111 | 112 | public static T GetValue(string nameRef) 113 | { 114 | object result = XlCall.Excel(XlCall.xlfEvaluate, "=" + nameRef); 115 | 116 | ExcelReference r = result as ExcelReference; 117 | if (r != null) 118 | return r.GetValue().ConvertTo(); 119 | else 120 | return result.ConvertTo(); 121 | } 122 | 123 | public static T GetValue(Workbook wb, string name) 124 | { 125 | return GetValue(new Name(wb, name).RefersTo); 126 | } 127 | 128 | public static T GetValue(Worksheet ws, string name) 129 | { 130 | return GetValue(new Name(ws, name).RefersTo); 131 | } 132 | 133 | public static string NameRefersTo(string nameRef) 134 | { 135 | return (string)XlCall.Excel(XlCall.xlfGetName, nameRef, Type.Missing); 136 | } 137 | 138 | #endregion 139 | 140 | #region functions 141 | 142 | public override string ToString() 143 | { 144 | return _name; 145 | } 146 | 147 | #endregion 148 | } 149 | 150 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/WorkBook.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | using ExcelDna.Integration; 26 | 27 | namespace ExcelDna.Utilities; 28 | 29 | public class Workbook 30 | { 31 | #region fields 32 | 33 | private string _path; 34 | private string _workbook; 35 | 36 | #endregion 37 | 38 | #region constructor 39 | 40 | public Workbook(string filename) 41 | { 42 | Init(filename); 43 | } 44 | 45 | private void Init(string filename) 46 | { 47 | FileInfo finfo = new(filename); 48 | _path = finfo.DirectoryName + System.IO.Path.DirectorySeparatorChar; 49 | _workbook = finfo.Name; 50 | } 51 | 52 | #endregion 53 | 54 | #region static methods 55 | 56 | public static Workbook CreateNew() 57 | { 58 | XlCall.Excel(XlCall.xlcNew, 5); 59 | var res = XlCall.Excel(XlCall.xlfGetDocument, 88); 60 | return new Workbook((string)res); 61 | } 62 | 63 | public static Workbook ActiveWorkbook() 64 | { 65 | var res = XlCall.Excel(XlCall.xlfGetDocument, 88); 66 | return new Workbook((string)res); 67 | } 68 | 69 | public static Workbook Open(string path, xlUpdateLinks update_links = xlUpdateLinks.Never, 70 | bool read_only = false, string password = null) 71 | { 72 | XlCall.Excel(XlCall.xlcOpen, path, (int)update_links, read_only, 73 | Type.Missing, password, Type.Missing, true, 2, 74 | Type.Missing, true, Type.Missing, Type.Missing, Type.Missing, Type.Missing); 75 | Workbook wb = new(path); 76 | return wb; 77 | } 78 | 79 | #endregion 80 | 81 | #region properties 82 | 83 | public string Path 84 | { 85 | get { return _path; } 86 | } 87 | 88 | public string Name 89 | { 90 | get { return _workbook; } 91 | } 92 | 93 | public bool IsOpen 94 | { 95 | get 96 | { 97 | var res = XlCall.Excel(XlCall.xlfGetDocument, 3, _workbook); 98 | Type t = res.GetType(); 99 | return (t != typeof(ExcelError)); 100 | } 101 | } 102 | 103 | public bool IsReadonly 104 | { 105 | get 106 | { 107 | var res = XlCall.Excel(XlCall.xlfGetDocument, 5, _workbook); 108 | Type t = res.GetType(); 109 | if (t == typeof(ExcelError)) return true; 110 | return (bool)res; 111 | } 112 | } 113 | 114 | #endregion 115 | 116 | #region close save 117 | 118 | public void Activate() 119 | { 120 | XlCall.Excel(XlCall.xlcActivate, this.Name, Type.Missing); 121 | } 122 | 123 | public void Close(bool saveChanges = true, bool routeFile = false) 124 | { 125 | Activate(); 126 | XlCall.Excel(XlCall.xlcClose, saveChanges, routeFile); 127 | } 128 | 129 | public string GetPath() 130 | { 131 | _path = (string)XlCall.Excel(XlCall.xlfGetDocument, _workbook, 2); 132 | return _path; 133 | } 134 | 135 | public void SaveAs(string path, string password = null, string write_password = null, bool read_only = false) 136 | { 137 | object pwd = string.IsNullOrEmpty(password) ? Type.Missing : password; 138 | object write_pwd = string.IsNullOrEmpty(write_password) ? Type.Missing : write_password; 139 | 140 | XlCall.Excel(XlCall.xlcSaveAs, path, Type.Missing, pwd, Type.Missing, write_pwd, read_only); 141 | Init(path); 142 | } 143 | 144 | public void Save() 145 | { 146 | Activate(); 147 | XlCall.Excel(XlCall.xlcSave); 148 | } 149 | 150 | #endregion 151 | 152 | #region functions 153 | 154 | public Worksheet AddWorksheet() 155 | { 156 | Activate(); 157 | XlCall.Excel(XlCall.xlcWorkbookInsert, 1); 158 | return Worksheet.ActiveSheet(); 159 | } 160 | 161 | public Worksheet[] Worksheets 162 | { 163 | get 164 | { 165 | object[,] sheetnames = 166 | (object[,])XlCall.Excel(XlCall.xlfGetWorkbook, 1, this.Name); 167 | int n = sheetnames.GetLength(1); 168 | Worksheet[] sheets = new Worksheet[n]; 169 | 170 | for (int j = 0; j < n; j++) 171 | { 172 | sheets[j] = new Worksheet(sheetnames[0, j].ToString()); 173 | } 174 | return sheets; 175 | } 176 | } 177 | 178 | public string[] SheetRefs 179 | { 180 | get 181 | { 182 | object[,] sheetnames = 183 | (object[,])XlCall.Excel(XlCall.xlfGetWorkbook, 1, this.Name); 184 | int n = sheetnames.GetLength(1); 185 | string[] sheets = new string[n]; 186 | 187 | for (int j = 0; j < n; j++) 188 | { 189 | sheets[j] = sheetnames[0, j].ToString(); 190 | } 191 | return sheets; 192 | } 193 | } 194 | 195 | public string[] SheetNames 196 | { 197 | get 198 | { 199 | var sheetrefs = this.SheetRefs; 200 | int n = sheetrefs.Length; 201 | string[] result = new string[n]; 202 | 203 | for (int i = 0; i < n; i++) 204 | result[i] = Worksheet.ExtractSheetName(sheetrefs[i]); 205 | 206 | return result; 207 | } 208 | } 209 | 210 | /// 211 | /// References a sheet from this workbook, however DOES NOT TEST wheter sheet actually exists for 212 | /// performance reasons 213 | /// 214 | /// 215 | /// 216 | public Worksheet this[string sheetName] 217 | { 218 | get 219 | { 220 | return new Worksheet(this, sheetName); 221 | } 222 | } 223 | 224 | #endregion 225 | } 226 | 227 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/WorkSheet.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | using ExcelDna.Integration; 27 | 28 | namespace ExcelDna.Utilities; 29 | 30 | public class Worksheet 31 | { 32 | #region fields 33 | 34 | private Workbook _workbook; 35 | private string _sheetname; 36 | 37 | #endregion 38 | 39 | #region constructors 40 | 41 | public Worksheet(Workbook wb, string sheet) 42 | { 43 | _workbook = wb; 44 | _sheetname = sheet; 45 | } 46 | 47 | public Worksheet(string sheetRef) 48 | { 49 | int pos = sheetRef.IndexOf(']'); 50 | _workbook = new Workbook(sheetRef.Substring(1, pos - 1)); 51 | _sheetname = sheetRef.Substring(pos + 1, sheetRef.Length - pos - 1); 52 | } 53 | 54 | #endregion 55 | 56 | #region properties 57 | 58 | public string Name 59 | { 60 | get { return _sheetname; } 61 | set 62 | { 63 | XlCall.Excel(XlCall.xlcWorkbookName, SheetRef, string.Format("[{0}]{1}", _workbook.Name, value)); 64 | _sheetname = value; 65 | } 66 | } 67 | 68 | public Workbook Workbook 69 | { 70 | get { return _workbook; } 71 | } 72 | 73 | /// 74 | /// Full sheet reference as [workbook]SheetName 75 | /// 76 | public string SheetRef 77 | { 78 | get { return string.Format("[{0}]{1}", _workbook.Name, _sheetname); } 79 | } 80 | 81 | /// 82 | /// Returns all local names 83 | /// 84 | public Name[] NamesLocal 85 | { 86 | get 87 | { 88 | var sheetRef = this.SheetRef; 89 | 90 | //get all names (local and global) for this sheet 91 | object o = XlCall.Excel(XlCall.xlfNames, sheetRef); 92 | 93 | var list = new List(); 94 | 95 | if (o is object[,] names) 96 | { 97 | int n = names.GetLength(1); 98 | for (int i = 0; i < n; i++) 99 | { 100 | string name = (string)names.GetValue(0, i); 101 | string nameRef = string.Format("'{0}'!{1}", this.SheetRef, name); 102 | 103 | //find out whether name is local or not 104 | if ((bool)XlCall.Excel(XlCall.xlfGetName, nameRef, 2)) 105 | list.Add(new Name(this, name)); 106 | 107 | } 108 | } 109 | 110 | return [.. list]; 111 | } 112 | } 113 | /// 114 | /// Returns all names for this worksheet (global and local) 115 | /// 116 | public Name[] Names 117 | { 118 | get 119 | { 120 | var sheetRef = this.SheetRef; 121 | 122 | //get all names (local and global) for this sheet 123 | object o = XlCall.Excel(XlCall.xlfNames, sheetRef); 124 | 125 | if (o is object[,] names) 126 | { 127 | int n = names.GetLength(1); 128 | Name[] res = new Name[n]; 129 | for (int i = 0; i < n; i++) 130 | { 131 | string name = (string)names.GetValue(0, i); 132 | string nameRef = string.Format("'{0}'!{1}", this.SheetRef, name); 133 | 134 | //find out whether name is local or not 135 | if ((bool)XlCall.Excel(XlCall.xlfGetName, nameRef, 2)) 136 | res[i] = new Name(this, name); 137 | else 138 | res[i] = new Name(this.Workbook, name); 139 | } 140 | return res; 141 | } 142 | 143 | return []; 144 | } 145 | } 146 | #endregion 147 | 148 | #region static methods 149 | 150 | public static Worksheet ActiveSheet() 151 | { 152 | var res = XlCall.Excel(XlCall.xlfGetDocument, 76); 153 | return new Worksheet((string)res); 154 | } 155 | 156 | public static string ExtractSheetName(string sheetRef) 157 | { 158 | int pos = sheetRef.IndexOf(']'); 159 | return sheetRef.Substring(pos + 1, sheetRef.Length - pos - 1); 160 | } 161 | 162 | public static string ExtractWorkbookName(string sheetRef) 163 | { 164 | int pos = sheetRef.IndexOf(']'); 165 | return sheetRef.Substring(1, pos - 1); 166 | } 167 | 168 | #endregion 169 | 170 | #region functions 171 | 172 | public void Select() 173 | { 174 | this.Workbook.Activate(); 175 | XlCall.Excel(XlCall.xlcWorkbookSelect, new object[,] { { this.SheetRef } }, Type.Missing, Type.Missing); 176 | } 177 | 178 | public void SelectAllCells() 179 | { 180 | XlCall.Excel(XlCall.xlcSelect, SheetRef + "!1:1048576", Type.Missing); 181 | } 182 | 183 | public ExcelReference Range(string range) 184 | { 185 | object result = XlCall.Excel(XlCall.xlfEvaluate, string.Format("='{0}'!{1}", this.SheetRef, range)); 186 | return result as ExcelReference; 187 | } 188 | 189 | /// 190 | /// Defines the name on the ACTIVE sheet 191 | /// 192 | /// 193 | /// 194 | /// 195 | /// 196 | public Name AddName(string name, string refersto, bool hidden = false, bool local = true) 197 | { 198 | //Check whether this is the active sheet 199 | var ws = Worksheet.ActiveSheet(); 200 | if (ws.Name != this.Name) return null; 201 | 202 | //DEFINE.NAME(name_text, refers_to, macro_type, shortcut_text, hidden, category, local) 203 | bool result = (bool)XlCall.Excel(XlCall.xlcDefineName, name, refersto, Type.Missing, Type.Missing, hidden, Type.Missing, local); 204 | if (result) return new Name(this, name); 205 | return null; 206 | } 207 | 208 | public void DeleteAllCells() 209 | { 210 | ExcelDna.Utilities.Name.GetRange(this.SheetRef + "!1:1048576").DeleteEntireRows(); 211 | } 212 | 213 | #endregion 214 | } 215 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/XLApp.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | using ExcelDna.Integration; 27 | 28 | namespace ExcelDna.Utilities; 29 | 30 | public static partial class XLApp 31 | { 32 | private static bool _screenupdating = true; 33 | private static Lazy _defaultDateFormat = new(() => GetDefaultDateFormat()); 34 | 35 | #region properties 36 | 37 | public static Workbook[] Workbooks 38 | { 39 | get 40 | { 41 | var list = new List(); 42 | 43 | object o = XlCall.Excel(XlCall.xlfDocuments); 44 | object[,] docs = o as object[,]; 45 | 46 | if (docs != null) 47 | for (int i = 0; i < docs.GetLength(1); i++) 48 | list.Add(new Workbook((string)docs.GetValue(0, i))); 49 | 50 | return list.ToArray(); 51 | } 52 | } 53 | 54 | public static string DefaultDateFormat => _defaultDateFormat.Value; 55 | 56 | static string GetDefaultDateFormat() 57 | { 58 | var result = XlCall.Excel(XlCall.xlfGetWorkspace, 37) as object[,]; 59 | 60 | int i = 16; 61 | string date_seperator = (string)result[0, i++]; 62 | string time_seperator = (string)result[0, i++]; 63 | string year_symbol = (string)result[0, i++]; 64 | string month_symbol = (string)result[0, i++]; 65 | string day_symbol = (string)result[0, i++]; 66 | string hour_symbol = (string)result[0, i++]; 67 | string minute_symbol = (string)result[0, i++]; 68 | string second_symbol = (string)result[0, i++]; 69 | //32 Number indicating the date order 70 | //0 = Month-Day-Year 71 | //1 = Day-Month-Year 72 | //2 = Year-Month-Day 73 | double date_order = (double)result[0, 31]; 74 | 75 | day_symbol += day_symbol; 76 | month_symbol += month_symbol; 77 | year_symbol = string.Concat(year_symbol, year_symbol, year_symbol, year_symbol); 78 | 79 | if (date_order == 0) 80 | return month_symbol + date_seperator + day_symbol + date_seperator + year_symbol; 81 | else if (date_order == 1) 82 | return day_symbol + date_seperator + month_symbol + date_seperator + year_symbol; 83 | else 84 | return year_symbol + date_seperator + month_symbol + date_seperator + day_symbol; 85 | } 86 | 87 | #endregion 88 | 89 | #region Message bar 90 | /// 91 | /// Similar to excel pass an empty string to reset message bar 92 | /// 93 | /// 94 | public static void MessageBar(string message) 95 | { 96 | bool display = !string.IsNullOrEmpty(message); 97 | XlCall.Excel(XlCall.xlcMessage, display, message); 98 | } 99 | 100 | public static void MessageBar(string message, params object[] obj) 101 | { 102 | MessageBar(string.Format(message, obj)); 103 | } 104 | 105 | #endregion 106 | 107 | #region calculation 108 | 109 | public static bool ScreenUpdating 110 | { 111 | set 112 | { 113 | XlCall.Excel(XlCall.xlcEcho, value); 114 | _screenupdating = value; 115 | } 116 | get 117 | { 118 | //return (bool)XlCall.Excel(XlCall.xlfGetWorkspace, 40);; 119 | return _screenupdating; 120 | } 121 | } 122 | 123 | public static xlCalculation Calcuation 124 | { 125 | get 126 | { 127 | var result = XlCall.Excel(XlCall.xlfGetDocument, 14); 128 | return (xlCalculation)(int)(double)result; 129 | } 130 | set 131 | { 132 | //get all calculation settings for the function call OPTIONS.CALCULATION 133 | object[,] result = XlCall.Excel(XlCall.xlfGetDocument, new object[,] { { 14, 15, 16, 17, 18, 19, 20, 33, 43 } }) as object[,]; 134 | object[] pars = result.ToVector(); 135 | pars[0] = (int)value; 136 | var retval = XlCall.Excel(XlCall.xlcOptionsCalculation, pars); 137 | } 138 | } 139 | 140 | public static void CalculateNow() 141 | { 142 | XlCall.Excel(XlCall.xlcCalculateNow); 143 | } 144 | 145 | public static void CalculateDocument() 146 | { 147 | XlCall.Excel(XlCall.xlcCalculateDocument); 148 | } 149 | 150 | #endregion 151 | 152 | #region wrapping actions 153 | 154 | /// 155 | /// Wrapper for macro functions that need a selection; remembers old selection 156 | /// 157 | /// 158 | /// 159 | public static void ActionOnSelectedRange(this ExcelReference range, Action action) 160 | { 161 | bool updating = ScreenUpdating; 162 | 163 | try 164 | { 165 | if (updating) ScreenUpdating = false; 166 | 167 | //remember the current active cell 168 | object oldSelectionOnActiveSheet = XlCall.Excel(XlCall.xlfSelection); 169 | 170 | //select caller range AND workbook 171 | string rangeSheet = (string)XlCall.Excel(XlCall.xlSheetNm, range); 172 | 173 | XlCall.Excel(XlCall.xlcWorkbookSelect, new object[] { rangeSheet }); 174 | XlCall.Excel(XlCall.xlcSelect, range); 175 | 176 | action.Invoke(); 177 | 178 | //go back to old selection 179 | XlCall.Excel(XlCall.xlcFormulaGoto, oldSelectionOnActiveSheet); 180 | } 181 | finally 182 | { 183 | if (updating) XLApp.ScreenUpdating = true; 184 | } 185 | } 186 | 187 | public static void ReturnToSelection(Action action) 188 | { 189 | bool updating = ScreenUpdating; 190 | 191 | try 192 | { 193 | if (updating) ScreenUpdating = false; 194 | 195 | //remember the current active cell 196 | object oldSelectionOnActiveSheet = XlCall.Excel(XlCall.xlfSelection); 197 | 198 | action.Invoke(); 199 | 200 | //go back to old selection 201 | XlCall.Excel(XlCall.xlcFormulaGoto, oldSelectionOnActiveSheet); 202 | } 203 | finally 204 | { 205 | if (updating) XLApp.ScreenUpdating = true; 206 | } 207 | } 208 | 209 | public static void NoCalcAndUpdating(this Action action) 210 | { 211 | xlCalculation calcMode = Calcuation; 212 | bool updating = ScreenUpdating; 213 | 214 | try 215 | { 216 | if (updating) ScreenUpdating = false; 217 | if (calcMode != xlCalculation.Manual) Calcuation = xlCalculation.Manual; 218 | 219 | action.Invoke(); 220 | } 221 | finally 222 | { 223 | if (updating) XLApp.ScreenUpdating = true; 224 | if (calcMode != xlCalculation.Manual) XLApp.Calcuation = calcMode; 225 | } 226 | } 227 | 228 | #endregion 229 | 230 | #region copy paste 231 | 232 | public static void PasteSpecial(xlPasteType type = xlPasteType.PasteAll, 233 | xlPasteAction action = xlPasteAction.None, bool skip_blanks = false, bool transpose = false) 234 | { 235 | XlCall.Excel(XlCall.xlcPasteSpecial, (int)type, (int)action, skip_blanks, transpose); 236 | } 237 | 238 | public static void Copy() 239 | { 240 | XlCall.Excel(XlCall.xlcCopy, Type.Missing, Type.Missing); 241 | } 242 | 243 | #endregion 244 | 245 | public static void SelectRange(string rangeRef) 246 | { 247 | XlCall.Excel(XlCall.xlcSelect, rangeRef, Type.Missing); 248 | } 249 | 250 | } 251 | 252 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/XLConversion.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | using System.Collections.Concurrent; 26 | 27 | using ExcelDna.Integration; 28 | 29 | namespace ExcelDna.Utilities; 30 | 31 | public static class XLConversion 32 | { 33 | #region object conversion 34 | //a thread-safe way to hold default instances created at run-time 35 | private static ConcurrentDictionary typeDefaults = new(); 36 | 37 | private static object GetDefault(Type type) 38 | { 39 | return type.IsValueType ? typeDefaults.GetOrAdd(type, t => Activator.CreateInstance(t)) : null; 40 | } 41 | 42 | public static object ConvertTo(this object vt, Type toType) 43 | { 44 | if (vt == null) return GetDefault(toType); 45 | Type fromType = vt.GetType(); 46 | if (fromType == typeof(DBNull)) return GetDefault(toType); 47 | 48 | if (fromType == typeof(ExcelDna.Integration.ExcelEmpty) || fromType == typeof(ExcelDna.Integration.ExcelError) || fromType == typeof(ExcelDna.Integration.ExcelMissing)) 49 | return GetDefault(toType); 50 | 51 | if (fromType == typeof(ExcelReference)) 52 | { 53 | ExcelReference r = (ExcelReference)vt; 54 | object val = r.GetValue(); 55 | return ConvertTo(val, toType); 56 | } 57 | 58 | //acount for nullable types 59 | toType = Nullable.GetUnderlyingType(toType) ?? toType; 60 | 61 | if (toType == typeof(DateTime)) 62 | { 63 | DateTime dt = DateTime.FromOADate(0.0); 64 | if (fromType == typeof(DateTime)) 65 | dt = (DateTime)vt; 66 | else if (fromType == typeof(double)) 67 | dt = DateTime.FromOADate((double)vt); 68 | else if (fromType == typeof(string)) 69 | { 70 | DateTime result; 71 | if (DateTime.TryParse((string)vt, out result)) 72 | dt = result; 73 | } 74 | return Convert.ChangeType(dt, toType); 75 | } 76 | else if (toType == typeof(XLDate)) 77 | { 78 | XLDate dt = 0.0; 79 | if (fromType == typeof(DateTime)) 80 | dt = (DateTime)vt; 81 | else if (fromType == typeof(double)) 82 | dt = (double)vt; 83 | else if (fromType == typeof(string)) 84 | { 85 | DateTime result; 86 | if (DateTime.TryParse((string)vt, out result)) 87 | dt = result; 88 | else 89 | dt = 0.0; 90 | } 91 | else 92 | dt = (double)Convert.ChangeType(vt, typeof(double)); 93 | return Convert.ChangeType(dt, toType); 94 | } 95 | else if (toType == typeof(double)) 96 | { 97 | double dt = 0.0; 98 | if (fromType == typeof(double)) 99 | dt = (double)vt; 100 | else if (fromType == typeof(DateTime)) 101 | dt = ((DateTime)vt).ToOADate(); 102 | else if (fromType == typeof(string)) 103 | double.TryParse((string)vt, out dt); 104 | else 105 | dt = (double)Convert.ChangeType(vt, typeof(double)); 106 | return Convert.ChangeType(dt, toType); 107 | } 108 | else if (toType.IsEnum) 109 | { 110 | try 111 | { 112 | return Enum.Parse(toType, vt.ToString(), true); 113 | } 114 | catch (Exception) 115 | { 116 | return GetDefault(toType); 117 | } 118 | 119 | } 120 | else 121 | return Convert.ChangeType(vt, toType); 122 | 123 | 124 | } 125 | 126 | public static T ConvertTo(this object vt) 127 | { 128 | if (vt == null) return default(T); 129 | 130 | Type toType = typeof(T); 131 | Type fromType = vt.GetType(); 132 | if (fromType == typeof(DBNull)) return default(T); 133 | 134 | if (fromType == typeof(ExcelDna.Integration.ExcelEmpty) || fromType == typeof(ExcelDna.Integration.ExcelError) || fromType == typeof(ExcelDna.Integration.ExcelMissing)) 135 | return default(T); 136 | 137 | if (fromType == typeof(ExcelReference)) 138 | { 139 | ExcelReference r = (ExcelReference)vt; 140 | object val = r.GetValue(); 141 | return ConvertTo(val); 142 | } 143 | 144 | //acount for nullable types 145 | toType = Nullable.GetUnderlyingType(toType) ?? toType; 146 | 147 | if (toType == typeof(DateTime)) 148 | { 149 | DateTime dt = DateTime.FromOADate(0.0); 150 | if (fromType == typeof(DateTime)) 151 | dt = (DateTime)vt; 152 | else if (fromType == typeof(double)) 153 | dt = DateTime.FromOADate((double)vt); 154 | else if (fromType == typeof(string)) 155 | { 156 | DateTime result; 157 | if (DateTime.TryParse((string)vt, out result)) 158 | dt = result; 159 | } 160 | //note this will work also if T is nullable 161 | return (T)Convert.ChangeType(dt, toType); 162 | } 163 | else if (toType == typeof(XLDate)) 164 | { 165 | XLDate dt = 0.0; 166 | if (fromType == typeof(DateTime)) 167 | dt = (DateTime)vt; 168 | else if (fromType == typeof(double)) 169 | dt = (double)vt; 170 | else if (fromType == typeof(string)) 171 | { 172 | DateTime result; 173 | if (DateTime.TryParse((string)vt, out result)) 174 | dt = result; 175 | else 176 | dt = 0.0; 177 | } 178 | else 179 | dt = (double)Convert.ChangeType(vt, typeof(double)); 180 | return (T)Convert.ChangeType(dt, toType); 181 | } 182 | else if (toType == typeof(double)) 183 | { 184 | double dt = 0.0; 185 | if (fromType == typeof(double)) 186 | dt = (double)vt; 187 | else if (fromType == typeof(DateTime)) 188 | dt = ((DateTime)vt).ToOADate(); 189 | else if (fromType == typeof(string)) 190 | double.TryParse((string)vt, out dt); 191 | else 192 | dt = (double)Convert.ChangeType(vt, typeof(double)); 193 | return (T)Convert.ChangeType(dt, toType); 194 | } 195 | else if (toType.IsEnum) 196 | { 197 | try 198 | { 199 | return (T)Enum.Parse(typeof(T), vt.ToString(), true); 200 | } 201 | catch (Exception) 202 | { 203 | return default(T); 204 | } 205 | 206 | } 207 | else 208 | return (T)Convert.ChangeType(vt, toType); 209 | 210 | } 211 | 212 | public static void ConvertVT(this object vt, out T value) 213 | { 214 | value = vt.ConvertTo(); 215 | } 216 | 217 | public static T[] ToVector(this object vt) 218 | { 219 | if (vt is Array) return ToVector(vt as object[,]); 220 | 221 | T[] retval = new T[1]; 222 | vt.ConvertVT(out retval[0]); 223 | 224 | return retval; 225 | } 226 | 227 | public static T[] ToVector(this object[,] vt) 228 | { 229 | int n = vt.GetLength(0), k = vt.GetLength(1); 230 | int l = 0; 231 | 232 | T[] @out = new T[n * k]; 233 | 234 | for (int i = 0; i < n; i++) 235 | for (int j = 0; j < k; j++) 236 | { 237 | vt.GetValue(i, j).ConvertVT(out @out[l]); 238 | l++; 239 | } 240 | 241 | return @out; 242 | } 243 | 244 | public static T[,] ToMatrix(this object vt) 245 | { 246 | if (vt is Array) return ToMatrix(vt as object[,]); 247 | 248 | T[,] retval = new T[1, 1]; 249 | vt.ConvertVT(out retval[0, 0]); 250 | 251 | return retval; 252 | } 253 | 254 | public static T[,] ToMatrix(this object[,] vt) 255 | { 256 | int n = vt.GetLength(0), k = vt.GetLength(1); 257 | 258 | T[,] @out = new T[n, k]; 259 | 260 | for (int i = 0; i < n; i++) 261 | for (int j = 0; j < k; j++) 262 | { 263 | vt.GetValue(i, j).ConvertVT(out @out[i, j]); 264 | } 265 | 266 | return @out; 267 | } 268 | 269 | public static object ToVariant(this T[,] vt) 270 | { 271 | int n = vt.GetLength(0), k = vt.GetLength(1); 272 | object[,] @out = new object[n, k]; 273 | 274 | for (int i = 0; i < n; i++) 275 | for (int j = 0; j < k; j++) 276 | { 277 | @out[i, j] = vt.GetValue(i, j); 278 | } 279 | 280 | return @out; 281 | } 282 | 283 | #endregion 284 | } 285 | 286 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/XLDBWrapper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | using System.Data.Common; 27 | 28 | using ExcelDna.Integration; 29 | 30 | 31 | namespace ExcelDna.Utilities; 32 | 33 | public static class XLDBWrapper 34 | { 35 | 36 | 37 | public delegate object FieldFormatter(object obj); 38 | 39 | public static object DefaultFieldFormatter(object obj) 40 | { 41 | return (obj is Array) ? "Array data" : obj; 42 | } 43 | /// 44 | /// Transforms a DBDataReader object into an Excel Variant datatype 45 | /// Note: Returned number of rows should not be too large (say below 10000) 46 | /// 47 | /// 48 | /// Field names to be selected; null for taking alll default 49 | /// The columnnames in the variant output; null for taking values in properts 50 | /// Should column names be written to the variant output 51 | /// the number of rows (ex header) 52 | /// the number of columns of the variant 53 | /// 54 | public static object[,] ToVariant(this DbDataReader reader, string[] properties = null, 55 | string[] colheaders = null, FieldFormatter formatter = null, bool header = true) 56 | { 57 | string[] props; 58 | string[] colheader; 59 | 60 | if (properties == null) 61 | { 62 | List sc = new(); 63 | for (int i = 0; i < reader.FieldCount; i++) 64 | sc.Add(reader.GetName(i)); 65 | props = sc.ToArray(); 66 | 67 | } 68 | else 69 | { 70 | props = properties; 71 | } 72 | 73 | if (header && (colheaders == null || colheaders.Length != props.Length)) 74 | colheader = props; 75 | else 76 | colheader = colheaders; 77 | if (formatter == null) 78 | formatter = DefaultFieldFormatter; 79 | 80 | int iheader = (header) ? 1 : 0; 81 | int @base = 0; 82 | 83 | int[] idx = new int[props.Length]; 84 | 85 | for (int i = 0; i < props.Length; i++) 86 | idx[i] = reader.GetOrdinal(props[i]); 87 | 88 | List oList = new(); 89 | int nFields = props.Length; 90 | while (reader.Read()) 91 | { 92 | object[] vals = new object[nFields]; 93 | for (int i = 0; i < nFields; i++) 94 | vals[i] = reader.GetValue(idx[i]); 95 | oList.Add(vals); 96 | } 97 | 98 | int n = oList.Count + iheader, m = nFields; 99 | int[] lbound = { @base, @base }; 100 | int[] lengths = { n, m }; 101 | 102 | object[,] @out = (object[,])Array.CreateInstance(typeof(object), lengths, lbound); 103 | 104 | int j = @base; 105 | if (header) 106 | foreach (string col in colheader) 107 | @out[@base, j++] = col; 108 | 109 | 110 | int k = @base + iheader; 111 | for (int i = 0; i < oList.Count; i++) 112 | for (int l = 0; l < m; l++) 113 | @out[i + @base + iheader, l + @base] = formatter(oList[i][l]); 114 | //rows = n; cols = m; 115 | return @out; 116 | 117 | } 118 | 119 | 120 | 121 | /// 122 | /// Directly load the the rows from a dbreader into a list of xlobjects 123 | /// Important: The order of the returned columns must match the one of T column mapping 124 | /// 125 | /// 126 | /// 127 | /// 128 | public static List ToXLObjectList(this DbDataReader reader) where T : class 129 | { 130 | var list = new List(); 131 | Type t = typeof(T); 132 | var map = XLObjectMapper.GetObjectMapping(null); 133 | 134 | while (reader.Read()) 135 | { 136 | T instance = (t.GetConstructor(Type.EmptyTypes) != null) ? (T)Activator.CreateInstance(t, []) 137 | : Activator.CreateInstance(); 138 | int nFields = reader.FieldCount; 139 | 140 | for (int i = 0; i < nFields; i++) 141 | map.SetColumn(instance, i, reader.GetValue(i)); 142 | list.Add(instance); 143 | } 144 | return list; 145 | } 146 | } 147 | 148 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/XLDate.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | namespace ExcelDna.Utilities; 27 | 28 | /// 29 | /// Convenience struct to work directly with Excel double dates rather than using DateTime.FromOADate() conversions 30 | /// for certain calculations there exist a direct and fast operation without going through all the conversions 31 | /// Implicit operators make sure that class is equivalent to DateTime and double. 32 | /// 33 | public struct XLDate : IComparable 34 | { 35 | 36 | private double _xlDate; 37 | 38 | #region constructors 39 | 40 | public XLDate(double xlDate) 41 | { 42 | _xlDate = xlDate; 43 | } 44 | public XLDate(XLDate xlDate) 45 | { 46 | _xlDate = xlDate._xlDate; 47 | } 48 | public XLDate(DateTime dateTime) 49 | { 50 | _xlDate = dateTime.ToOADate(); 51 | } 52 | public XLDate(int year, int month, int day, int hour = 0, int minute = 0, int second = 0, int millisecond = 0) 53 | { 54 | _xlDate = new DateTime(year, month, day, hour, minute, second, millisecond).ToOADate(); 55 | } 56 | 57 | #endregion 58 | 59 | #region properties 60 | 61 | public XLDate Date 62 | { 63 | get { return Math.Floor(this._xlDate); } 64 | } 65 | 66 | public int Year 67 | { 68 | get { return DateTime.FromOADate(_xlDate).Year; } 69 | } 70 | public int Month 71 | { 72 | get { return DateTime.FromOADate(_xlDate).Month; } 73 | } 74 | public int Day 75 | { 76 | get { return DateTime.FromOADate(_xlDate).Day; } 77 | } 78 | public DayOfWeek DayOfWeek 79 | { 80 | get { return DateTime.FromOADate(_xlDate).DayOfWeek; } 81 | } 82 | public int DayOfYear 83 | { 84 | get { return DateTime.FromOADate(_xlDate).DayOfYear; } 85 | } 86 | public int Hour 87 | { 88 | get { return DateTime.FromOADate(_xlDate).Hour; } 89 | } 90 | public int Minute 91 | { 92 | get { return DateTime.FromOADate(_xlDate).Minute; } 93 | } 94 | public int Second 95 | { 96 | get { return DateTime.FromOADate(_xlDate).Second; } 97 | } 98 | public int Millisecond 99 | { 100 | get { return DateTime.FromOADate(_xlDate).Millisecond; } 101 | } 102 | 103 | #endregion 104 | 105 | #region Date math 106 | 107 | public XLDate AddMilliseconds(double value) 108 | { 109 | return new XLDate(_xlDate + value / 86400000.0); 110 | } 111 | 112 | public XLDate AddSeconds(double value) 113 | { 114 | return new XLDate(_xlDate + value / 86400.0); 115 | } 116 | 117 | public XLDate AddMinutes(double value) 118 | { 119 | return new XLDate(_xlDate + value / 1440.0); 120 | } 121 | 122 | public XLDate AddHours(double value) 123 | { 124 | return new XLDate(_xlDate + value / 24.0); 125 | } 126 | 127 | public XLDate AddDays(double value) 128 | { 129 | return new XLDate(_xlDate + value); 130 | } 131 | 132 | public XLDate AddMonths(int value) 133 | { 134 | return new XLDate(DateTime.FromOADate(_xlDate).AddMonths(value)); 135 | } 136 | 137 | public XLDate AddYears(int value) 138 | { 139 | return new XLDate(DateTime.FromOADate(_xlDate).AddYears(value)); 140 | } 141 | 142 | #endregion 143 | 144 | #region Operators 145 | 146 | public static double operator -(XLDate lhs, XLDate rhs) 147 | { 148 | return lhs._xlDate - rhs._xlDate; 149 | } 150 | 151 | public static XLDate operator -(XLDate lhs, double rhs) 152 | { 153 | lhs._xlDate -= rhs; 154 | return lhs; 155 | } 156 | 157 | public static XLDate operator +(XLDate lhs, double rhs) 158 | { 159 | lhs._xlDate += rhs; 160 | return lhs; 161 | } 162 | 163 | public static XLDate operator +(XLDate d, TimeSpan t) 164 | { 165 | XLDate date = new(d); 166 | d.AddMilliseconds(t.TotalMilliseconds); 167 | return date; 168 | } 169 | 170 | public static XLDate operator -(XLDate d, TimeSpan t) 171 | { 172 | XLDate date = new(d); 173 | d.AddMilliseconds(-t.TotalMilliseconds); 174 | return date; 175 | } 176 | 177 | public static XLDate operator ++(XLDate xDate) 178 | { 179 | xDate._xlDate += 1.0; 180 | return xDate; 181 | } 182 | 183 | public static XLDate operator --(XLDate xDate) 184 | { 185 | xDate._xlDate -= 1.0; 186 | return xDate; 187 | } 188 | 189 | public static implicit operator double(XLDate xDate) 190 | { 191 | return xDate._xlDate; 192 | } 193 | 194 | public static implicit operator float(XLDate xDate) 195 | { 196 | return (float)xDate._xlDate; 197 | } 198 | 199 | public static implicit operator XLDate(double xlDate) 200 | { 201 | return new XLDate(xlDate); 202 | } 203 | 204 | public static implicit operator DateTime(XLDate xDate) 205 | { 206 | return DateTime.FromOADate(xDate); 207 | } 208 | 209 | public static implicit operator XLDate(DateTime dt) 210 | { 211 | return new XLDate(dt); 212 | } 213 | #endregion 214 | 215 | #region formatting 216 | 217 | public override string ToString() 218 | { 219 | return DateTime.FromOADate(_xlDate).ToString(); 220 | } 221 | 222 | public string ToString(string format) 223 | { 224 | return DateTime.FromOADate(_xlDate).ToString(format); 225 | } 226 | 227 | public string ToString(string format, IFormatProvider formatprovider) 228 | { 229 | return DateTime.FromOADate(_xlDate).ToString(format, formatprovider); 230 | } 231 | 232 | #endregion 233 | 234 | #region System 235 | 236 | public override bool Equals(object obj) 237 | { 238 | if (obj is XLDate date) 239 | { 240 | return date._xlDate == _xlDate; 241 | } 242 | else if (obj is double v) 243 | { 244 | return v == _xlDate; 245 | } 246 | else 247 | return false; 248 | } 249 | 250 | public override int GetHashCode() 251 | { 252 | return _xlDate.GetHashCode(); 253 | } 254 | 255 | public int CompareTo(object target) 256 | { 257 | if (target is not XLDate) 258 | throw new ArgumentException(); 259 | 260 | return (this._xlDate).CompareTo(((XLDate)target)._xlDate); 261 | } 262 | 263 | #endregion 264 | 265 | } 266 | 267 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/XLObjectMapper.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | using System.Collections.Concurrent; 26 | 27 | 28 | namespace ExcelDna.Utilities; 29 | 30 | /// 31 | /// Interface that allows to enumerate and name the fields of an object 32 | /// typically used for interacting with excel ranges and Lists of strongly typed objects 33 | /// that cannot be enumerated easily or automatically through reflection 34 | /// XLObjectMapping will use these values instead of reflection if the type implements this interface 35 | /// 36 | public interface IXLObjectMapping 37 | { 38 | /// 39 | /// Number of columns 40 | /// 41 | /// 42 | int ColumnCount(); 43 | /// 44 | /// Column name for index 45 | /// 46 | /// zero based 47 | /// 48 | string ColumnName(int index); 49 | /// 50 | /// Indexed getter 51 | /// 52 | /// 53 | /// 54 | object GetColumn(int index); 55 | /// 56 | /// Indexed setter 57 | /// 58 | /// 59 | /// 60 | void SetColumn(int index, object RHS); 61 | } 62 | 63 | 64 | /// 65 | /// Class to store the column mappings for poco objects so we can map excel ranges to objects of type T 66 | /// 67 | public static class XLObjectMapper 68 | { 69 | #region mappings 70 | 71 | private static ConcurrentDictionary _types = new(); 72 | 73 | public static ConcurrentDictionary ObjectMappings 74 | { 75 | get { return _types; } 76 | } 77 | 78 | public static XLObjectMapping GetObjectMapping(T instance) 79 | { 80 | var t = typeof(T); 81 | IXLObjectMapping xlobj = instance as IXLObjectMapping; 82 | if (xlobj != null) 83 | return new XLObjectMapping(t, () => xlobj); 84 | return _types.GetOrAdd(t, f => new XLObjectMapping(t)); 85 | } 86 | 87 | public static void SetObjectMapping(XLObjectMapping mapping) 88 | { 89 | _types.AddOrUpdate(mapping.MappedType, mapping, (t, m) => mapping); 90 | } 91 | 92 | #endregion 93 | 94 | #region utility functions 95 | 96 | public static object[,] ToVariant(this IEnumerable items, bool header = false) where T : class 97 | { 98 | if (items.Count() == 0) return new object[1, 1] { { 0 } }; 99 | 100 | T obj = items.First(); 101 | var map = GetObjectMapping(obj); 102 | int n = items.Count(), cols = map.Columns; 103 | int rows = header ? n + 1 : n; 104 | int start = header ? 1 : 0; 105 | 106 | 107 | object[,] retval = new object[rows, cols]; 108 | 109 | if (header) 110 | { 111 | for (int i = 0; i < cols; i++) 112 | retval[0, i] = map.Colnames[i]; 113 | } 114 | 115 | foreach (T item in items) 116 | { 117 | for (int j = 0; j < cols; j++) 118 | retval[start, j] = map.GetColumn(item, j); 119 | start++; 120 | } 121 | 122 | return retval; 123 | 124 | } 125 | 126 | public static string ToString(this IEnumerable items, string sep = ",", bool header = false) where T : class 127 | { 128 | var sb = new StringBuilder(); 129 | if (items.Count() == 0) return string.Empty; 130 | 131 | T obj = items.First(); 132 | var map = GetObjectMapping(obj); 133 | int n = items.Count(), cols = map.Columns; 134 | int rows = header ? n + 1 : n; 135 | int start = header ? 1 : 0; 136 | 137 | 138 | if (header) 139 | { 140 | for (int i = 0; i < cols; i++) 141 | sb.Append(map.Colnames[i] + sep); 142 | sb.Length -= sep.Length; 143 | sb.AppendLine(); 144 | } 145 | 146 | foreach (var item in items) 147 | { 148 | for (int j = 0; j < cols; j++) 149 | sb.Append(map.GetColumn(item, j) + sep); 150 | 151 | sb.Length -= sep.Length; 152 | sb.AppendLine(); 153 | start++; 154 | } 155 | return sb.ToString(); 156 | } 157 | 158 | public static void AddRange(this ICollection items, object vt, Func factory = null) where T : class 159 | { 160 | object[,] vtdata = vt as object[,]; 161 | 162 | int n = vtdata.GetLength(0); 163 | int k = vtdata.GetLength(1); 164 | 165 | Type t = typeof(T); 166 | 167 | 168 | bool implementsIXlObjectMapping = typeof(IXLObjectMapping).IsAssignableFrom(t); 169 | 170 | XLObjectMapping map = implementsIXlObjectMapping ? null : _types.GetOrAdd(t, f => new XLObjectMapping(t)); 171 | 172 | for (int i = 0; i < n; i++) 173 | { 174 | T instance = (factory != null) ? factory() : (t.GetConstructor(Type.EmptyTypes) != null) ? (T)Activator.CreateInstance(t, new object[0]) 175 | : (T)Activator.CreateInstance(t, true); 176 | 177 | 178 | if (implementsIXlObjectMapping) 179 | { 180 | var xlob = instance as IXLObjectMapping; 181 | for (int j = 0; j < k; j++) 182 | xlob.SetColumn(j, vtdata[i, j]); 183 | } 184 | else 185 | { 186 | for (int j = 0; j < k; j++) 187 | map.SetColumn(instance, j, vtdata[i, j]); 188 | } 189 | 190 | items.Add(instance); 191 | } 192 | } 193 | 194 | /// 195 | /// Reads the a delimeted string into the fields of a IXLRow object 196 | /// 197 | /// 198 | /// 199 | /// 200 | /// 201 | public static T DeserializeFromString(this string s, string sep = "|", Func factory = null) where T : class 202 | { 203 | Type t = typeof(T); 204 | T instance = (factory != null) ? factory() : (t.GetConstructor(Type.EmptyTypes) != null) ? (T)Activator.CreateInstance(t, new object[0]) 205 | : Activator.CreateInstance(); 206 | var map = GetObjectMapping(instance); 207 | string[] vt = s.Split(new string[] { sep }, StringSplitOptions.None); 208 | for (int i = 0; i < vt.Length; i++) 209 | { 210 | if (!string.IsNullOrEmpty(vt[i])) 211 | map.SetColumn(instance, i, vt[i]); 212 | } 213 | return instance; 214 | } 215 | /// 216 | /// Puts the contents of the IXLRow object's fields into a single line string that can be typically written into a Excel Name 217 | /// 218 | /// 219 | /// 220 | /// 221 | public static string SerializeToString(this T obj, string sep = "|") where T : class 222 | { 223 | StringBuilder sb = new(); 224 | var map = GetObjectMapping(obj); 225 | 226 | for (int i = 0; i < map.Columns; i++) 227 | { 228 | if (map.GetColumn(obj, i) != null) 229 | sb.Append(map.GetColumn(obj, i).ToString() + sep); 230 | else 231 | sb.Append(sep); 232 | } 233 | return sb.ToString(0, sb.Length - sep.Length); 234 | } 235 | 236 | public static string GetHeader(this T obj, string sep = ",") where T : class 237 | { 238 | var map = GetObjectMapping(obj); 239 | return string.Join(sep, map.Colnames); 240 | } 241 | 242 | 243 | #endregion 244 | 245 | } 246 | 247 | /// 248 | /// Proxy class to wrap existing classes to implement IXLObjectMapping 249 | /// 250 | /// 251 | public abstract class XLObjectProxy : IXLObjectMapping 252 | { 253 | protected static Func _getColumnCount; 254 | protected static Func _getColumnName; 255 | protected static Func _getters; 256 | protected static Action _setters; 257 | 258 | public T Instance { get; protected set; } 259 | 260 | #region IXLObjectMapping 261 | public int ColumnCount() 262 | { 263 | return _getColumnCount(Instance); 264 | } 265 | 266 | public string ColumnName(int index) 267 | { 268 | return _getColumnName(Instance, index); 269 | } 270 | 271 | public object GetColumn(int index) 272 | { 273 | return _getters(Instance, index); 274 | } 275 | 276 | public void SetColumn(int index, object RHS) 277 | { 278 | _setters(Instance, index, RHS); 279 | } 280 | #endregion 281 | } 282 | 283 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/XLObjectMapping.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | using System.Reflection; 27 | 28 | namespace ExcelDna.Utilities; 29 | 30 | [AttributeUsage(AttributeTargets.Property)] 31 | public class XLIgnorePropertyAttribute : Attribute 32 | { 33 | public XLIgnorePropertyAttribute() 34 | { } 35 | } 36 | 37 | public class XLObjectMapping 38 | { 39 | #region fields 40 | 41 | private Type _t; 42 | private Lazy _colnames; 43 | private Lazy _propnames; 44 | private int _columns; 45 | 46 | private Action _setters; 47 | private Func _getters; 48 | 49 | #endregion 50 | 51 | #region constructors 52 | 53 | static XLObjectMapping() 54 | { 55 | IgnorePropertyAttribute = typeof(XLIgnorePropertyAttribute); 56 | } 57 | 58 | public XLObjectMapping(Type t, Func factory = null) 59 | { 60 | _t = t; 61 | 62 | if (factory == null && !typeof(IXLObjectMapping).IsAssignableFrom(t)) 63 | { 64 | var fieldinfos = t.GetProperties() 65 | .Where(p => p.GetCustomAttributes(IgnorePropertyAttribute, false).Length == 0); 66 | 67 | var propnames = fieldinfos.Select(f => f.Name).ToArray(); 68 | 69 | SetColnames(t, propnames, propnames); 70 | } 71 | else 72 | { 73 | IXLObjectMapping instance = (factory != null) ? factory() : (t.GetConstructor(Type.EmptyTypes) != null) ? (IXLObjectMapping)Activator.CreateInstance(t, new object[0]) 74 | : (IXLObjectMapping)Activator.CreateInstance(t, true); 75 | _columns = instance.ColumnCount(); 76 | 77 | _setters = (o, i, v) => ((IXLObjectMapping)o).SetColumn(i, v); 78 | _getters = (o, i) => ((IXLObjectMapping)o).GetColumn(i); 79 | 80 | _colnames = new Lazy(() => 81 | { 82 | var retval = new string[_columns]; 83 | for (int i = 0; i < _columns; i++) 84 | retval[i] = instance.ColumnName(i); 85 | return retval; 86 | }); 87 | _propnames = _colnames; 88 | 89 | } 90 | } 91 | 92 | public XLObjectMapping(Type t, string[] colnames, string[] propnames) 93 | { 94 | _t = t; 95 | SetColnames(t, colnames, propnames); 96 | } 97 | 98 | private static IEnumerable GetAllFields(Type t) 99 | { 100 | if (t == null) 101 | return Enumerable.Empty(); 102 | 103 | BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance; 104 | return t.GetFields(flags).Concat(GetAllFields(t.BaseType)); 105 | } 106 | 107 | 108 | private void SetColnames(Type t, string[] colnames, string[] propnames) 109 | { 110 | if (colnames == null || propnames == null) 111 | throw new ArgumentException("colnames == null || propnames == null!"); 112 | if (colnames.Length != propnames.Length) 113 | throw new ArgumentException("colnames and propnames must have same length!"); 114 | 115 | _columns = colnames.Length; 116 | List allFields = null; 117 | 118 | var setters = propnames.Select(p => 119 | { 120 | var f = t.GetProperty(p); 121 | if (f.GetSetMethod() == null) // Check at least for auto-property 122 | { 123 | allFields ??= GetAllFields(t).ToList(); 124 | var field = allFields.FirstOrDefault(x => x.Name == string.Format("<{0}>k__BackingField", p)); 125 | 126 | if (field == null) 127 | return new Action((o, v) => { }); 128 | 129 | return new Action((o, v) => 130 | { 131 | field.SetValue(o, v.ConvertTo(f.PropertyType)); 132 | }); 133 | } 134 | else 135 | return new Action((o, v) => 136 | { 137 | f.SetValue(o, v.ConvertTo(f.PropertyType), null); 138 | }); 139 | }).ToArray(); 140 | _setters = (o, i, rhs) => 141 | { 142 | if (i >= 0 && i < _columns) 143 | setters[i](o, rhs); 144 | }; 145 | var getters = propnames.Select(p => 146 | { 147 | var f = t.GetProperty(p); 148 | return new Func(o => f.GetValue(o, null)); 149 | }).ToArray(); 150 | _getters = (o, i) => 151 | { 152 | if (i >= 0 && i < _columns) 153 | return getters[i](o); 154 | else 155 | return null; 156 | }; 157 | _colnames = new Lazy(() => colnames); 158 | _propnames = new Lazy(() => propnames); 159 | 160 | } 161 | 162 | #endregion 163 | 164 | #region properties 165 | 166 | public static Type IgnorePropertyAttribute { get; set; } 167 | 168 | public Type MappedType 169 | { 170 | get { return _t; } 171 | } 172 | 173 | public string[] Colnames 174 | { 175 | get { return _colnames.Value; } 176 | } 177 | 178 | public string[] Propnames 179 | { 180 | get { return _propnames.Value; } 181 | } 182 | 183 | public int Columns 184 | { 185 | get { return _columns; } 186 | } 187 | 188 | 189 | #endregion 190 | 191 | #region access to getters and setters 192 | 193 | public object GetColumn(object instance, int index) 194 | { 195 | if (index >= 0 && index < _columns) 196 | return _getters(instance, index); 197 | else 198 | return null; 199 | } 200 | 201 | public void SetColumn(object instance, int index, object RHS) 202 | { 203 | if (index >= 0 && index < _columns) 204 | _setters(instance, index, RHS); 205 | } 206 | 207 | #endregion 208 | } 209 | 210 | -------------------------------------------------------------------------------- /ExcelDna.Utilities/globals.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Joachim Loebb 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | global using System; 26 | global using System.Collections.Generic; 27 | global using System.Data; 28 | global using System.IO; 29 | global using System.Linq; 30 | global using System.Text; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2024 Joachim Loebb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ExcelDna.Utilities 2 | ================== 3 | 4 | Utilities that add functionality to ExcelDna such as creating a COM like interface to the C API 5 | 6 | This work builds on the excellent ExcelDna library by Govert van Drimmelen (http://excel-dna.net/). 7 | 8 | You can get the latest build from nuget. While the library is still in development, great care is taken to not break backward compatibility. 9 | 10 | 11 | - Light weight and intuitive (at least for myself) to use, trying to stay close to COM interface but adding C# language specifics such as generics, lambdas, collections 12 | 13 | 14 | - Have Workbook, Worksheet and other object types that nicely wrap the functionality (eg. Workbook.SaveAs()) 15 | 16 | - Range utilities like .ToVector or .ToMatrix that automatically do the correct type conversions (also for enums, Dates) 17 | 18 | - A double based date type XLDate equivalent to DateTime accelerating interaction with math libraries 19 | 20 | - Triggering / or preventing Recalculation by passing Action<> (or a lambda), the typical Screenupdating = false pattern 21 | 22 | - Interacting with sheets / range contents as List where T is a row object similar to an ORM 23 | 24 | - DataTable extensions for interacting with excel ranges 25 | 26 | Excel is a trademark of Microsoft 27 | 28 | 29 | 30 | ## General application functions 31 | 32 | 33 | ```csharp 34 | 35 | using ExcelDna.Integration; 36 | using ExcelDna.Utilities; 37 | 38 | public static class TestMacros 39 | { 40 | 41 | [ExcelCommand] 42 | public static void Test_click() 43 | { 44 | 45 | // message bar 46 | XLApp.MessageBar("This is number {0}", 5); 47 | 48 | // set calculation 49 | XLApp.Calcuation = xlCalculation.Manual; 50 | XLApp.CalculateNow(); 51 | XLApp.CalculateDocument(); 52 | 53 | // execute something on a range 54 | // can be on a different sheet; will return 55 | XLApp.ActionOnSelectedRange(r, () => { }); 56 | 57 | XLApp.ReturnToSelection(() => { }); 58 | 59 | // suspend screen updating and calculation 60 | Action a = ()=>{ /* lots of cell updating etc. */}; 61 | a.NoCalcAndUpdating(); //extension method 62 | 63 | 64 | //All open workbooks 65 | Workbook[] workbooks = XLApp.Workbooks; 66 | 67 | // Copy & paste 68 | XLApp.SelectRange("rangeRef"); 69 | XLApp.Copy(); 70 | XLApp.PasteSpecial(xlPasteType.PasteAll, 71 | xlPasteAction.None, skip_blanks: false, transpose: false); 72 | 73 | } 74 | } 75 | ``` 76 | 77 | ## Workbooks and Worksheets 78 | 79 | ```csharp 80 | 81 | 82 | [ExcelCommand] 83 | public static void Test1_click() 84 | { 85 | // get active work sheet; usually the easiest way to get reference 86 | Worksheet ws = Worksheet.ActiveSheet(); 87 | 88 | // get the workbook 89 | Workbook wb = ws.Workbook; 90 | 91 | // [Workbook]SheetName 92 | string sheetref = ws.SheetRef; 93 | 94 | // constructors 95 | // does not create sheet in workbook 96 | var ws1 = new Worksheet("[workbook]Sheetname"); 97 | var ws2 = new Worksheet(wb1, "sheet1"); 98 | 99 | var wb1 = new Workbook(@"filepath"); 100 | 101 | // creates sheet in workbook 102 | Worksheet ws3 = wb1.AddWorksheet(); 103 | ws3.Name = "Sheet3"; 104 | 105 | //Workbook functions 106 | wb1.Activate(); 107 | wb1.Save(); 108 | wb1.SaveAs("filepath", password: "", write_password: "", read_only: false); 109 | wb1.Close(saveChanges: true, routeFile: false); 110 | string wbpath = wb1.GetPath(); 111 | 112 | // {"sheet1","sheet2"} 113 | string[] sheetnames = wb1.SheetNames; 114 | // {"[Workbook1]sheet1","[Workbook1]sheet2"} 115 | string[] sheetrefs = wb1.SheetRefs; 116 | 117 | Worksheet[] sheets = wb1.Worksheets; 118 | // "Workbook1.xlsx" 119 | string wbname = wb1.Name; 120 | // "c:\temp\Workbook1.xlsx" 121 | string path = wb1.Path; 122 | } 123 | 124 | ``` 125 | 126 | ## Ranges 127 | 128 | ```csharp 129 | 130 | [ExcelCommand] 131 | public static void Test2_click() 132 | { 133 | 134 | // Get a range object 135 | ExcelReference range = Name.GetRange("[Workbook]Sheet!Name"); 136 | 137 | // copy & delete 138 | range.ClearContents(); //delete values 139 | range.Copy(toRange: null); 140 | range.DeleteEntireRows(); //shift cells up 141 | 142 | // offsetting & resizing 143 | var r = range.Offset(1, 1); 144 | r = r.Resize(10, 10); 145 | string referesto = r.RefersTo(); //"=R1C2:R2C5" 146 | 147 | // formatting 148 | r.FormatBorder(/* lots of options*/); 149 | r.FormatColor(backcolor: 0, forecolor: 0, pattern: 0); 150 | r.FormatNumber("YYYY.MM.DD"); 151 | // use this to figure out default date format for your version of excel 152 | string defaultDateFormat = XLApp.DefaultDateFormat; 153 | r.FormatNumber(defaultDateFormat); 154 | 155 | // get values 156 | string val = range.GetValue(); 157 | double[] vec = range.GetValue().ToVector(); 158 | double[,] mat = range.GetValue().ToMatrix(); 159 | 160 | //get a table / (named) range into datatable 161 | var dt1 = ws.Range("table1").ToDataTable(header: false); 162 | var dt2 = ws.Range("table1[#All]").ToDataTable(header: true); 163 | } 164 | 165 | ``` 166 | 167 | 168 | ## Names 169 | 170 | ```csharp 171 | 172 | [ExcelCommand] 173 | public static void Test3_click() 174 | { 175 | // get active work sheet; usually the easiest way to get reference 176 | Worksheet ws = Worksheet.ActiveSheet(); 177 | var wb = ws.Workbook; 178 | 179 | // create a local name 180 | var name1 = ws.AddName("name1", "=R1C1:R5C5", hidden: false, local: true); 181 | 182 | bool isglobal = name1.IsGlobalScope; 183 | bool isLocal = name1.IsLocalScope; 184 | string nameLocal = name1.NameLocal; 185 | // "[Workbook]Sheet!Name" - or - "Workbook!Name" 186 | string nameRef = name1.NameRef; 187 | //"=R1C1:R5C5" 188 | string x = name1.RefersTo; 189 | 190 | Name[] names_all = ws.Names; //local and workbook 191 | Name[] names_local = ws.NamesLocal; 192 | 193 | Name.GetValue("[Workbook]Sheet!Name"); 194 | Name.GetValue(ws, "localname"); 195 | Name.GetValue(wb, "globalname"); 196 | 197 | // Get a range object 198 | ExcelReference range = Name.GetRange("[Workbook]Sheet!Name"); 199 | } 200 | 201 | ``` 202 | 203 | ## Interaction with List and DataTable 204 | 205 | There exist a simple object mapper that allows to interact with ranges and strongly typed lists. 206 | 207 | ```csharp 208 | 209 | class Person 210 | { 211 | 212 | /* 213 | Mark properties that should be ignored by the mapper with XLIgnorePropertyAttribute 214 | 215 | It is also possible to use your own IgnorePropertyAttribute 216 | 217 | Then set XLObjectMapping.IgnorePropertyAttribute = typeof(MyIgnorePropertyAttribute); at startup 218 | This has the obvious advantage that domain model objects don't need a reference to this assembly 219 | */ 220 | 221 | [XLIgnoreProperty] 222 | public long DBID { get; set; } 223 | 224 | public string Name { get; set; } 225 | public string Address { get; set; } 226 | public DateTime BirthDay { get; set; } 227 | public int Age { get; set; } 228 | 229 | } 230 | 231 | [ExcelCommand] 232 | public static void Test4_click() 233 | { 234 | // get active work sheet; usually the easiest way to get reference 235 | Worksheet ws = Worksheet.ActiveSheet(); 236 | var wb = ws.Workbook; 237 | 238 | // if the range has the same order of fields than the object we 239 | // can do the following 240 | List persons = ws.Range("persons").ToList(); 241 | 242 | // and dump back 243 | // this will automatically adjust the size of the named output range 244 | ws.Range("persons").Fill(persons, "persons", header: false); 245 | } 246 | 247 | ``` 248 | 249 | This simple way to interact with POCOs will only work for simple field types (string, DateTime, double, ...) and if the order of the properties in the class is the same is in the columns. However, we can influence how this mapping can be done. 250 | 251 | Another way to map the properties is to define the mapping directly in the object mapper. 252 | 253 | ```csharp 254 | 255 | //Get only Name and Adress fields and use MyName and MyAdress for the header instead 256 | XLObjectMapper.SetObjectMapping(new XLObjectMapping(typeof(Person), 257 | new string[] { "MyName", "MyAddress" }, new string[] {"Name","Adress" })); 258 | 259 | ``` 260 | 261 | If we want to control more granularly how to map the fields we can implement the following interface. This is particularly useful if certain fields are arrays or class types. The object mapper will automatically pick this interface up. 262 | 263 | ```csharp 264 | /// 265 | /// Interface that allows to enumerate and name the fields of an object 266 | /// typically used for interacting with excel ranges and Lists of strongly typed objects 267 | /// that cannot be enumerated easily or automatically through reflection 268 | /// XLObjectMapping will use these values instead of reflection if the type implements this interface 269 | /// 270 | public interface IXLObjectMapping 271 | { 272 | /// 273 | /// Number of columns 274 | /// 275 | /// 276 | int ColumnCount(); 277 | /// 278 | /// Column name for index 279 | /// 280 | /// zero based 281 | /// 282 | string ColumnName(int index); 283 | /// 284 | /// Indexed getter 285 | /// 286 | /// 287 | /// 288 | object GetColumn(int index); 289 | /// 290 | /// Indexed setter 291 | /// 292 | /// 293 | /// 294 | void SetColumn(int index, object RHS); 295 | } 296 | ``` 297 | --------------------------------------------------------------------------------