You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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;
    }
}

新手必看注意事项

  1. Interop资源清理:一定要在finally块中释放Excel对象并退出,否则会在后台残留Excel进程。
  2. 权限问题:确保程序有文件读写权限,尤其是配置文件和Excel文件的路径。
  3. 配置扩展性:可以给配置类添加TemplateType字段,区分不同类型的Excel模板,方便批量处理同类型文件。
  4. 异常处理:实际使用时要添加更多异常捕获,比如文件不存在、Excel版本不兼容、单元格格式异常等。

内容的提问来源于stack exchange,提问作者wyntermvre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:18:13