Chapter 2
This chapter will delve into the functionality and the programming model of the Microsoft Office Spreadsheet component. Since this book focuses on building real solutions, I'll present a few interesting uses of each component feature along with its description. In Part II of the book, you'll see many of these ideas actually implemented.
This chapter will give you an understanding of what the Spreadsheet component can and cannot do, suggest clever ways you can use your own code to add functionality to the component, and show the key elements of the programming model that will get you started.
Before we get too detailed, let's examine the basic features of the Spreadsheet component and describe the various ways it can load and save data.
The recalculation engine lies at the heart of the Spreadsheet component—making this component more than just a typical grid control. Designed by the same developers who built the recalculation engine for Microsoft Excel, the Spreadsheet component supports nearly all the functions in Excel 2000, including most of those in the Analysis ToolPak (ATP).
NOTE
For those who arecurious , the following functions are not supported by the Spreadsheet component: ASC, CALL, DATEDIF, FINDB, FREQUENCY, GETPIVOTDATA, GROWTH,INFO , ISPMT, JIS, LEFTB, LENB, LINEST, LOGEST, MDETERM, MIDB, MINVERSE, MMULT, PHONETIC, REGISTER.ID, REPLACEB, RIGHTB, SEARCHB, SQL.REQUEST, TRANSPOSE, TREND, and YEN. All the functions ending in the letter "B"operate at the byte level instead of the character level in a double-byte character set (DBCS) system.Refer to the sections on property binding and function add-ins later in this chapter to see how to use the VBA or VBScript equivalents of these functions. Also, the INDEX and LOOKUP functions each have two forms in Excel, one using arrays and one using vectors (
single-dimension ranges). The Spreadsheet control supports the vector forms but not the array forms.
Any use of the Spreadsheet component that involves formulas will naturally require the recalculation engine—for example, a solution allowing users to view a product
When the Spreadsheet component is used without a
For example, a bank or
The Spreadsheet component supports a worksheet with a maximum of 65,536 rows by 702 columns (A through ZZ) but supports only one worksheet per instance of the component. This is the same number of rows that Excel 2000 offers but nearly three times the number of columns. (Excel supports only 256
WARNING
If you try to load all 65,536 rows and 702 columns, you'll be waiting quite a while. Since the Spreadsheet control loads its content from HTML, the load operation is naturally slower than that of loading binary data, such as when Excel loads an XLS file. Although you cangenerally load files with hundreds of rowsfairly quickly, large models will not load quickly in the Spreadsheet component.
The Spreadsheet control supports both absolute ($A$1) and relative (A1)
NOTE
The Spreadsheet component does not support the old R1C1 reference style. It also doesn't support English language formulas, which attempt to let you build formulas based on named ranges. While this was a wonderful goal, it seldom works as expected.
Unfortunately, the Spreadsheet component doesn't support named ranges in this version of Office. When Excel publishes a spreadsheet model with
interactivity
(in other words, publishes a page containing the Spreadsheet control), it automatically converts named range references to absolute range references. This also occurs when you copy and paste cells from Excel to the Spreadsheet control. If you want to use a named range in the code around the Spreadsheet component, consider using the Dictionary object (Scripting.Dictionary) as an easy way to store and retrieve the actual references for a given
For example, if you want to define a named range that refers to a list of data in the spreadsheet and want to use that range in script to perform a
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "MyName", "A1:F20"
Set rng = Spreadsheet1.Range(dict("MyName"))
|
The Dictionary object is an associative array. You add key/value pairs to it and, given the key, you can
On top of the recalculation engine sits the Spreadsheet component user interface. The user interface is, of course, similar to that of Excel but is
Spreadsheet Reference MadnessWhile reviewing this chapter, one of our capable Spreadsheet component developers, Andrew Milton, showed me many other types of range references supported by Excel but not supported by the Spreadsheet component. However, I had never seen most of these references and didn't even know they were possible in Excel. Try this:
- Type 1 , 2 , 3 , 4 , 5 into the cells A1:A5.
- Next, type the value 2 in cell B1.
- Finally, enter the formula =SUM(A1:CHOOSE(B1, A1, A2, A3, A4, A5)) into cell C1. You should get 3 as the result.
- Now change cell B1 to 3 . The formula will recalculate to 6.
The CHOOSE function acts like a Select Case statement in VBA and can be used in the middle of a range reference to define the range's end point dynamically. Defining a range dynamically also works with the INDIRECT function, but the Spreadsheet component simply doesn't support such dynamic range definition.
Range intersection is another interesting construct. On the same spreadsheet you used for the last example, type the formula =A1:A5 A1:B1 into cell D1. You should get 1. This type of reference makes Excel perform an intersection of the two ranges, resolving to a reference of A1. The Spreadsheet component lets you enter a formula like this, but it resolves the formula to =A1, while Excel
leaves the formula as you originally entered it.
Figure 2-1. A basic Spreadsheet component.
The Spreadsheet component user interface contains a great number of spreadsheet features that people now take for granted. Rather than list every last one here, I'll leave you to discover most of them as you play with the control. Although the following list is not exhaustive, it should give you an idea of the level of user interface features supported by the Spreadsheet control:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Since the Spreadsheet component is just a control and not an application unto itself, most of the formatting capabilities are exposed through a modeless tool window called the
Property Toolbox
. This part of the user interface is actually shared among all the
Interestingly enough, the Property Toolbox was written entirely in Dynamic HTML (DHTML). The Office Web Components host an instance of the Internet Explorer control to render the Property Toolbox and execute its code when you interact with the elements on the web page. The Property Toolbox merely executes script against the component's programming model so that anything the Property Toolbox does, you can do in code too.
The Spreadsheet control, along with the other Office Web Components, supports the Office 2000 language settings that enable users to set the user interface language used in the Office applications without having to reset the underlying system regional settings. If these language settings have been changed from their defaults, the controls will automatically adjust their user interfaces to show the selected language. This also affects currency, date, and number formatting. The Spreadsheet component supports Unicode
While you are reading this section, you might find it useful to
The Spreadsheet component supports the basic sorting and filtering functionality found in Excel and exposes it through the programming model and the user interface. However, the sort and filter user interface in the Spreadsheet control is somewhat improved over that in Excel. Let's look at an example.
Adventures in DHTMLThe Property Toolbox is an incredible piece of work created by two of OWC's talented developers, Eric Matteson and Cesar Alvarez. The Property Toolbox is also proof that attempting to emulate Office user interface conventions in DHTML is just asking for trouble. Early on, we decided that we should make the Property Toolbox look as much like the standard Office user interface as possible, and Eric and Cesar faithfully spent many months twisting and contorting HTML and Internet Explorer to make it
comply . Most people can't believe that the result is actually in HTML. However, the jury's still out on whether emulating the Office user interface in HTML makes it any easier to use, considering that the average person can use web sites quite effectively but is still befuddled by many of the advanced dialog boxes in the Office applications.I think everyone would agree that it would have been much easier to follow the user interface conventions made popular on the Web and present a new kind of command interface born and bred in HTML. For developers intending to use the Office Web Components in web pages, my advice is not to
spend your time trying to wrangle HTML into a traditional Microsoft Windows application user interface. Instead, use HTML's simplicity and dynamic layout strengths to develop a more natural, easy-to-use interface for your application.
Suppose you have developed a spreadsheet that lists your current product line, showing each product's unit price, quantity in stock, and quantity on order, as well as a calculated column showing a potential worth given a sell-through rate. Now the user wants to sort the list of products by their potential worth in descending order. Through the Spreadsheet component user interface, the user simply selects the range to sort (or selects any cell in the range) and clicks the Sort Descending toolbar button. When this button is clicked, a menu appears below it that does not appear in Excel, as Figure 2-2 depicts.
Figure 2-2. The Spreadsheet component user interface in action.
One of the common problems users encounter while sorting Excel ranges is selecting the range to sort and the column to sort by. The Spreadsheet component lets users easily select the range to sort, and it lets them choose the column to sort by from a list of column
You might notice that the Spreadsheet component lets you sort the list only one column at a time. Excel offers a Sort dialog box that lets you sort by up to three keys at once (for example, sort by category, then by shipper, and then by potential worth). The Spreadsheet component has no user interface for doing this, but the underlying engine does support it. To emulate multicolumn sorting, you can use the following routine:
'--------------------------------------------------------------------------
' MultiColumnSort
' Purpose: Sorts the spreadsheet by many columns at once
' In: References to the spreadsheet and range to sort,
' an array of column numbers on which to sort,
' and an array (same size) of direction indicators
' Out: Nothing (performs the sort)
'
Sub MultiColumnSort(Spreadsheet, Range, Columns, Directions)
' Start an undo unit so that this can undo as a whole unit of work
Spreadsheet.BeginUndo()
' Turn ScreenUpdating off so that the spreadsheet does not redraw
' while we are resetting filters, sorting, and reapplying filters
Spreadsheet.ScreenUpdating = False
' Now loop over the Columns and Directions arrays backwards,
' which will give us the effect we want
For ct = ubound(Columns) To lbound(Columns) Step -1
' 0 is a guess for column headings
Range.Sort Columns(ct), Directions(ct), 0
Next 'ct
' Turn ScreenUpdating back on so that the spreadsheet redraws
Spreadsheet.ScreenUpdating = True
' End the undo unit
Spreadsheet.EndUndo()
End Sub 'MultiColumnSort()
|
The trick to multicolumn sorts is to actually perform the sorts in the
The Spreadsheet component also sports a new AutoFilter user interface. The filtering functionality is similar to that found in Excel, but the AutoFilter drop-down lists in the user interface are a little different. Suppose that in the list of products we examined in Figure 2-2 you want to filter out some product categories to see how it would affect the products with high potential worth. The developer or user could turn on AutoFilter, click the AutoFilter arrow for the Category column, and see the screen shown in Figure 2-3.
Figure 2-3. The Spreadsheet component's AutoFilter user interface.
In Excel, you can choose a single item quite easily; however, selecting more than one item requires using the Custom AutoFilter dialog box, which can be quite arduous when you simply want to exclude four or five items. In the Spreadsheet component, the AutoFilter drop-down list has check boxes next to each item, as well as a Show All item at the top that lets you quickly toggle all items between the checked and unchecked states.
The astute reader will notice that the AutoFilter drop-down lists do not include two of the useful settings found in Excel. For instance, you won't find a Top 10 item, which allows you to quickly filter for the top 10 (or any other number of) items. Nor will you find a Custom item, which you can use to perform more complex filtering than simple include and exclude filters allow. Unfortunately, these higher-level functions aren't built in to the Spreadsheet component yet. However, you can easily emulate them by making a few calls to the Spreadsheet control's programming model.
To emulate top N filtering, you can use the following routine:
'--------------------------------------------------------------------------
' TopNFilter
' Purpose: Filters for the top N items in the list given a column number
' In: References to the spreadsheet and range, column number,
' number of rows, and direction value that indicates
' top N or bottom N filtering
' Out: Nothing (performs the top N filter)
'
Sub TopNFilter(Spreadsheet, Range, ColumnNum, N, Direction)
Set c = Spreadsheet.Constants
Set rngData = Range
Set af = Spreadsheet.ActiveSheet.AutoFilter
' Start an undo unit so that this can undo as a whole unit of work
Spreadsheet.BeginUndo()
' Turn ScreenUpdating off so that the spreadsheet does not redraw
' while we are resetting filters, sorting, and reapplying filters
Spreadsheet.ScreenUpdating = False
' Clear any existing filters
ClearFilters Spreadsheet
' Sort the list in the data range by the column number
If LCase(Direction) = "bottom" Then
rngData.Sort ColumnNum, c.ssAscending, c.ssNo
Else
rngData.Sort ColumnNum, c.ssDescending, c.ssNo
End If
' Top N can actually include more than N rows if the N+1, N+2,
' and so on rows have the same value as the Nth row.
' Go to the N+1 row and see if it's the same as the Nth.
' Loop until there is a different value.
vNValue = rngData.Cells(N,ColumnNum).Value
While rngData.Cells(N+1,ColumnNum).Value = vNValue
N = N + 1
Wend
' N is now set to the number of rows we want to include in the filter
Set fltr = af.Filters(ColumnNum)
fltr.Criteria.FilterFunction = c.ssFilterFunctionInclude
For ct = 1 To N
fltr.Criteria.Add(rngData.Cells(ct,ColumnNum).Text)
Next
' Finally apply the AutoFilter
af.Apply
' Turn ScreenUpdating back on so that the spreadsheet redraws
Spreadsheet.ScreenUpdating = True
' End the undo unit
Spreadsheet.EndUndo()
End Sub 'TopNFilter()
|
Top N filtering might seem as easy as sorting and then viewing the first N rows. But true top N filtering can return more than N rows because it really means "include the top N values." If the tenth and eleventh values are identical after the sort, a top 10 filter will return both products since they are among the top 10 values. The previous code can perform both top and bottom N filters by merely changing the sort direction from descending to ascending.
Similarly, you can emulate
'--------------------------------------------------------------------------
' ExpressionFilter
' Purpose: Filters a list on a given column using an arbitrary expression
' that can be evaluated by VBScript
' In: References to the spreadsheet and range, column number to
' filter upon, and expression to use for evaluation
' Out: None (list is filtered)
'
Sub ExpressionFilter(Spreadsheet, Range, ColumnNum, Expression)
Dim sExp ' Temporary expression variable
Dim vValue ' Temporary value holder
Set c = Spreadsheet.Constants
Set rngData = Range
Set af = Spreadsheet.ActiveSheet.AutoFilter
' Start an undo unit so that this can undo as a whole unit of work
Spreadsheet.BeginUndo()
' Turn ScreenUpdating off so that the spreadsheet does not redraw
' while we are resetting filters, sorting, and reapplying filters
Spreadsheet.ScreenUpdating = False
' Clear any existing filters
ClearFilters Spreadsheet
' Get the filter object for the specified column,
' and set the filter function to "include"
Set fltr = af.Filters(ColumnNum)
fltr.Criteria.FilterFunction = c.ssFilterFunctionInclude
' Check whether the expression contains the column value token,
' and set a flag if it does
fValueToken = cbool( _
instr(1, Expression, g_sValueToken, vbTextCompare) > 0)
' Loop over the column values in all the rows
For Each cell In rngData.Columns(ColumnNum).Cells
' Get the current row's value
vValue = cell.Value
' If vValue is a string, we need to wrap quotes around it in
' case it contains spaces
If vartype(vValue) = vbString Then
vValue = """" & vValue & """"
End If
' Build the expression we need to execute by inserting the
' current row's value in the right place
If fValueToken Then
sExp = "g_fEval = cbool(" & Replace(Expression, _
g_sValueToken, vValue, 1, -1, vbTextCompare) & ")"
Else
sExp = "g_fEval = cbool(" & vValue & " " & Expression & ")"
End If
' Execute the expression
window.execScript sExp, "VBScript"
' The global g_fEval will now be set to True or False.
' If True, the row should be included in the filter.
If g_fEval Then
fltr.Criteria.Add cell.Text
End If
Next 'ct
' Finally apply the AutoFilter
af.Apply
' Turn ScreenUpdating back on so that the spreadsheet redraws
Spreadsheet.ScreenUpdating = True
' End the undo unit
Spreadsheet.EndUndo()
End Sub 'ExpressionFilter()
|
This routine uses a Document Object Model (DOM) method named execScript to evaluate expressions. (DOM is the programming model exposed to scripting in Internet Explorer.) This method
Alternatively, you can evaluate expressions by using the Eval method of the Spreadsheet component's Worksheet object. Eval uses the Spreadsheet component's function libraries and expression evaluator instead of the active scripting engine, meaning it's useful in containers other than Internet Explorer or when you want to let users include spreadsheet functions or range references in the expression. However, the active scripting engine can give you a powerful expression
Since the Spreadsheet component is not an application, the questions "Where does it get data from?" and "How can I save its data?" are crucial. Yet the answers to these questions are much more complicated than they would be for an application. The good news is that the Spreadsheet component can load and save data in a variety of ways that you can use creatively in your solutions.
Unlike an application, a component does not "own" the storage mechanism used by the container. It is the container's responsibility to save the entire form or document into a persistence mechanism and to reload it. A container usually asks the component to save its current state into a stream or
property bag;
the container then
Publishing a spreadsheet or range from Excel 2000 with interactivity will prompt Excel to create an HTML file with an <object> tag for the Spreadsheet component. Excel copies the content of the selected spreadsheet or range into the HTML page as a parameter to the <object> tag, so once the data is published, it no longer refers to the source spreadsheet. However, you can easily republish content from Excel, and Excel will replace the previous content with the new content,
To try publishing from Excel 2000, open your favorite workbook and choose the Save As Web Page command from the File menu. You will then see the dialog box depicted in Figure 2-4.
Figure 2-4. Choose Save As Web Page to display this dialog box.
Choose the Selection option, and check the Add Interactivity check box. When you save, instead of saving the content as static HTML, Excel will write a page containing the Spreadsheet control and a copy of the selected content. For more advanced control over what is published, click the Publish button to display the Publish As Web Page dialog box.
You might encounter an error message when attempting to publish certain spreadsheets to HTML. If the source spreadsheet is protected with a password (using the ToolsProtectionProtect Sheet command), Excel won't let you publish the spreadsheet or any range on it to a web page. Doing so is a breach of security since a web page is only plain text that
Note that you can still use protection to lock most cells and let users change only the cells you've designated as updateable. As long as you don't use a password when protecting the spreadsheet, you can publish or copy the spreadsheet to the Spreadsheet component and all the protection settings will be preserved.
Excel 2000 and the Spreadsheet component are both capable of reading and writing ranges in HTML table format, augmented with extra attributes and XML (Extensible Markup Language) code that conveys information specific to Excel. This means you can copy ranges from Excel 2000 and paste them into the Spreadsheet control and vice versa, which is useful for both authoring spreadsheets and copying data seen in a Spreadsheet control to Excel for further analysis.
You should note a few things when copying and pasting ranges. First, if a cell in the range has a formula referring to a cell that's not in the range, that's on another worksheet, or that's in another workbook, Excel will simply copy the current value for that cell but not the formula. Consider the problem for a moment: If you paste a formula referring to a cell that is outside the range of the copied cell into the Spreadsheet control, the Spreadsheet control has no way to resolve that reference and can't show any data. So any reference to a cell outside the copied range turns into a literal value that equals the value of the reference when it was copied.
Second, a more advanced structure in an Excel spreadsheet, such as a PivotTable, will paste only as literal data cells and not as a PivotTable structure. (In other words, you won't be able to pivot or drill to more detail.) The Office Web Components do, however, include the PivotTable component for performing PivotTable functionality. Charts will not paste at all since the Spreadsheet component isn't capable of hosting other controls or floating images.
Third, spreadsheet protection settings affect not only how the content gets published but also how it gets pasted into the Spreadsheet control. If the source spreadsheet is password protected, the range will still copy and paste, but only literal values will be pasted into the Spreadsheet component. If the spreadsheet is protected, but not with a password, the range will paste normally.
It's also interesting to note that the format of data pasted into the Spreadsheet component is HTML, so any application that can copy an HTML table to the clipboard can be used to get data into the Spreadsheet control. Excel 2000 does embed other information in the clipboard data, such as the formula and full-precision value for a given cell. However, if another application copies an HTML table to the clipboard without this extra information, the table will still be pasted into the Spreadsheet control as literal data with formatting.
There isn't much to say about this approach, except that you will find many of the features that make authoring spreadsheets easy in Excel missing in the Spreadsheet component. Nevertheless, you can still select cells while entering formulas to quickly enter cell references, set formatting information through the Property Toolbox, and hide or show various elements such as the title bar, toolbar, column and row headings, and gridlines. It's much more practical to author your spreadsheets in Excel 2000 and then publish or copy the content into the Spreadsheet component when done. But for simple spreadsheets, you might find it easier to type the model directly into the Spreadsheet control.
As noted earlier, not all containers make it easy to activate controls at design time and interact with them. Typing directly into the Spreadsheet component will work only in containers that allow controls to activate and that persist the control's content when saving.
Using the Spreadsheet Component in Visual Basic and FrontPageBoth Visual Basic and FrontPage make
excellent containers for the Spreadsheet component; however, a few tips can help make the editing experience much easier.In Visual Basic, the Spreadsheet control will be UI active as soon as you click it. This makes it hard to move the control around the form since clicking and dragging will simply select ranges of cells. However, you can move the entire control by clicking the title bar and then dragging the control. If the title bar isn't visible, set the DisplayTitleBar property to True to temporarily show it, and then set it to False to hide it.
When editing the Spreadsheet control in Visual Basic or FrontPage, you should also avoid using the AutoFit property (leave it set to False). Using AutoFit in Visual Basic is dangerous because the Spreadsheet control will resize to fit whatever content is loaded into it without scroll bars. If that content is larger than your Visual Basic form, the control will resize right off the edge of the form. However, if you know that the content will be smaller than the form and that it will never receive larger content, it's safe to use AutoFit.
In FrontPage 2000, the AutoFit behavior will not work in the Normal view; however, it will work correctly in the Preview view or in the web browser.
A URL is the most curious yet powerful mechanism for loading data into the Spreadsheet component. Using the Property Toolbox or the programming model, you can tell the Spreadsheet control to load its content by opening a specific URL and loading the first HTML table
Note that the Spreadsheet component uses the security mechanism in Internet Explorer to make sure that it doesn't load data from a domain other than the one where the first page originated (depending on your Internet Explorer security settings). This feature
This feature uses the security settings the client specifies in Internet Explorer, so users can turn off cross-domain access warnings if they think the site the page originated from is "trusted." If the site is not in the trusted sites zone or has anything but the
I'll discuss security a bit more in the chapters ahead. The Spreadsheet component's security mechanism is slightly different from security used when accessing databases (more on this in Chapter 5): it will never give the user the opportunity to permit cross-domain access when the URL refers to a site not in the trusted sites zone. The Spreadsheet control merely returns an error and says that it's not allowed, whereas in the database access scenario, the user can approve the cross-domain access if he or she chooses.
Unfortunately, the Spreadsheet control cannot load data directly from a binary XLS file, but it can load from an Excel file saved in HTML format by loading from a URL as described above. This allows developers building a spreadsheet model to use Excel 2000 for creation and maintenance. Plus, it lets developers load that model directly into the Spreadsheet control at runtime.
Is a Different Share on the Same File Server a Different Domain?Near the end of the OWC product cycle, a tester posted a bug against the Spreadsheet component that said it was showing the security warning message even though the URL it was getting data from was on the same file server as the original HTML page. We were perplexed since it did seem that this should be a trusted scenario, but because we used Internet Explorer's security code to determine whether two URLs were from the same domain, we
pushed the bug over to the Internet Explorer team.It turns out that the page's URL and the data's URL were indeed pointing to the same file server but were pointing to different shares on that file server. The Internet Explorer team explained that technically these are two different domains since it's common to use
mammoth file servers for many different groups in an organization, and those that have permissions to one share might not have the same permissions on the other share.So if you are using file share access instead of a web server, keep in mind that different shares on the same file server are different domains as far as security is
concerned . However, this applies only to file share access, not HTTP access to a web server.
Just as loading data into the Spreadsheet component is not as straightforward as in the Excel application, saving data from the Spreadsheet control can take place in various ways, making it difficult to explain.
Most containers don't allow the user to save the form or document while it is in a runtime state. For example, a Visual Basic form has no direct saving mechanism when it is running. Internet Explorer is slightly different: it offers a "save as" mechanism for running pages, but it doesn't allow users to alter the original page from the web server. (If that were possible, any hacker could alter your company's home page!) Often, when we show the Office Web Components to customers, one of their first assumptions is that they can change the content of a Spreadsheet control and resave it to the web server like a shared file. Because of the way the Web works, this isn't possible—unless Internet Explorer can return a new version of the page to the web server, which most servers wouldn't allow anyway.
To