Table of Contents

Validating FlexCel recalculation (VB.Net / netframework)

Note

This demo is available in your FlexCel installation at <FlexCel Install Folder>\samples\vb\VS2022\netframework\10.API\75.Validate Recalc and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​NET-​demos/​tree/​master/​vb/​VS2022/​netframework/​Modules/​10.​API/​75.​Validate Recalc

Overview

By default, FlexCel recalculates the files it generates before saving. This is not completely necessary for normal xls/x files, because Excel will recalculate the file again when opening. But when natively printing or exporting to PDF, you need the values from the recalculation, because no Excel is involved in the process.

While most files will recalculate fine, if you have complex formulas and need to verify they will recalculate ok, this is the application to use.

Concepts

  • Button "Validate Recalc": This will verify that flexcel can understand all the formulas on your sheet.

  • Button "Compare with Excel": This will open a file you saved with Excel, force a recalculation in FlexCel and then compare all formula results with the original ones. You can use this to verify the results are actually what you expect them to be.

Files

AssemblyInfo.vb

Imports System.Reflection
Imports System.Runtime.CompilerServices

'
' General Information about an assembly is controlled through the following 
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
'
<Assembly: AssemblyTitle("")>
<Assembly: AssemblyDescription("")>
<Assembly: AssemblyConfiguration("")>
<Assembly: AssemblyCompany("")>
<Assembly: AssemblyProduct("")>
<Assembly: AssemblyCopyright("(c) 2002 - 2014 TMS Software")>
<Assembly: AssemblyTrademark("")>
<Assembly: AssemblyCulture("")>

'
' Version information for an assembly consists of the following four values:
'
'      Major Version
'      Minor Version 
'      Build Number
'      Revision
'
' You can specify all the values or you can default the Revision and Build Numbers 
' by using the '*' as shown below:

<Assembly: AssemblyVersion("6.2.1.0")>

'
' In order to sign your assembly you must specify a key to use. Refer to the 
' Microsoft .NET Framework documentation for more information on assembly signing.
'
' Use the attributes below to control which key is used for signing. 
'
' Notes: 
'   (*) If no key is specified, the assembly is not signed.
'   (*) KeyName refers to a key that has been installed in the Crypto Service
'       Provider (CSP) on your machine. KeyFile refers to a file which contains
'       a key.
'   (*) If the KeyFile and the KeyName values are both specified, the 
'       following processing occurs:
'       (1) If the KeyName can be found in the CSP, that key is used.
'       (2) If the KeyName does not exist and the KeyFile does exist, the key 
'           in the KeyFile is installed into the CSP and used.
'   (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
'       When specifying the KeyFile, the location of the KeyFile should be
'       relative to the project output directory which is
'       %Project Directory%\obj\<configuration>. For example, if your KeyFile is
'       located in the project directory, you would specify the AssemblyKeyFile 
'       attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
'   (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
'       documentation for more information on this.
'
<Assembly: AssemblyDelaySign(False)>
<Assembly: AssemblyKeyFile("")>
<Assembly: AssemblyKeyName("")>

Form1.Designer.vb

