下面我们通过一个例子,来了解一下日常宏编程中最常面临的任务(因为使用了全局表达式,请将【工具】》【选项】》【编译】》【禁止全局作用域表达式】取消勾选):
/*提供一些表格相关的工具函数与常数*/const XLS = {//数据有效性类型枚举 Range.Validation.Add(...)方法的第一个参数XlDVType : {xlValidateInputOnly : 0,//仅在用户更改值时进行验证。xlValidateWholeNumber : 1,//全部数值。xlValidateDecimal : 2,//数值。xlValidateList : 3,//值必须存在于指定列表中。xlValidateDate : 4,//日期值。xlValidateTime : 5,//时间值。xlValidateTextLength : 6,//文本长度。xlValidateCustom : 7//使用任意公式验证数据有效性。},//常用颜色常数Colors : {Black : 0, //黑色DarkRed : 192, //深红Red : 255, //红色Orange : 49407, //橙色Yellow : 65535, //黄色LightGreen : 5296274, //浅绿Green : 5287936, //绿色LightBlue : 15773696, //浅蓝Blue : 12611584, //蓝色DarkBlue : 6299648, //深蓝Purpose : 10498160, //紫色Magenta : 0xFF00FF, //紫红色Cyan : 0xFFFF00, //青色White : 0xFFFFFF, //白色},/*获取鼠标选取的单元格区域对象prompt : 对话框提示信息title : 对话框标题must : 是否强制返回一个单元格区域对象*/GetRange : function(prompt = '请用鼠标框选单元格区域',title = '选取单元格区域', must = false) {if (!g.IsType(prompt, 'String') ||!g.IsType(title, 'String') ||!g.IsType(must, 'Boolean'))throw new TypeError('参数 prompt/title/must 分别必' +'须是 String/String/Boolean 对象');if (must) title += '[必须]';while(true) {var rng = Application.InputBox(prompt, title, undefined,undefined, undefined, undefined, undefined, 8);if (!must) break;if (must && (typeof rng == 'object')) break;}return rng;},/*获取指定列的列字母columnIndex : 列序数,Number 类型*/GetColumnLetter : function(columnIndex) {if (!g.IsType(columnIndex, 'Number'))throw new TypeError('参数 columnIndex 必须是一个数字');if (columnIndex <= 0 || columnIndex > 16384)throw new Error('Please make sure 1 <= columnIndex <= 16384.');let address = ActiveSheet.Columns.Item(columnIndex).Address();return address.substr(1, address.indexOf(':') - 1);},/*功能:为单元格区域的每个单元格值上面加(数字)或追加(文本)指定数据value : 要加/追加的值rng : 目标单元格区域*/AddOrAppend : function(value, rng) {if (!(typeof value == 'string' ||typeof value == 'number'))throw new Error('Parameter "value" must be a number/string object.');if (typeof rng == 'undefined')rng = XLS.GetRange(undefined, undefined, true);if (rng.constructor.name != 'Range')throw new Error('Parameter "rng" must be a Range object.');for (let iArea = 1; iArea <= rng.Areas.Count; iArea++) {let area = rng.Areas.Item(iArea);for (let iRow = 1; iRow <= area.Rows.Count; iRow++) {for (let iColumn = 1; iColumn <= area.Columns.Count; iColumn++) {let cell = area.Cells.Item(iRow, iColumn);if (typeof cell.Value2 == 'undefined')cell.Value2 = value;elsecell.Value2 += value;}}}},/*获取单元格区域的所有数据,如果有多个子区域,将返回一个多维数组,否则,返回一维数组*/GetValues : function(rng) {if (typeof rng == 'undefined')rng = XLS.GetRange(undefined, undefined, true);if (rng.constructor.name != 'Range')throw new Error('Parameter "rng" must be a Range object.');let result = [];for (let iArea = 1; iArea <= rng.Areas.Count; iArea++) {let values = [];let area = rng.Areas.Item(iArea);for (let iRow = 1; iRow <= area.Rows.Count; iRow++) {for (let iColumn = 1; iColumn <= area.Columns.Count; iColumn++) {values.push(area.Cells.Item(iRow, iColumn).Value());}}result.push(values);}if (result.length == 1)return result[0];elsereturn result;},/*获取单元格区域的完全引用的地址*/GetRangeFullAddress : function(rng) {if (typeof rng == 'undefined')rng = XLS.GetRange(undefined, undefined, true);if (rng.constructor.name != 'Range')throw new Error('Parameter "rng" must be a Range object.');return "'" + rng.Worksheet.Parent.Path + "\\[" +rng.Worksheet.Parent.Name + "]" + rng.Worksheet.Name +"'!" + rng.Address();},/*为单元格区域创建简单的边框*/MakeSimpleBorders : function(rng, color, weight, lineStyle) {if (!XLS.IsRange(rng))throw new TypeError('参数 rng 必须是一个单元格区域对象');if (typeof color == 'undefined')color = 0; //黑色if (typeof color != 'number' ||Math.ceil(color) != color)throw new TypeError('参数 color 必须是一个整数');if (typeof weight == 'undefined')weight = xlThin; //细if (typeof weight != 'number' ||Math.ceil(weight) != weight)throw new TypeError('参数 weight 必须是一个整数');if (typeof lineStyle == 'undefined')lineStyle = xlContinuous;if (typeof lineStyle != 'number' ||Math.ceil(lineStyle) != lineStyle)throw new TypeError('参数 lineStyle 必须是一个整数');let indices = [xlEdgeLeft, xlEdgeTop,xlEdgeBottom, xlEdgeRight,xlInsideHorizontal, xlInsideVertical];for (let index of indices) {(obj=>{obj.Weight = weight;obj.LineStyle = lineStyle;obj.Color = color;})(rng.Borders.Item(index));}},/*判断一个对象是否是 Range 类型的对象*/IsRange : function(rng) {return g.IsType(rng, 'Range');},SetFormatConditionByExampleRange : function() {{//与用户交互,取得操作需要的输入//指定数据表所在的单元格区域let title = '选取数据表';let prompt = '请用鼠标框选你要按值表样例加设条件格式的工作' +'表数据所在的单元格区域(请不要连带选中表头行)';var rngMain = XLS.GetRange(prompt, title, true);//指定值表样例所在的单元格区域title = '选取值表样例';prompt = '请用鼠标框选你要设置条件格式参照的值表样例所在的' +'单元格区域(请确保设置了格式)';var rngExample = XLS.GetRange(prompt, title, true);//指定条件格式的基准列title = '选取条件格式基准列';prompt = '请用鼠标选取为数据表设置条件格式时的基准列';var rngBaseColumn;while(true) {rngBaseColumn = XLS.GetRange(prompt, title, true);if (rngBaseColumn.Columns.Count > 1)alert('此类型条件的基准列只能是一列,请只选择一个列');else {if (Application.Intersect(rngBaseColumn, rngMain) == undefined)alert('你指定的基准列与之前选取的数据表之间没有交集,所以' +'此列不能作为基准列,请重新选取');elsebreak;}}}{//为条件格式准备需要的公式let rngIntersect = Application.Intersect(rngBaseColumn, rngMain);let addrFirstCell = rngIntersect.Cells.Item(1).Address();let columnAddress = addrFirstCell.substr(0, addrFirstCell.lastIndexOf('$'));var tempFormula = '=INDIRECT("Column" & ROW()) = "Value"';tempFormula = tempFormula.replace('Column', columnAddress);}//从值表样例单元格区域创建可迭代对象,以迭代每个单元格let ociCells = new OfficeCollectionIterator(rngExample);//按值表样例增加条件格式for (let cell of ociCells) {let info = {Value : cell.Value(),BackColor : cell.Interior.Color,};//因为是要写在公式中,双写可能存在的引号if (typeof info.Value === 'string')info.Value = info.Value.replace('"', '""');let fcFormula = tempFormula.replace('Value', info.Value);let formatCondition = rngMain.FormatConditions.Add(xlExpression, -1, fcFormula, "",undefined, undefined, undefined, undefined);//formatCondition.SetFirstPriority();formatCondition.Interior.Color = info.BackColorformatCondition.StopIfTrue = false;}},/*列出菜单栏清单*/ListAllCommandBarsInTable : function() {let cbs = new OfficeCollectionIterator(Application.CommandBars);let data = Enumerable.from(cbs).select((cb, i) => [i, cb.Name,cb.NameLocal, cb.Type, cb.BuiltIn]).toArray();//写数据到表let writter = new XLSTableWritter('序号,名称,友好名,类型,内建?'.split(','), data, '菜单栏清单','类型有:\n0 => 默认菜单栏;\n1 => 菜单栏;\n2 => 快捷菜单');writter.WriteTo(new Range('B40'));},}/*一个数据表测试它向外静态提供了一个数据表的完整数据并以实例的方式围绕一个数据表,向用户初步测试各类相关对象与功能*/class XLSExample {constructor(rng) {if (rng == null ||rng == undefined ||rng.constructor.name != 'Range')throw new TypeError('要求传入的 rng 参数是一个单元格区域对象');this.TopLeftCell = rng.Cells.Item(1);this.RowCount = XLSExample.Data.length;this.ColumnCount = XLSExample.Headers.length;//标题区域this.TitleRange = this.TopLeftCell.Resize(1, this.ColumnCount);//表头区域this.HeadersRange = this.TopLeftCell.Offset(1, 0).Resize(1, this.ColumnCount);//主数据区域this.MainRange = this.TopLeftCell.Offset(1, 0).Resize(XLSExample.Data.length + 1, this.ColumnCount);this.TableRange = this.TopLeftCell.Resize(XLSExample.Data.length + 2, XLSExample.Headers.length);this.IsTableCreated = false;this.Comment = null;this.Borders = null;this.Validation = null;this.FormatCondition = null;this.Sort = null;this.WriteData();}//表格样例的标题static get Title() {if (XLSExample._Title == undefined)XLSExample._Title = '古名人成绩单';return XLSExample._Title;}//表格样例的表头static get Headers() {if (XLSExample._Headers == undefined)XLSExample._Headers = ['姓名' , '性别','年龄', '语文', '数学', '外语'];return XLSExample._Headers;}//表格样例的数据static get Data() {if (XLSExample._Data == undefined)XLSExample._Data = [['李白' , '男', 23, 99, 57, 80],['赵云' , '男', 32, 77, 63, 55],['貂蝉' , '女', 18, 80, 80, 80],['李清照', '女', 25, 98, 66, 90],['赵佶' , '男', 54, 96, 33, 82],['武曌' , '女', 78, 65, 66, 63],['力士' , '阉', 55, 79, 67, 77],['赵高' , '阉', 43, 82, 88, 83],['玄奘' , '僧', 56, 78, 54, 98],['罗麽' , '僧', 42, 88, 77, 66]];return XLSExample._Data;}//将数据写到初始化时的单元格位置WriteData() {//写标题数据this.TitleRange.Merge();this.TitleRange.Cells.Item(1).Value2 = XLSExample.Title;//写表头this.HeadersRange.Value2 = XLSExample.Headers;//写表内容for (let i = 0; i < XLSExample.Data.length; i++) {this.TopLeftCell.Offset(i + 2).Resize(1,this.ColumnCount).Value2 = XLSExample.Data[i];}}//添加批注,并保存创建的批注对象的引用,以备更多探索访问AddComment() {this.TitleRange.ClearComments();let comment = this.TopLeftCell.AddComment();comment.Visible = true;let now = new Date(Date.now());comment.Text('批注添加于 :\n' + now.toLocaleString());comment.Shape.Width = 136;comment.Shape.Height = 30;//这里只右移了 2 列,可见单元格区域是否被合并,会影响//Range.Offset() 方法的功能let rngLocation = this.TopLeftCell.Offset(8, 2);comment.Shape.Left = rngLocation.Left;comment.Shape.Top = rngLocation.Top;this.Comment = comment;}//给单元格区域添加边框AddBorders() {let borders = this.MainRange.Borders;//1.外边框for (let iBorder of [xlEdgeBottom,xlEdgeLeft,xlEdgeRight,xlEdgeTop]) {let border = borders.Item(iBorder);border.Color = XLS.Colors.Blue/*color:蓝色*/;border.LineStyle = xlDouble/*lineStyle:双实线*/;border.Weight = xlMedium/*weight:中等粗细*/;}//2.内边框for (let iBorder of [xlInsideHorizontal, xlInsideVertical]) {let border = borders.Item(iBorder);border.Color = XLS.Colors.Red/*color:红色*/;border.LineStyle = xlDot/*lineStyle:点线*/;border.Weight = xlThin/*weight:细线*/;}/*//3.斜边框for (let iBorder of [xlDiagonalDown, xlDiagonalUp]){let border = borders.Item(iBorder);border.Color = XLS.Colors.Blue; //color:蓝色border.LineStyle = xlContinuous;//lineStyle:实线border.Weight = xlThin;//weight:细线}*///最后,留存边框对象的引用,以备更多探索访问this.Borders = borders;}//设置字体与对齐方式SetFontAndAlignment() {//将表标题加粗,并水平分散缩进5对齐(obj=>{obj.Font.Bold = true;obj.HorizontalAlignment = xlHAlignDistributed;obj.VerticalAlignment = xlVAlignCenter;obj.AddIndent = false;obj.IndentLevel = 5;})(this.TitleRange);//将表头行加粗this.HeadersRange.Font.Bold = true;//设置主区域的第一列为楷体,水平分散对齐let rngFirstColumn = this.MainRange.Columns.Item(1);rngFirstColumn.HorizontalAlignment = xlHAlignDistributed;rngFirstColumn.Font.Name = '楷体';//设置主区域除第一列以外的区域,水平居中对齐for (let iColumn = 2; iColumn <= this.ColumnCount; iColumn++)this.MainRange.Columns.Item(iColumn).HorizontalAlignment = xlHAlignCenter;}//给单元格区域添加数据有效性验证AddValidation() {let iColumn = XLSExample.Headers.indexOf('性别') + 1;let rngValidation = this.MainRange.Columns.Item(iColumn);rngValidation.Validation.Delete();rngValidation.Validation.Add(/*JSA不支持 XlDVType 枚举,在此用全局变量,模拟自定义了一个 Enum,为使 JSA 能编译通过,请确保【工具】》【选项】》【编译】》【禁止全局作用域表达式】处于未被勾选状态*/XLS.XlDVType.xlValidateList,xlValidAlertStop, xlBetween, "男,女,阉,僧", undefined);(obj => {obj.InputTitle = "性别";obj.InputMessage = "允许的性别是:男,女,阉,僧";obj.ErrorTitle = "数据非法";obj.ErrorMessage = "只接受“男,女,阉,僧”四种性别";obj.InCellDropdown = true;})(rngValidation.Validation);//留存数据有效对象的引用,以备更多探索访问this.Validation = rngValidation.Validation;}//添加条件格式:当各科都及格时,把行单元格的字段设置为绿色AddFormatCondition() {this.MainRange.FormatConditions.Delete();//如果“语文、数学、英语”都及格了,将字体颜色改为绿色let formatCondition = this.MainRange.FormatConditions.Add(xlExpression, -1, '=AND(' +'ISNUMBER(INDIRECT("$E" & ROW())),' +'INDIRECT("$E" & ROW())>=60,' +'INDIRECT("$F" & ROW())>=60,' +'INDIRECT("$G" & ROW())>=60)',"", undefined, undefined, undefined, undefined);formatCondition.SetFirstPriority();formatCondition.Font.Color = XLS.Colors.Green;//绿色formatCondition.Font.TintAndShade = 0;formatCondition.StopIfTrue = false;//留存创建的条件格式对象,以备更多探索this.FormatCondition = formatCondition;}//添加自动筛选AddAutoFilter() {if (this.TopLeftCell.Worksheet.AutoFilter != undefined) {alert('数据自动筛选已经开启,无法再次开启');return;}this.MainRange.AutoFilter(undefined,undefined, xlAnd, undefined, undefined);let refersTo = "='" + this.MainRange.Worksheet.Name +"'!" + this.MainRange.Address();this.MainRange.Worksheet.Names.Add(XLSExample.name,refersTo, false, undefined, undefined, undefined,undefined, undefined, undefined, undefined, undefined);}//添加排序规则:按年龄升序排列AddSortRule() {//获取排序基准列内容区域let iColumn = XLSExample.Headers.indexOf('年龄') + 1;let rngSortBy = this.HeadersRange.Cells.Item(iColumn).Offset(1, 0).Resize(XLSExample.Data.length, 1);//添加排序规则(obj=>{(obj=>{obj.Clear();obj.Add(rngSortBy, xlSortOnValues,xlAscending, "", undefined);})(obj.SortFields);obj.Header = xlYes;obj.Orientation = xlSortColumns;obj.MatchCase = false;obj.SortMethod = xlPinYin;obj.SetRange(this.MainRange);obj.Apply();})(this.MainRange.Worksheet.Sort);//留存排序规则对象,以备更多探索this.Sort = this.MainRange.AutoFilter.Sort;}//为数据表创建条形图AddChart() {let rngMain = this.MainRange;let sht = rngMain.Worksheet;//取得给定数据表底部单元格,以定位新建的图表let rngLocation = rngMain.Cells.Item(1).Offset(rngMain.Rows.Count + 2, 0);//在给定位置按给定大小创建【簇状条形图】let shape = sht.Shapes.AddChart(xlBarClustered,rngLocation.Left, rngLocation.Top,rngMain.Width, rngMain.Height * 2);let chart = shape.Chart;//将条形图的样式修改为 209chart.ChartStyle = 209;//设置引用的数据表chart.SetSourceData(rngMain, xlColumns);//Axes 方法返回坐标轴对象,无参时返回它们的集合//设置横坐标的最大刻度chart.Axes(xlValue).MaximumScale = 100;//设置纵坐标(分类轴)的分类,可以是 Range 对象let rngCategory = rngMain.Cells.Item(1).Offset(1, 0).Resize(rngMain.Rows.Count - 1, 1);chart.Axes(xlCategory).CategoryNames = rngCategory;//删除图表名为【年龄】的系列;SeriesCollection 方法无参调用//会返回当前图表对象的所有系列的集合chart.SeriesCollection('年龄').Delete();//设置图表的标题let rngTitle = this.TitleRange.Cells.Item(1);//必须先设置 HasTitle 属性为 true,才能设置标题文本chart.HasTitle = true;chart.ChartTitle.Text = rngTitle.Value2;}//为数据表创建透视表AddPivotTable() {let rngMain = this.MainRange;let sht = rngMain.Worksheet;//Create() 方法:为创建数据透视表创建缓冲对象let address = `=${sht.Name}!${rngMain.Address(true, true, xlR1C1)}`;let pivotCache = sht.Parent.PivotCaches().Create(xlDatabase, address, xlPivotTableVersion15)//在缓冲对象上创建数据透视表let rngLocation = rngMain.Cells.Item(1).Offset(rngMain.Rows.Count + 25, 0);address = `${sht.Name}!${rngLocation.Address(true, true, xlR1C1)}`;//这个调用必须使用 xlR1C1 样式的地址let pivotTable = pivotCache.CreatePivotTable(address, undefined, false, xlPivotTableVersion15);(obj=>{obj.Orientation = xlRowField;obj.Position = 1;})(pivotTable.PivotFields("性别"));(obj=>{obj.Orientation = xlRowField;obj.Position = 2;})(pivotTable.PivotFields("姓名"));pivotTable.AddDataField(pivotTable.PivotFields("语文"), undefined, undefined);pivotTable.AddDataField(pivotTable.PivotFields("数学"), undefined, undefined);pivotTable.AddDataField(pivotTable.PivotFields("外语"), undefined, undefined);//设置字段pivotTable.PivotFields("求和项:语文").Function = xlMax;pivotTable.PivotFields("最大值项:语文").Caption = "最大值项:语文";pivotTable.PivotFields("求和项:数学").Function = xlMax;pivotTable.PivotFields("最大值项:数学").Caption = "最大值项:数学";pivotTable.PivotFields("求和项:外语").Function = xlMax;pivotTable.PivotFields("最大值项:外语").Caption = "最大值项:外语";//创建计算型字段pivotTable.CalculatedFields().Add("总分", "= 语文+ 数学+ 外语", true);pivotTable.PivotFields("总分").Orientation = xlDataField;//将【姓名】字段按【总分】字段降序排序pivotTable.PivotFields("姓名").AutoSort(xlDescending, '求和项:总分');//添加切片器 :JSA 调用 API 创建切片器,代码可以无误执行,但无任何效果//新创建的 slicer 对象,仍然是初始状态,Name/Caption = default,//Top/Left/Width/Height = 1,赋值语句也可执行,但仍然无效rngLocation = rngLocation.Offset(0, pivotTable.PivotFields().Count - 2);let slicerCache = sht.Parent.SlicerCaches.Add(pivotTable, "性别");let slicer = slicerCache.Slicers.Add(sht, undefined, "性别","性别", rngLocation.Left, rngLocation.Top, 80, 100);//Console.WriteAll(slicer.Name, slicer.Caption);}//创建超链接AddHyperlinks() {let rngTable = this.TableRange;let sht = rngTable.Worksheet;//创建内部超链接let rngHeaders = rngTable.Rows.Item(2);let rngInnerLink = rngTable.Cells.Item(1).Offset(rngTable.Rows.Count, 0);let address = `'${sht.Name}'!${rngHeaders.Address(false, false)}`sht.Hyperlinks.Add(rngInnerLink, "",address, "内部位置超链接", "表头");//创建外部文件的超链接address = 'C:\\Windows\\System32\\cmd.exe';let rngFile = rngInnerLink.Offset(0, 1);sht.Hyperlinks.Add(rngFile, address, '','外部文件超链接', '文件');//创建网页超链接address = 'http://www.baidu.com';let rngUrl = rngFile.Offset(0, 1);sht.Hyperlinks.Add(rngUrl, address, '','外部网页超链接', '百度');//创建邮件超链接address = 'mailto:123456789@qq.com?subject=还好吗';let rngMail = rngUrl.Offset(0, 1);let lnk = sht.Hyperlinks.Add(rngMail,address, '', '邮件超链接', address);}//创建文本框AddTextBox() {let rngMain = this.MainRange;let rngLocation = rngMain.Cells.Item(1).Offset(0, rngMain.Columns.Count);let shape = ActiveSheet.Shapes.AddTextbox(msoTextOrientationVertical, rngLocation.Left,rngLocation.Top, rngLocation.Width, rngMain.Height);shape.Placement = xlMoveAndSize;let characters = shape.TextFrame.Characters();characters.Text = "文本框试验";characters.Font.Bold = true;characters.Font.Size = 15;}//添加标签AddLabel() {let rngMain = this.MainRange;let rngLocation = rngMain.Cells.Item(1).Offset(0, rngMain.Columns.Count + 1);let label = this.TopLeftCell.Worksheet.Shapes.AddLabel(msoTextOrientationVertical,rngLocation.Left, rngLocation.Top,rngLocation.Width, this.MainRange.Height);label.Placement = xlMoveAndSize;label.TextFrame.Characters().Text = "标签试验"}//添加直线AddLine() {let rngLocation = this.MainRange.Cells.Item(1).Offset(0, this.MainRange.Columns.Count + 2);let shape = this.TopLeftCell.Worksheet.Shapes.AddLine(rngLocation.Left, rngLocation.Top,rngLocation.Left + rngLocation.Width,rngLocation.Top + rngLocation.Height);shape.Placement = xlMoveAndSize;let line = shape.Line;line.DashStyle = msoLineDashDotDot;}static RunAll() {let rng = new Range('B1');let eg = new XLSExample(rng);eg.AddComment();eg.AddBorders();eg.SetFontAndAlignment();eg.AddValidation();eg.AddFormatCondition();eg.AddAutoFilter();eg.AddSortRule();eg.AddChart();eg.AddPivotTable();eg.AddHyperlinks();eg.AddTextBox();eg.AddLabel();eg.AddLine();}}
