Demonstrates how to create a meta-function from the relationship between two cells and use standard numerical methods to integrate the function
This sample add-in contains a dialog which allows the user to integrate a mathematical function f(x).
A function can be implied by the relationship between two cells: we treat the contents of the input cell as x and the value in the output cell as f(x).
We can then use standard numerical methods to integrate the function between two x values.
The sample displays the following features:
Displays a dialog to let the user select cells for: x, f(x) and the lower and upper bounds of integration, and a cell in which to put the result. The dialog also lets the user select an integration algorithm.
The implied function f(x) is calculated by (i) putting the value of x into the input cell, (ii) recalculating the worksheet and (iii) reading the value of the output cell. (This mechanism is referred to as the meta-function.)
The integration of the meta-function is performed by standard methods from 'Numerical Recipes in C'. The meta-function has the correct signature to be used by NR methods: double func(double).
As a time-saving feature, if the active cell is a cell that has been used in a previous integration operation, the dialog automatically fills in all the fields as they were filled in on the last occasion. (In addition, this information is saved with the worksheet when it is closed.)
The CMetaFunction class is used to represent the meta-function. The following points are important to the implementation.
The class is initialized with the addresses of the input and output cells:
CMetaFunction::CMetaFunction(const CXlOper& xloInput, const CXlOper& xloOutput) : m_xloInput(xloInput), m_xloOutput(xloOutput) { // Register self as global instance mc_instance = this; }
The class implements a call to the meta-function using CXlOper::SetValue(), CXlMacros::CalculateNow() and CXlOper::Coerce(), as follows:
double Func_(double x) { ... // Set x into m_xloInput if (!m_xloInput.SetValue(x)) { strErr.Format("Unable to set the value of X in cell %s", (LPCSTR)m_xloInput.GetRef().ToString(TRUE)); throw CMetaFunctionException(strErr); } // Recalculate CXlMacros::CalculateNow(); // Read y (as a number) from m_xloOutput CXlOper xloValue; if (xloValue.Coerce(m_xloOutput) != 0) { strErr.Format("Calculation failed for value %f", x); throw CMetaFunctionException(strErr); } if (xloValue.ChangeType(xltypeNum) != 0) { strErr.Format("Result of calculation was not a number for value %f", x); throw CMetaFunctionException(strErr); } return xloValue.ToDouble(); }
In calling the meta-function, we change the contents of the worksheet. It is polite to restore it to its original state after the operation is complete.
The CMetaFunctionState class is used to save the state of the worksheet before the meta-function is used to restore it after the meta-function has completed for the last time.
The class records (i) the contents of the input cell, which may be a formula or a value, and (ii) the calculation mode, since we will be setting it to Manual.
In addition, the CMetaFunctionState class is responsible for switching off warnings, and switching them back on when the operation is complete.
The following steps were significant in integrating the application with code from 'Numerical Recipes'.
The "Numeric Recipes" code was used without any changes.
The nrerror() function was implemented to throw an exception.
void nrerror(const char* err) { throw CMetaFunctionException(err); }
This technique is safe to use, because these NR functions do not allocate any memory. The exception must be caught by all functions that use NR code.
The following static method was added to the CMetaFunction class, with the correct signature for use by NR methods.
double CMetaFunction::Func(double);Thus, lines like the following can be used:
dResult = qgauss(CMetaFunction::Func, dLower, dUpper);
The CIntegrationDialog class contains all the logic for populating and displaying a dialog.
The CIntegrationDialog class is descended from the CXlDialog class. (For more details on using CXlDialog, see the Dialogs sample.)
Five RefEdit fields are used to let the user assign cells to x, f(x), Lower x, Upper x and the result. Each field is assigned a sequential ID.
The layout and creation of all fields is done in the constructor.
The virtual method OnOK() is implemented, to trap the pressing of the OK button. The method first validates the input fields; if any errors are found, the dialog is left open, and the focus is set to the aberrant field. If validation succeeds, EndDialog(TRUE); is called, to close the dialog and return TRUE from the CXlDialog::Show() method. (The OnOK() method is also responsible for saving the contents of the input fields for later reuse.)
The DIALOG_DATA structure is used to save the contents of the dialog's entry fields, and to repopulate them later, if the user invokes the dialog from a cell that was used in a previous intergration operation.
The DIALOG_DATA structure is a single continuous block of memory. It can therefore be used as the data assigned to a binary name (see Persistent Data below).
The SerializeDataset() method can read the contents of input fields into a DIALOG_DATA structure, or it can populate the input fields from a DIALOG_DATA structure that was created earlier.
The FindCurrentDataset() method is used to search a set of DIALOG_DATA structures for a data set that includes the address of the active cell.
The dialog saves the current list of DIALOG_DATA sets as binary data within the active worksheet, using binary names. For a complete example using binary names, see the BinaryData sample.
The entire vector of DIALOG_DATA objects is treated as a single BLOB, and saved as a binary name:
void CIntegrationDialog::SaveDatasets() { CXllApp::DefineBinaryName(mc_pszDataSetName, &m_datasets[0], m_datasets.size() * sizeof(DIALOG_DATA)); }
The BLOB is restored using GetBinaryName() and copied into the dialog's vector:
void CIntegrationDialog::RestoreDatasets() { CXlBinaryNameData data; if (CXllApp::GetBinaryName(mc_pszDataSetName, data)) { m_datasets.assign((DIALOG_DATA*)data.GetData(), (DIALOG_DATA*)(((char*)data.GetData()) + data.GetCount())); } }
The data saved in the current sheet is completely removed by calling the ClearBinaryName() method:
static void CIntegrationDialog::ClearDatasets() { CXllApp::ClearBinaryName(mc_pszDataSetName); }
This function is declared as static so that it can be called without the hassle of instantiating a CIntegrationDialog object. (It's not beautiful, but it is convenient, and it works because CXllApp::ClearBinaryName is also a static method.)
The following steps were significant in creating and completing the application's menu.
The project was created with the Add a Menu option switched on, so that the code to display a menu was automatically added to the project.
The menu code was amended to call our two add-in functions: ShowIntegrationDialog() and ClearIntegrations().
BOOL CIntegrationApp::OnXllOpenEx() { // Set up menu m_menu.SetTexts("Integ&rate"); m_menu.AddItem("&Run integration...", "ShowIntegrationDialog"); m_menu.AddItem("&Clear previous integrations", "ClearIntegrations"); m_menu.Create(); return TRUE; }
Under Excel 2007, the restoration of the serialized data fails, and the dialog fields remain empty. This is because of a known bug with BinaryName data in Excel 2007 which, it has been announced, will be fixed in Office 2007 SP1.
CXlDialog | CXlDialog::Show | CXlDialog::SetFocus | CXlDialog::ControlFromID | CXlControlRefEdit | CXlControlRefEdit::GetRef | CXlControlRefEdit::SetRef | CXlControlListBox | CXlControlListBox::AddString | CXlControlListBox::GetCurSel | CXlControlListBox::SetCurSel | CXllApp::DefineBinaryName | CXllApp::GetBinaryName | CXllApp::ClearBinaryName | CXllApp::SetCalculationMode | CXllApp::SetErrorHandling | CXllApp::GetUserAbort | CXllApp::CancelUserAbort | CXllApp::XlMessageBox | CXlMacros::CalculateNow | CXlOper::MakeRef | CXlOper::Coerce | CXlOper::ChangeType | CXlOper::GetRef | CXlOper::GetFormula | CXlOper::SetFormula | CXlOper::SetValue | CXlRef::ToString | CXlMenu | CXlMenu::SetTexts | CXlMenu::AddItem | CXlMenu::Create | CXlMenu::Destroy
Each sample project is located in a sub-directory of the Samples directory of the XLL+ installation. To use the sample project, open the solution file Integration.sln or the project file Integration.vcproj.
You can enable debugging under Excel by using the Setup Debugging command in the XLL+ ToolWindow.
When delivered, the help files are excluded from the build.
You can enable the help build by selecting the files
Integration.help.xml
and
Integration.chm
in the Solution Explorer,
and using the right-click menu to view Properties.
Select the page "Configuration Properties/General" and
set the "Excluded from build" property to "No".
See Generating help
in the User Guide for more information.