第03章-Excel基础操作-工作簿与工作表
第三章:Excel基础操作-工作簿与工作表
3.1 工作簿(Workbook)操作
3.1.1 创建新工作簿
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using NPOI.HSSF.UserModel;
// 创建 .xlsx 格式工作簿(Excel 2007+)
IWorkbook xlsxWorkbook = new XSSFWorkbook();
// 创建 .xls 格式工作簿(Excel 97-2003)
IWorkbook xlsWorkbook = new HSSFWorkbook();
3.1.2 打开现有工作簿
// 方式一:通过文件流打开 .xlsx 文件
using (FileStream fs = new FileStream("example.xlsx", FileMode.Open, FileAccess.Read))
{
IWorkbook workbook = new XSSFWorkbook(fs);
// 进行操作...
}
// 方式二:通过文件流打开 .xls 文件
using (FileStream fs = new FileStream("example.xls", FileMode.Open, FileAccess.Read))
{
IWorkbook workbook = new HSSFWorkbook(fs);
// 进行操作...
}
// 方式三:自动识别格式
public static IWorkbook OpenWorkbook(string filePath)
{
string extension = Path.GetExtension(filePath).ToLower();
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return extension switch
{
".xlsx" => new XSSFWorkbook(fs),
".xls" => new HSSFWorkbook(fs),
_ => throw new NotSupportedException($"不支持的文件格式:{extension}")
};
}
3.1.3 保存工作簿
IWorkbook workbook = new XSSFWorkbook();
// ... 进行操作 ...
// 方式一:保存到新文件
using (FileStream fs = new FileStream("output.xlsx", FileMode.Create))
{
workbook.Write(fs);
}
// 方式二:保存到内存流(适用于Web下载)
using (MemoryStream ms = new MemoryStream())
{
workbook.Write(ms);
byte[] bytes = ms.ToArray();
// 可以用于HTTP响应返回
}
// 方式三:覆盖保存(注意:需要先读取完整内容)
public void SaveAndOverwrite(string filePath, Action<IWorkbook> operation)
{
IWorkbook workbook;
// 先读取
using (FileStream fsRead = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
workbook = new XSSFWorkbook(fsRead);
}
// 执行操作
operation(workbook);
// 再写入
using (FileStream fsWrite = new FileStream(filePath, FileMode.Create))
{
workbook.Write(fsWrite);
}
}
3.1.4 工作簿属性设置
// 对于 XSSFWorkbook,可以设置文档属性
XSSFWorkbook workbook = new XSSFWorkbook();
// 获取核心属性
var coreProperties = workbook.GetProperties().CoreProperties;
coreProperties.Title = "销售报表";
coreProperties.Creator = "张三";
coreProperties.Subject = "2024年度销售数据";
coreProperties.Description = "这是一份年度销售数据汇总报表";
coreProperties.Keywords = "销售,报表,2024";
coreProperties.Category = "财务报表";
// 设置自定义属性
var customProperties = workbook.GetProperties().CustomProperties;
customProperties.AddProperty("部门", "销售部");
customProperties.AddProperty("审核人", "李四");
3.1.5 工作簿格式转换
/// <summary>
/// 将 .xls 转换为 .xlsx 格式
/// </summary>
public static void ConvertXlsToXlsx(string xlsPath, string xlsxPath)
{
// 读取 .xls 文件
using FileStream fsRead = new FileStream(xlsPath, FileMode.Open, FileAccess.Read);
HSSFWorkbook xlsWorkbook = new HSSFWorkbook(fsRead);
// 创建新的 .xlsx 工作簿
XSSFWorkbook xlsxWorkbook = new XSSFWorkbook();
// 复制每个工作表
for (int i = 0; i < xlsWorkbook.NumberOfSheets; i++)
{
ISheet sourceSheet = xlsWorkbook.GetSheetAt(i);
ISheet targetSheet = xlsxWorkbook.CreateSheet(sourceSheet.SheetName);
CopySheet(sourceSheet, targetSheet, xlsxWorkbook);
}
// 保存
using FileStream fsWrite = new FileStream(xlsxPath, FileMode.Create);
xlsxWorkbook.Write(fsWrite);
}
/// <summary>
/// 复制工作表内容
/// </summary>
private static void CopySheet(ISheet source, ISheet target, IWorkbook targetWorkbook)
{
for (int i = source.FirstRowNum; i <= source.LastRowNum; i++)
{
IRow sourceRow = source.GetRow(i);
if (sourceRow == null) continue;
IRow targetRow = target.CreateRow(i);
targetRow.Height = sourceRow.Height;
for (int j = sourceRow.FirstCellNum; j < sourceRow.LastCellNum; j++)
{
ICell sourceCell = sourceRow.GetCell(j);
if (sourceCell == null) continue;
ICell targetCell = targetRow.CreateCell(j);
CopyCell(sourceCell, targetCell, targetWorkbook);
}
}
// 复制列宽
for (int i = 0; i < source.GetRow(source.FirstRowNum)?.LastCellNum; i++)
{
target.SetColumnWidth(i, source.GetColumnWidth(i));
}
}
/// <summary>
/// 复制单元格
/// </summary>
private static void CopyCell(ICell source, ICell target, IWorkbook targetWorkbook)
{
switch (source.CellType)
{
case CellType.String:
target.SetCellValue(source.StringCellValue);
break;
case CellType.Numeric:
target.SetCellValue(source.NumericCellValue);
break;
case CellType.Boolean:
target.SetCellValue(source.BooleanCellValue);
break;
case CellType.Formula:
target.SetCellFormula(source.CellFormula);
break;
}
}
3.2 工作表(Sheet)操作
3.2.1 创建工作表
IWorkbook workbook = new XSSFWorkbook();
// 创建默认名称的工作表
ISheet sheet1 = workbook.CreateSheet(); // 默认名称 "Sheet0"
// 创建指定名称的工作表
ISheet sheet2 = workbook.CreateSheet("销售数据");
ISheet sheet3 = workbook.CreateSheet("库存数据");
ISheet sheet4 = workbook.CreateSheet("员工信息");
// 注意:工作表名称限制
// - 最大31个字符
// - 不能包含 : \ / ? * [ ] 等特殊字符
// - 不能以单引号开头或结尾
// - 不能为空
3.2.2 获取工作表
// 通过索引获取
ISheet sheet = workbook.GetSheetAt(0);
// 通过名称获取
ISheet sheet2 = workbook.GetSheet("销售数据");
// 获取活动工作表(当前选中的工作表)
ISheet activeSheet = workbook.GetSheetAt(workbook.ActiveSheetIndex);
// 获取工作表数量
int sheetCount = workbook.NumberOfSheets;
// 获取工作表索引
int index = workbook.GetSheetIndex("销售数据");
// 遍历所有工作表
for (int i = 0; i < workbook.NumberOfSheets; i++)
{
ISheet s = workbook.GetSheetAt(i);
Console.WriteLine($"工作表{i}: {s.SheetName}");
}
3.2.3 工作表重命名与排序
// 重命名工作表
workbook.SetSheetName(0, "新名称");
// 移动工作表位置(设置工作表顺序)
workbook.SetSheetOrder("销售数据", 0); // 移到第一位
// 设置活动工作表
workbook.SetActiveSheet(1);
// 选中某个工作表
ISheet sheet = workbook.GetSheetAt(0);
sheet.IsSelected = true;
3.2.4 删除工作表
// 通过索引删除
workbook.RemoveSheetAt(0);
// 通过名称删除(需要先获取索引)
int indexToRemove = workbook.GetSheetIndex("临时数据");
if (indexToRemove >= 0)
{
workbook.RemoveSheetAt(indexToRemove);
}
// 安全删除方法
public static void SafeRemoveSheet(IWorkbook workbook, string sheetName)
{
int index = workbook.GetSheetIndex(sheetName);
if (index >= 0 && workbook.NumberOfSheets > 1) // 至少保留一个工作表
{
workbook.RemoveSheetAt(index);
}
}
3.2.5 复制工作表
// 克隆工作表(在同一工作簿内)
ISheet sourceSheet = workbook.GetSheetAt(0);
ISheet clonedSheet = workbook.CloneSheet(0);
workbook.SetSheetName(workbook.GetSheetIndex(clonedSheet), "复制的工作表");
// 跨工作簿复制工作表
public static void CopySheetToAnotherWorkbook(
IWorkbook sourceWorkbook, int sourceSheetIndex,
IWorkbook targetWorkbook, string targetSheetName)
{
ISheet sourceSheet = sourceWorkbook.GetSheetAt(sourceSheetIndex);
ISheet targetSheet = targetWorkbook.CreateSheet(targetSheetName);
// 复制行和单元格
for (int i = sourceSheet.FirstRowNum; i <= sourceSheet.LastRowNum; i++)
{
IRow sourceRow = sourceSheet.GetRow(i);
if (sourceRow == null) continue;
IRow targetRow = targetSheet.CreateRow(i);
for (int j = sourceRow.FirstCellNum; j < sourceRow.LastCellNum; j++)
{
ICell sourceCell = sourceRow.GetCell(j);
if (sourceCell == null) continue;
ICell targetCell = targetRow.CreateCell(j);
CopyCellValue(sourceCell, targetCell);
}
}
}
private static void CopyCellValue(ICell source, ICell target)
{
switch (source.CellType)
{
case CellType.Blank:
target.SetCellType(CellType.Blank);
break;
case CellType.Boolean:
target.SetCellValue(source.BooleanCellValue);
break;
case CellType.Numeric:
target.SetCellValue(source.NumericCellValue);
break;
case CellType.String:
target.SetCellValue(source.StringCellValue);
break;
case CellType.Formula:
target.SetCellFormula(source.CellFormula);
break;
case CellType.Error:
target.SetCellErrorValue(source.ErrorCellValue);
break;
}
}
3.2.6 工作表隐藏与显示
// 隐藏工作表
workbook.SetSheetHidden(1, SheetState.Hidden);
// 深度隐藏(用户无法通过Excel界面取消隐藏)
workbook.SetSheetHidden(1, SheetState.VeryHidden);
// 显示工作表
workbook.SetSheetHidden(1, SheetState.Visible);
// 检查工作表是否隐藏
bool isHidden = workbook.IsSheetHidden(1);
bool isVeryHidden = workbook.IsSheetVeryHidden(1);
3.3 行(Row)操作
3.3.1 创建和获取行
ISheet sheet = workbook.CreateSheet("数据");
// 创建行(索引从0开始)
IRow row0 = sheet.CreateRow(0); // 第1行
IRow row1 = sheet.CreateRow(1); // 第2行
// 获取已存在的行
IRow existingRow = sheet.GetRow(0); // 如果不存在返回null
// 获取或创建行
IRow row = sheet.GetRow(5) ?? sheet.CreateRow(5);
// 批量创建行
for (int i = 0; i < 100; i++)
{
IRow newRow = sheet.CreateRow(i);
}
3.3.2 行属性设置
IRow row = sheet.CreateRow(0);
// 设置行高(以点为单位,1点 = 1/72英寸)
row.Height = 400; // 约13.3点
// 设置行高(以点为单位)
row.HeightInPoints = 20f;
// 使用默认行高
row.Height = -1;
// 设置行样式(影响该行所有单元格的默认样式)
ICellStyle rowStyle = workbook.CreateCellStyle();
rowStyle.FillForegroundColor = IndexedColors.LightBlue.Index;
rowStyle.FillPattern = FillPattern.SolidForeground;
row.RowStyle = rowStyle;
// 隐藏行
row.ZeroHeight = true; // 隐藏
row.ZeroHeight = false; // 显示
// 检查行是否为空
bool isEmpty = row.FirstCellNum < 0;
3.3.3 行信息查询
// 获取工作表中的行数信息
int firstRowNum = sheet.FirstRowNum; // 第一个非空行索引
int lastRowNum = sheet.LastRowNum; // 最后一个非空行索引
int physicalRows = sheet.PhysicalNumberOfRows; // 物理行数(非空行数)
// 获取行中的单元格信息
short firstCellNum = row.FirstCellNum; // 第一个单元格索引
short lastCellNum = row.LastCellNum; // 最后一个单元格索引+1
int physicalCells = row.PhysicalNumberOfCells; // 物理单元格数
// 遍历行
foreach (ICell cell in row)
{
Console.WriteLine(cell.ToString());
}
3.3.4 插入和删除行
// 插入行(需要先移动下面的行)
public static void InsertRow(ISheet sheet, int rowIndex)
{
// 将rowIndex及以下的行向下移动一行
if (rowIndex <= sheet.LastRowNum)
{
sheet.ShiftRows(rowIndex, sheet.LastRowNum, 1);
}
// 创建新行
sheet.CreateRow(rowIndex);
}
// 删除行
public static void DeleteRow(ISheet sheet, int rowIndex)
{
IRow row = sheet.GetRow(rowIndex);
if (row != null)
{
sheet.RemoveRow(row);
// 将下面的行向上移动
if (rowIndex < sheet.LastRowNum)
{
sheet.ShiftRows(rowIndex + 1, sheet.LastRowNum, -1);
}
}
}
// 批量删除行
public static void DeleteRows(ISheet sheet, int startRow, int count)
{
for (int i = 0; i < count; i++)
{
IRow row = sheet.GetRow(startRow);
if (row != null)
{
sheet.RemoveRow(row);
}
}
// 移动剩余行
if (startRow + count <= sheet.LastRowNum)
{
sheet.ShiftRows(startRow + count, sheet.LastRowNum, -count);
}
}
3.4 列操作
3.4.1 列宽设置
// 设置列宽(单位为1/256字符宽度)
sheet.SetColumnWidth(0, 20 * 256); // 20个字符宽度
// 自动调整列宽
sheet.AutoSizeColumn(0);
// 自动调整列宽(考虑合并单元格)
sheet.AutoSizeColumn(0, true);
// 批量设置列宽
for (int i = 0; i < 10; i++)
{
sheet.SetColumnWidth(i, 15 * 256);
}
// 设置默认列宽
sheet.DefaultColumnWidth = 15;
// 获取列宽
int width = sheet.GetColumnWidth(0);
3.4.2 列隐藏与显示
// 隐藏列
sheet.SetColumnHidden(0, true);
// 显示列
sheet.SetColumnHidden(0, false);
// 检查列是否隐藏
bool isHidden = sheet.IsColumnHidden(0);
// 批量隐藏列
for (int i = 5; i < 10; i++)
{
sheet.SetColumnHidden(i, true);
}
3.4.3 列样式设置
// 设置列的默认样式
ICellStyle columnStyle = workbook.CreateCellStyle();
columnStyle.Alignment = HorizontalAlignment.Center;
sheet.SetDefaultColumnStyle(0, columnStyle);
// 设置整列格式(需要遍历所有行)
public static void SetColumnStyle(ISheet sheet, int columnIndex, ICellStyle style)
{
for (int i = sheet.FirstRowNum; i <= sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
if (row == null) continue;
ICell cell = row.GetCell(columnIndex);
if (cell != null)
{
cell.CellStyle = style;
}
}
}
3.5 视图设置
3.5.1 冻结窗格
// 冻结首行
sheet.CreateFreezePane(0, 1);
// 冻结首列
sheet.CreateFreezePane(1, 0);
// 冻结首行和首列
sheet.CreateFreezePane(1, 1);
// 冻结前2行和前3列
sheet.CreateFreezePane(3, 2);
// 带滚动位置的冻结
// 参数:冻结列数, 冻结行数, 右侧首列位置, 下方首行位置
sheet.CreateFreezePane(2, 3, 2, 3);
3.5.2 拆分窗格
// 水平拆分(在2000twip处拆分)
sheet.CreateSplitPane(0, 2000, 0, 1, PanePosition.LowerLeft);
// 垂直拆分
sheet.CreateSplitPane(2000, 0, 1, 0, PanePosition.UpperRight);
// 同时水平和垂直拆分
sheet.CreateSplitPane(2000, 2000, 1, 1, PanePosition.LowerRight);
3.5.3 显示设置
// 显示/隐藏网格线
sheet.DisplayGridlines = false;
// 显示/隐藏行列标题
sheet.DisplayRowColHeadings = false;
// 设置缩放比例(百分比)
sheet.SetZoom(150); // 150%
// 打印设置
IPrintSetup printSetup = sheet.PrintSetup;
printSetup.Landscape = true; // 横向打印
printSetup.FitWidth = 1; // 适合1页宽
printSetup.FitHeight = 0; // 高度不限
printSetup.PaperSize = (short)PaperSize.A4;
// 设置打印区域
workbook.SetPrintArea(0, 0, 10, 0, 20); // Sheet索引, 起始列, 结束列, 起始行, 结束行
// 设置重复打印行(表头)
sheet.RepeatingRows = new CellRangeAddress(0, 0, 0, 10); // 第1行
3.6 综合示例
3.6.1 创建带多个工作表的报表
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.IO;
public class MultiSheetReportExample
{
public static void CreateSalesReport()
{
IWorkbook workbook = new XSSFWorkbook();
// 创建汇总表
CreateSummarySheet(workbook);
// 创建各月份明细表
string[] months = { "一月", "二月", "三月" };
foreach (var month in months)
{
CreateMonthDetailSheet(workbook, month);
}
// 设置第一个工作表为活动工作表
workbook.SetActiveSheet(0);
// 保存文件
using (FileStream fs = new FileStream("销售报表.xlsx", FileMode.Create))
{
workbook.Write(fs);
}
Console.WriteLine("报表创建成功!");
}
private static void CreateSummarySheet(IWorkbook workbook)
{
ISheet sheet = workbook.CreateSheet("汇总");
// 创建表头样式
ICellStyle headerStyle = workbook.CreateCellStyle();
IFont font = workbook.CreateFont();
font.IsBold = true;
font.FontHeightInPoints = 14;
headerStyle.SetFont(font);
headerStyle.Alignment = HorizontalAlignment.Center;
headerStyle.FillForegroundColor = IndexedColors.LightBlue.Index;
headerStyle.FillPattern = FillPattern.SolidForeground;
// 创建表头
IRow headerRow = sheet.CreateRow(0);
string[] headers = { "月份", "销售额", "成本", "利润", "增长率" };
for (int i = 0; i < headers.Length; i++)
{
ICell cell = headerRow.CreateCell(i);
cell.SetCellValue(headers[i]);
cell.CellStyle = headerStyle;
}
// 创建数据
object[][] data = {
new object[] { "一月", 100000, 60000, 40000, "10%" },
new object[] { "二月", 120000, 70000, 50000, "25%" },
new object[] { "三月", 150000, 85000, 65000, "30%" }
};
for (int i = 0; i < data.Length; i++)
{
IRow row = sheet.CreateRow(i + 1);
for (int j = 0; j < data[i].Length; j++)
{
ICell cell = row.CreateCell(j);
if (data[i][j] is int num)
cell.SetCellValue(num);
else
cell.SetCellValue(data[i][j].ToString());
}
}
// 自动调整列宽
for (int i = 0; i < headers.Length; i++)
{
sheet.AutoSizeColumn(i);
}
// 冻结首行
sheet.CreateFreezePane(0, 1);
}
private static void CreateMonthDetailSheet(IWorkbook workbook, string month)
{
ISheet sheet = workbook.CreateSheet($"{month}明细");
// 创建表头
IRow headerRow = sheet.CreateRow(0);
headerRow.CreateCell(0).SetCellValue("日期");
headerRow.CreateCell(1).SetCellValue("产品");
headerRow.CreateCell(2).SetCellValue("数量");
headerRow.CreateCell(3).SetCellValue("金额");
// 创建示例数据
Random rand = new Random();
for (int i = 1; i <= 10; i++)
{
IRow row = sheet.CreateRow(i);
row.CreateCell(0).SetCellValue($"{month}{i}日");
row.CreateCell(1).SetCellValue($"产品{rand.Next(1, 5)}");
row.CreateCell(2).SetCellValue(rand.Next(1, 100));
row.CreateCell(3).SetCellValue(rand.Next(100, 10000));
}
// 设置列宽
sheet.SetColumnWidth(0, 15 * 256);
sheet.SetColumnWidth(1, 15 * 256);
sheet.SetColumnWidth(2, 10 * 256);
sheet.SetColumnWidth(3, 15 * 256);
}
}
3.6.2 工作表管理器类
using NPOI.SS.UserModel;
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 工作表管理器
/// </summary>
public class SheetManager
{
private readonly IWorkbook _workbook;
public SheetManager(IWorkbook workbook)
{
_workbook = workbook ?? throw new ArgumentNullException(nameof(workbook));
}
/// <summary>
/// 获取所有工作表名称
/// </summary>
public List<string> GetAllSheetNames()
{
var names = new List<string>();
for (int i = 0; i < _workbook.NumberOfSheets; i++)
{
names.Add(_workbook.GetSheetName(i));
}
return names;
}
/// <summary>
/// 检查工作表是否存在
/// </summary>
public bool SheetExists(string sheetName)
{
return _workbook.GetSheetIndex(sheetName) >= 0;
}
/// <summary>
/// 获取或创建工作表
/// </summary>
public ISheet GetOrCreateSheet(string sheetName)
{
ISheet sheet = _workbook.GetSheet(sheetName);
return sheet ?? _workbook.CreateSheet(sheetName);
}
/// <summary>
/// 安全重命名工作表
/// </summary>
public bool RenameSheet(string oldName, string newName)
{
int index = _workbook.GetSheetIndex(oldName);
if (index < 0) return false;
if (SheetExists(newName)) return false;
_workbook.SetSheetName(index, newName);
return true;
}
/// <summary>
/// 复制工作表
/// </summary>
public ISheet CopySheet(string sourceName, string targetName)
{
int sourceIndex = _workbook.GetSheetIndex(sourceName);
if (sourceIndex < 0)
throw new ArgumentException($"工作表 '{sourceName}' 不存在");
if (SheetExists(targetName))
throw new ArgumentException($"工作表 '{targetName}' 已存在");
ISheet cloned = _workbook.CloneSheet(sourceIndex);
_workbook.SetSheetName(_workbook.GetSheetIndex(cloned), targetName);
return cloned;
}
/// <summary>
/// 删除所有空工作表
/// </summary>
public int RemoveEmptySheets()
{
int removedCount = 0;
var emptyIndexes = new List<int>();
for (int i = 0; i < _workbook.NumberOfSheets; i++)
{
ISheet sheet = _workbook.GetSheetAt(i);
if (IsSheetEmpty(sheet))
{
emptyIndexes.Add(i);
}
}
// 从后向前删除,避免索引变化问题
for (int i = emptyIndexes.Count - 1; i >= 0; i--)
{
if (_workbook.NumberOfSheets > 1) // 保留至少一个工作表
{
_workbook.RemoveSheetAt(emptyIndexes[i]);
removedCount++;
}
}
return removedCount;
}
/// <summary>
/// 检查工作表是否为空
/// </summary>
private bool IsSheetEmpty(ISheet sheet)
{
return sheet.PhysicalNumberOfRows == 0;
}
/// <summary>
/// 获取工作表统计信息
/// </summary>
public Dictionary<string, SheetStatistics> GetStatistics()
{
var stats = new Dictionary<string, SheetStatistics>();
for (int i = 0; i < _workbook.NumberOfSheets; i++)
{
ISheet sheet = _workbook.GetSheetAt(i);
stats[sheet.SheetName] = new SheetStatistics
{
RowCount = sheet.LastRowNum - sheet.FirstRowNum + 1,
PhysicalRowCount = sheet.PhysicalNumberOfRows,
FirstRowIndex = sheet.FirstRowNum,
LastRowIndex = sheet.LastRowNum,
IsHidden = _workbook.IsSheetHidden(i) || _workbook.IsSheetVeryHidden(i)
};
}
return stats;
}
}
public class SheetStatistics
{
public int RowCount { get; set; }
public int PhysicalRowCount { get; set; }
public int FirstRowIndex { get; set; }
public int LastRowIndex { get; set; }
public bool IsHidden { get; set; }
}
3.7 本章小结
本章详细介绍了NPOI中工作簿和工作表的基础操作。通过本章学习,你应该掌握:
- 工作簿的创建、打开、保存和格式转换
- 工作表的创建、获取、重命名、复制和删除
- 行的创建、属性设置、插入和删除
- 列的宽度设置、隐藏和样式设置
- 视图设置如冻结窗格、拆分窗格等
- 打印设置的基本配置
这些基础操作是后续进行复杂Excel处理的基础。在下一章中,我们将学习单元格的详细操作和数据类型处理。
下一章预告:第四章将介绍Excel单元格操作与数据类型,包括各种数据类型的读写、单元格引用和单元格区域操作。

浙公网安备 33010602011771号