如何通过C#程序读取已打开Excel文件中用户选中的单元格?
交互式Excel字段配置与数据提取实现方案
针对你遇到的不同Excel布局数据定位问题,下面提供两种C#实现思路,从交互式配置到配合OpenXML提取数据的完整流程,适合新手上手:
一、用Excel Interop实现可视化单元格选择
这种方式直接调用本地Excel应用,让用户直观选择目标字段位置,适合有Excel环境的场景:
步骤1:安装依赖
右键项目 → 管理NuGet程序包,搜索安装Microsoft.Office.Interop.Excel和Newtonsoft.Json(用来序列化配置)。
步骤2:核心实现代码
using Microsoft.Office.Interop.Excel; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; class ExcelFieldConfigTool { static void Main(string[] args) { Application excelApp = null; try { // 启动Excel并显示界面 excelApp = new Application { Visible = true }; // 打开目标Excel文件(替换为你的文件路径) Workbook workbook = excelApp.Workbooks.Open(@"D:\Test\SourceData.xlsx"); Worksheet activeSheet = workbook.ActiveSheet; // 提示用户操作 Console.WriteLine("请在Excel中选中对应字段的表头单元格(如「姓名」列的表头),选好后按回车..."); Console.ReadLine(); // 获取用户选中的区域 Range selectedRange = excelApp.Selection; if (selectedRange == null || selectedRange.Cells.Count > 1) { Console.WriteLine("请仅选择单个表头单元格!"); return; } // 提取配置信息:列号、字段名、数据起始行(表头下一行) int columnIndex = selectedRange.Column; string fieldName = selectedRange.Value?.ToString() ?? "未命名字段"; int dataStartRow = selectedRange.Row + 1; // 构建配置对象 var fieldConfig = new FieldConfig { FieldName = fieldName, ColumnIndex = columnIndex, DataStartRow = dataStartRow, SourceFilePath = workbook.FullName }; // 保存配置到JSON文件(支持多字段配置) string configFilePath = @"D:\Test\ExcelFieldConfigs.json"; List<FieldConfig> configList = new List<FieldConfig>(); if (File.Exists(configFilePath)) { string existingConfig = File.ReadAllText(configFilePath); configList = JsonConvert.DeserializeObject<List<FieldConfig>>(existingConfig); } // 避免重复添加同文件同字段的配置 if (!configList.Any(c => c.SourceFilePath == fieldConfig.SourceFilePath && c.FieldName == fieldConfig.FieldName)) { configList.Add(fieldConfig); File.WriteAllText(configFilePath, JsonConvert.SerializeObject(configList, Formatting.Indented)); Console.WriteLine($"配置已保存:{fieldName} → 第{columnIndex}列"); } else { Console.WriteLine("该字段配置已存在!"); } } catch (Exception ex) { Console.WriteLine($"操作出错:{ex.Message}"); } finally { // 清理Excel进程,避免残留 if (excelApp != null) { excelApp.Quit(); Marshal.ReleaseComObject(excelApp); } } } } // 配置实体类,用于序列化存储 public class FieldConfig { public string FieldName { get; set; } public int ColumnIndex { get; set; } public int DataStartRow { get; set; } public string SourceFilePath { get; set; } }
二、无Excel依赖的配置方案(EPPlus + WinForm)
如果用户机器没有安装Excel,可以用EPPlus读取Excel内容,配合WinForm界面让用户选择字段:
步骤1:安装依赖
NuGet安装EPPlus(注意5.x版本需要设置非商用授权)和System.Windows.Forms(控制台项目需手动添加引用)。
步骤2:WinForm核心代码片段
using OfficeOpenXml; using System; using System.IO; using System.Windows.Forms; using Newtonsoft.Json; using System.Collections.Generic; public partial class ExcelConfigForm : Form { public ExcelConfigForm() { InitializeComponent(); } private void btnLoadExcel_Click(object sender, EventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog { Filter = "Excel文件 (*.xlsx)|*.xlsx|Excel 97-2003文件 (*.xls)|*.xls", Title = "选择要配置的Excel文件" }; if (openFileDialog.ShowDialog() != DialogResult.OK) return; // 设置EPPlus非商用授权 ExcelPackage.LicenseContext = LicenseContext.NonCommercial; using (ExcelPackage package = new ExcelPackage(new FileInfo(openFileDialog.FileName))) { ExcelWorksheet worksheet = package.Workbook.Worksheets[0]; // 加载表头到DataGridView dgvExcelColumns.Columns.Clear(); for (int col = 1; col <= worksheet.Dimension.End.Column; col++) { string headerText = worksheet.Cells[1, col].Text; dgvExcelColumns.Columns.Add(col.ToString(), headerText); } txtFilePath.Text = openFileDialog.FileName; } } private void btnSaveConfig_Click(object sender, EventArgs e) { if (dgvExcelColumns.SelectedColumns.Count == 0) { MessageBox.Show("请先选择一个字段列!"); return; } int columnIndex = int.Parse(dgvExcelColumns.SelectedColumns[0].Name); string fieldName = dgvExcelColumns.SelectedColumns[0].HeaderText; var fieldConfig = new FieldConfig { FieldName = fieldName, ColumnIndex = columnIndex, DataStartRow = 2, // 默认表头在第1行,数据从第2行开始 SourceFilePath = txtFilePath.Text }; // 保存配置逻辑 string configPath = @"D:\Test\ExcelFieldConfigs.json"; List<FieldConfig> configList = new List<FieldConfig>(); if (File.Exists(configPath)) { configList = JsonConvert.DeserializeObject<List<FieldConfig>>(File.ReadAllText(configPath)); } configList.Add(fieldConfig); File.WriteAllText(configPath, JsonConvert.SerializeObject(configList, Formatting.Indented)); MessageBox.Show("配置保存成功!"); } }
三、配合OpenXML使用配置提取数据
有了配置文件后,直接读取配置定位数据,解决之前的布局差异问题:
using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; class DataExtractor { static void Main(string[] args) { // 读取配置文件 string configPath = @"D:\Test\ExcelFieldConfigs.json"; List<FieldConfig> configList = JsonConvert.DeserializeObject<List<FieldConfig>>(File.ReadAllText(configPath)); // 找到「姓名」字段的配置 var nameConfig = configList.Find(c => c.FieldName == "姓名"); if (nameConfig == null) { Console.WriteLine("未找到「姓名」字段的配置!"); return; } // 用OpenXML读取数据 using (SpreadsheetDocument spreadsheetDoc = SpreadsheetDocument.Open(nameConfig.SourceFilePath, false)) { WorkbookPart workbookPart = spreadsheetDoc.WorkbookPart; WorksheetPart worksheetPart = workbookPart.WorksheetParts.First(); SheetData sheetData = worksheetPart.Worksheet.Elements<SheetData>().First(); // 从配置的起始行开始遍历数据行 for (int rowNum = nameConfig.DataStartRow; ; rowNum++) { Row row = sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex == rowNum); if (row == null) break; // 没有更多数据行 // 根据配置的列号获取单元格 Cell targetCell = row.Elements<Cell>().FirstOrDefault(c => GetColumnIndex(c.CellReference) == nameConfig.ColumnIndex); string cellValue = GetCellValue(spreadsheetDoc, targetCell); Console.WriteLine($"姓名:{cellValue}"); } } } // 辅助方法:将单元格引用(如A1)转换为列索引 private static int GetColumnIndex(string cellReference) { int index = 0; foreach (char c in cellReference.Where(char.IsLetter)) { index = index * 26 + (c - 'A' + 1); } return index; } // 辅助方法:获取单元格的实际值(处理共享字符串) private static string GetCellValue(SpreadsheetDocument doc, Cell cell) { if (cell == null || cell.CellValue == null) return string.Empty; string value = cell.CellValue.Text; if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString) { SharedStringTablePart stringTablePart = doc.WorkbookPart.SharedStringTablePart; value = stringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(int.Parse(value)).InnerText; } return value; } }
新手必看注意事项
- Interop资源清理:一定要在finally块中释放Excel对象并退出,否则会在后台残留Excel进程。
- 权限问题:确保程序有文件读写权限,尤其是配置文件和Excel文件的路径。
- 配置扩展性:可以给配置类添加
TemplateType字段,区分不同类型的Excel模板,方便批量处理同类型文件。 - 异常处理:实际使用时要添加更多异常捕获,比如文件不存在、Excel版本不兼容、单元格格式异常等。
内容的提问来源于stack exchange,提问作者wyntermvre
相关产品推荐
相关产品推荐