Imports System.Collections
Imports System.ComponentModel
Imports FlexCel.Core
Imports FlexCel.XlsAdapter
Imports System.IO
Imports System.Reflection
Namespace ValidateRecalc
	Partial Public Class mainForm
		Inherits System.Windows.Forms.Form

		Private openFileDialog1 As System.Windows.Forms.OpenFileDialog
		Private report As System.Windows.Forms.RichTextBox
		Private linkedFileDialog As System.Windows.Forms.OpenFileDialog
		Private components As System.ComponentModel.IContainer = Nothing

		''' <summary>
		''' Clean up any resources being used.
		''' </summary>
		Protected Overrides Sub Dispose(ByVal disposing As Boolean)
			If disposing Then
				If components IsNot Nothing Then
					components.Dispose()
				End If
			End If
			MyBase.Dispose(disposing)
		End Sub

		#Region "Windows Form Designer generated code"
		''' <summary>
		''' Required method for Designer support - do not modify
		''' the contents of this method with the code editor.
		''' </summary>
		Private Sub InitializeComponent()
			Dim resources As New System.ComponentModel.ComponentResourceManager(GetType(mainForm))
			Me.openFileDialog1 = New System.Windows.Forms.OpenFileDialog()
			Me.report = New System.Windows.Forms.RichTextBox()
			Me.XlsReport = New FlexCel.Report.FlexCelReport()
			Me.linkedFileDialog = New System.Windows.Forms.OpenFileDialog()
			Me.mainToolbar = New System.Windows.Forms.ToolStrip()
			Me.validateRecalc = New System.Windows.Forms.ToolStripButton()
			Me.compareWithExcel = New System.Windows.Forms.ToolStripButton()
			Me.toolStripSeparator1 = New System.Windows.Forms.ToolStripSeparator()
			Me.btnInfo = New System.Windows.Forms.ToolStripButton()
			Me.btnExit = New System.Windows.Forms.ToolStripButton()
			Me.mainToolbar.SuspendLayout()
			Me.SuspendLayout()
			' 
			' openFileDialog1
			' 
			Me.openFileDialog1.DefaultExt = "xls"
			Me.openFileDialog1.Filter = "Excel Files|*.xls;*.xlsx;*.xlsm|Excel 97/2003|*.xls|Excel 2007|*.xlsx;*.xlsm|All " & "files|*.*"
			Me.openFileDialog1.Title = "Open an Excel File"
			' 
			' report
			' 
			Me.report.Dock = System.Windows.Forms.DockStyle.Fill
			Me.report.Font = New System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, (CByte(0)))
			Me.report.Location = New System.Drawing.Point(0, 38)
			Me.report.Name = "report"
			Me.report.ReadOnly = True
			Me.report.Size = New System.Drawing.Size(768, 327)
			Me.report.TabIndex = 3
			Me.report.Text = ""
			' 
			' linkedFileDialog
			' 
			Me.linkedFileDialog.DefaultExt = "xls"
			Me.linkedFileDialog.Filter = "Excel Files|*.xls;*.xlsx;*.xlsm|Excel 97/2003|*.xls|Excel 2007|*.xlsx;*.xlsm|All " & "files|*.*"
			Me.linkedFileDialog.Title = "Please supply the location for the following linked file."
			' 
			' mainToolbar
			' 
			Me.mainToolbar.Items.AddRange(New System.Windows.Forms.ToolStripItem() { Me.validateRecalc, Me.compareWithExcel, Me.toolStripSeparator1, Me.btnInfo, Me.btnExit})
			Me.mainToolbar.Location = New System.Drawing.Point(0, 0)
			Me.mainToolbar.Name = "mainToolbar"
			Me.mainToolbar.Size = New System.Drawing.Size(768, 38)
			Me.mainToolbar.TabIndex = 11
			Me.mainToolbar.Text = "toolStrip1"
			' 
			' validateRecalc
			' 
			Me.validateRecalc.Image = (CType(resources.GetObject("validateRecalc.Image"), System.Drawing.Image))
			Me.validateRecalc.ImageTransparentColor = System.Drawing.Color.Magenta
			Me.validateRecalc.Name = "validateRecalc"
			Me.validateRecalc.Size = New System.Drawing.Size(90, 35)
			Me.validateRecalc.Text = "&Validate Recalc"
			Me.validateRecalc.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageAboveText
'			Me.validateRecalc.Click += New System.EventHandler(Me.validateRecalc_Click)
			' 
			' compareWithExcel
			' 
			Me.compareWithExcel.Image = (CType(resources.GetObject("compareWithExcel.Image"), System.Drawing.Image))
			Me.compareWithExcel.ImageTransparentColor = System.Drawing.Color.Magenta
			Me.compareWithExcel.Name = "compareWithExcel"
			Me.compareWithExcel.Size = New System.Drawing.Size(115, 43)
			Me.compareWithExcel.Text = "Compare with Excel"
			Me.compareWithExcel.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageAboveText
'			Me.compareWithExcel.Click += New System.EventHandler(Me.compareWithExcel_Click)
			' 
			' toolStripSeparator1
			' 
			Me.toolStripSeparator1.Name = "toolStripSeparator1"
			Me.toolStripSeparator1.Size = New System.Drawing.Size(6, 46)
			' 
			' btnInfo
			' 
			Me.btnInfo.Image = (CType(resources.GetObject("btnInfo.Image"), System.Drawing.Image))
			Me.btnInfo.ImageTransparentColor = System.Drawing.Color.Magenta
			Me.btnInfo.Name = "btnInfo"
			Me.btnInfo.Size = New System.Drawing.Size(74, 43)
			Me.btnInfo.Text = "Information"
			Me.btnInfo.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageAboveText
'			Me.btnInfo.Click += New System.EventHandler(Me.btnInfo_Click)
			' 
			' btnExit
			' 
			Me.btnExit.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right
			Me.btnExit.Image = (CType(resources.GetObject("btnExit.Image"), System.Drawing.Image))
			Me.btnExit.ImageTransparentColor = System.Drawing.Color.Magenta
			Me.btnExit.Name = "btnExit"
			Me.btnExit.Size = New System.Drawing.Size(59, 35)
			Me.btnExit.Text = "     E&xit     "
			Me.btnExit.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageAboveText
'			Me.btnExit.Click += New System.EventHandler(Me.button2_Click)
			' 
			' mainForm
			' 
			Me.AutoScaleDimensions = New System.Drawing.SizeF(6F, 13F)
			Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
			Me.ClientSize = New System.Drawing.Size(768, 365)
			Me.Controls.Add(Me.report)
			Me.Controls.Add(Me.mainToolbar)
			Me.Name = "mainForm"
			Me.Text = "Validate FlexCel recalculation"
			Me.mainToolbar.ResumeLayout(False)
			Me.mainToolbar.PerformLayout()
			Me.ResumeLayout(False)
			Me.PerformLayout()

		End Sub
		#End Region

		Private mainToolbar As ToolStrip
		Private WithEvents validateRecalc As ToolStripButton
		Private WithEvents compareWithExcel As ToolStripButton
		Private toolStripSeparator1 As ToolStripSeparator
		Private WithEvents btnInfo As ToolStripButton
		Private WithEvents btnExit As ToolStripButton
	End Class
End Namespace


Form1.vb

Imports System.Collections
Imports System.ComponentModel
Imports FlexCel.Core
Imports FlexCel.XlsAdapter
Imports System.IO
Imports System.Reflection

Namespace ValidateRecalc
	''' <summary>
	''' Use this demo to validate the recalculation made by FlexCel.
	''' </summary>
	Partial Public Class mainForm
		Inherits System.Windows.Forms.Form

		Private XlsReport As FlexCel.Report.FlexCelReport

		Public Sub New()
			InitializeComponent()
			ResizeToolbar(mainToolbar)
		End Sub

		Private Sub ResizeToolbar(ByVal toolbar As ToolStrip)

			Using gr As Graphics = CreateGraphics()
				Dim xFactor As Double = gr.DpiX / 96.0
				Dim yFactor As Double = gr.DpiY / 96.0
				toolbar.ImageScalingSize = New Size(CInt(Fix(24 * xFactor)), CInt(Fix(24 * yFactor)))
				toolbar.Width = 0 'force a recalc of the buttons.
			End Using
		End Sub


		Private Sub button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnExit.Click
			Close()
		End Sub

		Private Sub btnInfo_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnInfo.Click
			MessageBox.Show("This example will validate the calculations performed by the FlexCel engine." & vbLf & "It can do it in 2 different ways:" & vbLf & "  1) The button 'Validate Recalc' will analyze a file, and report if there is anything that FlexCel doesn't support on it." & vbLf & "  2) The button 'Compare with Excel' will open a file saved by Excel, recalculate it with FlexCel, compare the values reported by both FlexCel and Excel and report if there are any differences.")
		End Sub

		Private Sub validateRecalc_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles validateRecalc.Click
			If openFileDialog1.ShowDialog() <> System.Windows.Forms.DialogResult.OK Then
				Return
			End If
			Dim Xls As New XlsFile()

			Xls.Open(openFileDialog1.FileName)

			' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			' ////////Code here is only needed if you have linked files. In this example we don't know, so we will use it /////////
			Dim Work As New TWorkspace() 'Create a workspace
			Work.Add(Path.GetFileName(openFileDialog1.FileName), Xls) 'Add the original file to it
			AddHandler Work.LoadLinkedFile, AddressOf Work_LoadLinkedFile 'Set up an event to load the linked files.
																						 ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

			report.Text = "Results on file: " & openFileDialog1.FileName
			Dim Usl As TUnsupportedFormulaList = Xls.RecalcAndVerify()
			If Usl.Count = 0 Then
				report.Text &= vbLf & "**********All formulas supported!**********"
				Return
			End If

			report.Text &= vbLf & "Issues Found:"
			For i As Integer = 0 To Usl.Count - 1
				Dim FileName As String = String.Empty
				If Usl(i).FileName IsNot Nothing Then
					FileName = "File: " & Usl(i).FileName & "  => "
				End If
				report.Text &= vbLf & "     " & FileName & Usl(i).Cell.CellRef & ": " & Usl(i).ErrorType.ToString()
				If Usl(i).StackTrace IsNot Nothing Then
					For k As Integer = 0 To Usl(i).StackTrace.Length - 1
						If Usl(i).StackTrace(k).Address IsNot Nothing Then
							Dim TraceFileName As String = ""
							If Usl(i).StackTrace(k).FileName Is Nothing Then TraceFileName = "[" & Usl(i).StackTrace(k).FileName & "]"
							report.Text &= vbLf & "         -> References cell: " & TraceFileName & Usl(i).StackTrace(k).Address.CellRef
						End If
					Next k
				End If
				If Usl(i).FunctionName IsNot Nothing Then
					Dim FunctionStr As String = "Function"
					If Usl(i).ErrorType = TUnsupportedFormulaErrorType.ExternalReference Then
						FunctionStr = "Linked file not found"
					End If
					report.Text &= " ->" & FunctionStr & ": " & Usl(i).FunctionName
				End If
			Next i
		End Sub

		Private Sub compareWithExcel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles compareWithExcel.Click
			If openFileDialog1.ShowDialog() <> System.Windows.Forms.DialogResult.OK Then
				Return
			End If
			compareWithExcel.Enabled = False
			validateRecalc.Enabled = False
			Try
				Dim xls1 As New XlsFile()
				Dim xls2 As New XlsFile()

				xls1.Open(openFileDialog1.FileName)
				xls2.Open(openFileDialog1.FileName)
				report.Text = "Compare with Excel: " & openFileDialog1.FileName

				' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
				' ////////Code here is only needed if you have linked files. In this example we don't know, so we will use it /////////
				Dim Work As New TWorkspace() 'Create a workspace
				Work.Add(Path.GetFileName(openFileDialog1.FileName), xls1) 'Add the original file to it
				AddHandler Work.LoadLinkedFile, AddressOf Work_LoadLinkedFile 'Set up an event to load the linked files.
																							 ' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


				CompareXls(xls1, xls2, Nothing)
			Finally
				compareWithExcel.Enabled = True
				validateRecalc.Enabled = True
			End Try
		End Sub

		Private Sub CompareXls(ByVal xls1 As XlsFile, ByVal xls2 As XlsFile, ByVal table As DataTable)
			Dim DiffCount As Integer = 0
			xls1.Recalc()

			For sheet As Integer = 1 To xls1.SheetCount
				xls1.ActiveSheet = sheet
				xls2.ActiveSheet = sheet
				Dim aColCount As Integer = xls1.ColCount
				For r As Integer = 1 To xls1.RowCount
					For c As Integer = 1 To aColCount
						Dim f As TFormula = TryCast(xls1.GetCellValue(r, c), TFormula)
						If f IsNot Nothing Then
							Dim ad As New TCellAddress(r, c)
							Dim f2 As TFormula = CType(xls2.GetCellValue(r, c), TFormula)
							If f.Result Is Nothing Then
								f.Result = ""
							End If
							If f2.Result Is Nothing Then
								f2.Result = ""
							End If
							Dim eps As Double = 0
							If TypeOf f.Result Is Double AndAlso TypeOf f2.Result Is Double Then
								If CDbl(f2.Result) = 0 Then
									If Math.Abs(CDbl(f.Result)) < Double.Epsilon Then
										eps = 0
									Else
										eps = Double.NaN
									End If
								Else
									eps = CDbl(f.Result) / CDbl(f2.Result)
								End If
								If Math.Abs(eps - 1) < 0.001 Then
									f.Result = f2.Result
								End If
							End If
							If Not f.Result.Equals(f2.Result) Then
								If table Is Nothing Then
									report.Text &= vbLf & "Sheet:" & xls1.SheetName & " --- Cell:" & ad.CellRef & " --- Calculated: " & f.Result.ToString() & "    Excel: " & f2.Result.ToString() & "  dif: " & eps.ToString() & "   formula: " & f.Text
									Application.DoEvents()
								Else
									table.Rows.Add(New Object() { xls1.SheetName, ad.CellRef, f.Result.ToString(), f2.Result.ToString(), eps.ToString(), f.Text })
								End If
								DiffCount += 1

							End If
						End If
					Next c
				Next r
			Next sheet

			If table Is Nothing Then
				report.Text &= vbLf & "Finished Comparing."
				If DiffCount = 0 Then
					report.Text &= vbLf & "**********No differences found!**********"
				Else
					report.Text &= String.Format(vbLf & "  --->Found {0} differences", DiffCount)
				End If
			End If
		End Sub

		Private Sub ValidateXls(ByVal xls As XlsFile, ByVal table As DataTable)
			Dim Usl As TUnsupportedFormulaList = xls.RecalcAndVerify()
			For i As Integer = 0 To Usl.Count - 1
				table.Rows.Add(New Object() { Usl(i).FileName, Usl(i).Cell.CellRef, Usl(i).ErrorType.ToString(), Usl(i).FunctionName })
			Next i
		End Sub

		''' <summary>
		''' This is the method that will be called by the ASP.NET front end. It returns an array of bytes 
		''' with the report data, so the ASP.NET application can stream it to the client.
		''' </summary>
		''' <returns>The generated file as a byte array.</returns>
		Public Function WebRun(ByVal DataStream As Stream, ByVal FileName As String) As Byte()
			XlsReport.SetValue("Date", Date.Now)
			XlsReport.SetValue("FileName", FileName)
			Dim Data As New DataSet()
			Dim ValidateResult As DataTable = Data.Tables.Add("ValidateResult")
			ValidateResult.Columns.Add("FileName", GetType(String))
			ValidateResult.Columns.Add("CellRef", GetType(String))
			ValidateResult.Columns.Add("ErrorType", GetType(String))
			ValidateResult.Columns.Add("FunctionName", GetType(String))

			Dim CompareResult As DataTable = Data.Tables.Add("CompareResult")
			CompareResult.Columns.Add("SheetName", GetType(String))
			CompareResult.Columns.Add("CellRef", GetType(String))
			CompareResult.Columns.Add("CalcResult", GetType(String))
			CompareResult.Columns.Add("XlsResult", GetType(String))
			CompareResult.Columns.Add("Diff", GetType(String))
			CompareResult.Columns.Add("FormulaText", GetType(String))

			XlsReport.AddTable(Data)

			Dim xls1 As New XlsFile()
			Dim xls2 As New XlsFile()

			xls1.Open(DataStream)
			DataStream.Position = 0
			xls2.Open(DataStream)

			CompareXls(xls1, xls2, CompareResult)
			ValidateXls(xls1, ValidateResult)

			Dim DataPath As String = Path.Combine(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), ".."), "..") & Path.DirectorySeparatorChar

			Using OutStream As New MemoryStream()
				Using InStream As New FileStream(DataPath & "ValidateReport.xls", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
					XlsReport.Run(InStream, OutStream)
					Return OutStream.ToArray()
				End Using
			End Using
		End Function

		''' <summary>
		''' This event is used when there are linked files, to load them on demand.
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="e"></param>
		Private Sub Work_LoadLinkedFile(ByVal sender As Object, ByVal e As LoadLinkedFileEventArgs)
			'IMPORTANT: DO NOT USE THIS METHOD IN PRODUCTION IF SECURITY IS IMPORTANT.
			'This method will access any file in your harddisk, as long as it is linked in the spreaadhseet, and
			'that could mean an IMPORTANT SECURITY RISK. You should limit the places where the app can search for 
			'linked files. Look at the "Recalculating Linked Files" in the PDF API Guide for more information.

			Dim FilePath As String = Path.Combine(Path.GetDirectoryName(openFileDialog1.FileName), e.FileName)

			If File.Exists(FilePath) Then 'If we find the path, just load the file.
				e.Xls = New XlsFile()
				e.Xls.Open(FilePath)
				Return
			End If

			'If we couldn't find the file, ask the user for its location.
			linkedFileDialog.FileName = FilePath
			If linkedFileDialog.ShowDialog() <> System.Windows.Forms.DialogResult.OK Then 'if user cancels, e.Xls will be null, so no file will be used and an #errna error will show in the formulas.
				Return
			End If

			e.Xls = New XlsFile()
			e.Xls.Open(linkedFileDialog.FileName)

		End Sub

	End Class

End Namespace


Program.vb

Namespace ValidateRecalc
	Friend NotInheritable Class Program

		Private Sub New()
		End Sub

		''' <summary>
		''' The main entry point for the application.
		''' </summary>
		<STAThread> _
		Shared Sub Main()
			Application.EnableVisualStyles()
			Application.SetCompatibleTextRenderingDefault(False)
			Application.Run(New mainForm())
		End Sub
	End Class
End Namespace