
Office Space 系列的多个专栏都已讨论过创建 Microsoft Excel 实例。理由很充分:因为 Excel 可以方便快捷地显示格式美观的数据,而且还可以对数据进行排序、运用公式求和与计算平均值、绘制数据的图形,等等。综上所述,在编写脚本时为什么不使用 Microsoft Office Excel 呢?
如果正在编写 HTA(HTML 应用程序),则还可以弹出 Microsoft Excel 的实例,作为显示数据的方式。这很好,没什么不对的。但在 HTA 中嵌入电子表格还是会很棒:这会创造更好的用户体验,并使数据更易于交互和处理。但无法在 HTA 中嵌入 Excel 电子表格。或者,可不可以……?
说实话,不可以。但可以退而求其次:可以嵌入 Microsoft Office 电子表格控件的实例,该控件是随 Microsoft Office 一起安装的 ActiveX 控件。电子表格控件不如 Excel 那么美观,但它确实具有很多 Excel 的功能。而且与试图使用 HTML 复制电子表格功能相比,它是更好、更容易的方法。
但是凭什么相信我们的话?还是让我们看一个嵌入 HTA 的电子表格简单示例吧:
<html>
<head>
<title>Spreadsheet Example</title>
</head>
<Script Language="VBScript">
Sub Window_Onload
Spreadsheet1.TitleBar.Caption = "Process Information"
End Sub
Sub GetProcesses
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("Select * From Win32_Process")
i = 1
For Each objProcess in colProcesses
strCell = "A" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.Name
strCell = "B" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.WorkingSetSize
strCell = "C" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.HandleCount
i = i + 1
Next
End Sub
</script>
<body bgcolor="buttonface">
<br>
<object classid="clsid:0002E559-0000-0000-C000-000000000046" id="Spreadsheet1">
<param name="DisplayToolbar" value="0">
<param name="DisplayWorkbookTabs" value="0">
<param name="DisplayTitleBar" value="-1">
</object>
<p><input type="button" value="Get Process Information" onclick="GetProcesses">
</body>
</html>
首次加载 HTA 时,它看起来如下:

单击按钮后,会如下显示:

但这没什么;等到在我们介绍完基本要点后,请耐心等待,直到您明白我们对此简单的小示例所做的操作。
记住这一点,现在看看我们是否能涵盖那些基本要点。为插入和配置电子表格控件,我们使用以下几行代码:
<object classid="clsid:0002E559-0000-0000-C000-000000000046" id="Spreadsheet1">
<param name="DisplayToolbar" value="0">
<param name="DisplayWorkbookTabs" value="0">
<param name="DisplayTitleBar" value="-1">
</object>
插入控件只涉及添加一个 <object> 标记,并同时指定一对参数:classid,即全局唯一标识号,可告诉操作系统要在 HTA 中嵌入哪个 ActiveX 控件;id,即分配给该控件的别名。任何时候我们要在脚本中引用该控件都将使用此别名。
还为该电子表格控件配置了三个属性。为使界面保持整洁,我们希望隐藏工具栏及工作簿选项卡。因此,我们将 DisplayToolbar 属性和 DisplayWorkbookTabs 属性设置为 0 (False)。但如果想要显示项目又该如何呢?这时,我们会将值设置为 -1 (True)。实际上,这就是我们使用此行代码要实现的,该代码将显示电子表格控件标题栏:
<param name="DisplayTitleBar" value="-1">
注意:存在其他可配置的电子表格属性吗?事实上,简直太多了,无法在本文中一一详述。有关详细信息,请参阅 MSDN 中的 Microsoft Office 电子表格组件参考(英文)。 |
然后添加一个标准 HTML 按钮,单击该按钮时会运行名为 GetProcesses 的子例程:
<input type="button" value="Get Process Information" onclick="GetProcesses">
该子例程负责用户界面:它只包含一个电子表格和一个按钮。现在让我们看一看在此 HTA 中所用的两个子例程。首先,我们有 Window_Onload 子例程;正如我们本周已多次指出的,该名称的子例程会在每次加载或刷新 HTA 时自动运行。在此示例 HTA 中,我们在 Window_Onload 子例程中只做一件事:将值 Process Information 分配给标题栏 Caption:
Spreadsheet1.TitleBar.Caption = "Process Information"
而 GetProcesses 子例程则更有趣,它从本地计算机检索进程信息并随后在电子表格中显示该信息:
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("Select * From Win32_Process")
i = 1
For Each objProcess in colProcesses
strCell = "A" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.Name
strCell = "B" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.WorkingSetSize
strCell = "C" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.HandleCount
i = i + 1
Next
首先我们使用某种标准 WMI 代码,该代码检索当前在本地计算机上运行的全部进程的信息;此进程信息集存储在名为 colProcesses 的变量中。然后将计数器变量 (i) 的值设置为 1;将使用此计数器变量跟踪电子表格的当前行。
最后,设置一个 For Each 循环,以遍历整个进程集。现在,有趣的事情发生了。
对于初学者,我们会使用下面这行代码:
strCell = "A" & i
这行代码起什么作用呢?在电子表格控件中,我们需要按地址(A1、B4、C17,等等)引用单元格。第一段数据 ¨C 集合中第一个进程的名称 ¨C 将存储在单元格 A1 中。此行代码只是向变量 strCell 分配值 A1(A 加上计数器变量 i 的当前值)。
在下一行代码中,我们将进程名称写入单元格 A1:
Spreadsheet1.Range(strCell).Cells(1) = objProcess.Name
此处的语法有点怪,但基本上我们是引用一个范围(由单个单元格,即单元格 A1 组成的范围)及该范围中的第一个单元格;这即是 Cells(1) 的作用所在。有点令人费解,但真正重要的是(至少现在如此),该范围被设定在我们要配置的单元格。然后我们集中注意力到该范围的第一个(且唯一的)单元格。
随后为单元格 B1(进程 WorkingSetSize)和 C1(进程 HandleCount)重复该过程。我们将变量 i 的值加 1(因为下次循环时我们要将数据写入到第 2 行),然后进入下一轮循环,重新开始来处理该集合中的第二个进程。
这看起来不错,特别是想到这是一个真正的电子表格时确实不错。想要计算句柄总数吗?没问题:将光标置于句柄数下的单元格中(假定有 40 个进程,意味着最后一个句柄数在单元格 C40 中),然后在标准 Excel 公式中键入以下内容(或通过编程方式将此公式分配至该单元格):
=SUM(C1:C40)
想要使数据按工作集大小排序吗?选中数据,右键单击该电子表格,然后选择 Sort Ascending 或 Sort Descending。想要修改文本的外观吗?选中一个或两个单元格,然后按 Ctrl-B 选择粗体或 Ctrl-I 选择斜体,等等。
正如我们前面所说,这没什么。以下是修改后的电子表格,该表格增加了更多的点缀:

稍后我们将向您展示该电子表格的全部代码。首先,我们看看新增加的一些内容。
首先,您可能注意到了我们调整了列 A、B 和 C 的大小;这样我们可以真正读到写入这些列的数据。要实现此目的,只需将此行代码添加到 Window_Onload 子例程:
Spreadsheet1.Range("A1:C1").ColumnWidth = 20
此处我们已创建了由单元格 A1 到 C1 组成的范围;随后我们将 ColumnWidth 属性设置为 20。请注意,不必选中此三列中的所有单元格;对某列中任意单元格设置栏宽将导致为该列中的所有单元格设置栏宽。
我们还将列标题放入了单元格 A1、B1 和 C1。我们通过将以下行代码添加到 GetProcesses 子例程实现了此目的:
Spreadsheet1.Range("A1").Cells(1) = "Process"
Spreadsheet1.Range("B1").Cells(1) = "Working Set Size"
Spreadsheet1.Range("C1").Cells(1) = "Handle Count"
Spreadsheet1.Range("A1:C1").Font.Bold = True
我们只将标题名称写入各个单元格,然后在创建了由单元格 A1 到 C1 组成的另一个范围,再将 Font.Bold 属性设置为 True。
注意:因为要将标题放入第 1 行,所以要从第 2 行开始写入数据。因此,您可能注意到了,我们将计数器变量的值设置为 2。 |
啊,您注意到我们设置为黄色的单元格了吗?这些单元格恰好指示 WorkingSetSize 大于 5,000,000 的进程。以下是我们更改颜色所使用的代码:
If objProcess.WorkingSetSize > 5000000 Then
strCell = "A" & i & ":C" & i
Spreadsheet1.Range(strCell).Interior.ColorIndex = 6
End If
这实际相当简单(虽然看起来可能不简单)。我们编写一个字符串,代表需要着色的三个单元格(即当前行中的列 A、B 和 C):
strCell = "A" & i & ":C" & i
然后使用此行代码将 Interior.ColorIndex 属性设置为相应的颜色值:
Spreadsheet1.Range(strCell).Interior.ColorIndex = 6
注意:我们如何知道 6 会将单元格标为黄色?您可以在 Office Space 专栏中了解详细信息。 |
最后一件事是:使数据按进程名的字母顺序进行排序。下面是实现该目的的代码:
Set objConstants = Spreadsheet1.Constants strSortRange = "A1:C" & i Spreadsheet1.Range(strSortRange).Sort 1, objConstants.xlAscending, objConstants.xlYes
首先创建 Spreadsheet.Constants 对象的实例;该实例提供对控件类型库内置常量的访问。然后使用此代码行选中该电子表格中的全部数据:
strSortRange = "A1:C" & i
最后,对该范围进行排序,并传递三个参数:
| • | 1,指示进行排序的列号。(即该范围中的第一列。) |
| • | objConstants:xlAscending,告诉控件对数据按升序排序。如果要以降序排序,则可以使用值 objConstants.xlDescending。 |
| • | objConstants.xlYes,该参数只是说“是的,该数据确实具有标题行”。如果数据无标题行,则可以使用值 xlNo。 |
这就是需要做的所有工作。运行该子例程,即可在 HTA 中实现对数据排序和颜色编码。用 HTML 试一试这个!
注意:千万别真试!我们的保险可没包括这项。 |
顺便说一下,以下是这个“精美”的 HTA 的完整代码:
<html>
<head>
<title>Spreadsheet Example</title>
</head>
<Script Language="VBScript">
Sub Window_Onload
Spreadsheet1.Range("A1:C1").ColumnWidth = 20
Spreadsheet1.TitleBar.Caption = "Process Information"
End Sub
Sub GetProcesses
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery("Select * From Win32_Process")
i = 2
Spreadsheet1.Range("A1").Cells(1) = "Process"
Spreadsheet1.Range("B1").Cells(1) = "Working Set Size"
Spreadsheet1.Range("C1").Cells(1) = "Handle Count"
Spreadsheet1.Range("A1:C1").Font.Bold = True
For Each objProcess in colProcesses
strCell = "A" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.Name
strCell = "B" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.WorkingSetSize
strCell = "C" & i
Spreadsheet1.Range(strCell).Cells(1) = objProcess.HandleCount
If objProcess.WorkingSetSize > 5000000 Then
strCell = "A" & i & ":C" & i
Spreadsheet1.Range(strCell).Interior.ColorIndex = 6
End If
i = i + 1
Next
Set objConstants = Spreadsheet1.Constants
strSortRange = "A1:C" & i
Spreadsheet1.Range(strSortRange).Sort 1, objConstants.xlAscending, objConstants.xlYes
End Sub
</script>
<body bgcolor="buttonface">
<br>
<object classid="clsid:0002E559-0000-0000-C000-000000000046" id="Spreadsheet1" width="90%">
<param name="DisplayToolbar" value="0">
<param name="DisplayWorkbookTabs" value="0">
<param name="DisplayTitleBar" value="-1">
</object>
<p><input type="button" value="Get Process Information" name="B3" onclick="GetProcesses">
</body>
</html>