当前位置:首页 > 技术 > 正文内容

在C# WinForm中通过NPOI处理Excel文件中的图片数据

访客 技术 2026年7月4日 2

需求:在WinForm应用中实现Excel数据导入功能,同时处理嵌入的图片资源。

虽然代码实现方式可能不同(我未深入研究具体实现),但参考了相关技术思路获取灵感http://www.wjhsh.net/IT-Ramon-p-13100039.html,通过修改文件后缀为Zip进行解压,解析XML文件中的图片位置信息及关联数据

示例效果:

1、关键处理逻辑

        DataTable dataResult;
        private void ImportButton_Click(object sender, EventArgs e)
        {
            dataResult = null;
            string tempFileName = DateTime.Now.ToString("yyyyMMddHHmmss");
            string zipSuffix = ".zip";
            string cachePath = Path.Combine(Environment.CurrentDirectory, "Temp", "Cache");
            if (!Directory.Exists(cachePath)) Directory.CreateDirectory(cachePath);
            string zipFilePath = Path.Combine(cachePath, tempFileName + zipSuffix);
            string extractPath = Path.Combine(cachePath, tempFileName);

            OpenFileDialog fileDialog = new OpenFileDialog();
            fileDialog.Filter = "Excel文件(*.xls,*.xlsx)|*.xls;*.xlsx";
            fileDialog.FilterIndex = 0;
            fileDialog.RestoreDirectory = true;
            fileDialog.Title = "选择要导入的文件";
            //fileDialog.ShowDialog();
            if (fileDialog.ShowDialog() != DialogResult.OK) return;

            Common.ShowWaitForm();
            FileInfo sourceFile = new FileInfo(fileDialog.FileName);
            sourceFile.CopyTo(zipFilePath);

            try
            {
                ImageMetadata imageMeta = null;
                if (!Directory.Exists(extractPath)) Directory.CreateDirectory(extractPath);
                if (!ZipExtractor.Extract(zipFilePath, extractPath)) 
                {
                    Common.ShowErrorDialog("解压失败!");
                    LogHelper.WriteError("导入异常,解压操作失败:" + extractPath);
                    return;
                }
                else
                {
                    imageMeta = ExtractImagePaths(extractPath);
                }

                string errorMsg = "";
                dataResult = ExcelProcessor.ReadExcelData(fileDialog.FileName, imageMeta, ref errorMsg);
                if (!string.IsNullOrEmpty(errorMsg)) Common.ShowInfoDialog(errorMsg);
                if (dataResult == null || dataResult.Rows.Count <= 0)
                {
                    Common.ShowSuccessTip("未找到有效数据!");
                    return;
                }
                RefreshDataGrid();
            }
            catch (Exception ex)
            {
                Common.ShowErrorDialog("处理异常:" + ex.Message);
            }
            finally
            {
                Common.HideWaitForm();
            }
        }

        private ImageMetadata ExtractImagePaths(string extractDir)
        {
            string xmlPath = Path.Combine(extractDir, "xl", "drawings", "drawing1.xml");
            var xmlDoc = XDocument.Load(xmlPath);
            xmlDoc.Descendants().Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
            foreach (var node in xmlDoc.Descendants()) node.Name = node.Name.LocalName;
            xmlDoc.Save(xmlPath);
            
            ImageMetadata metadata = XmlSerializer.Deserialize<ImageMetadata>(xmlPath);
            ImageRelationships relations = ExtractImageReferences(extractDir);

            if (metadata.AnchorItems.Count > 0)
            {
                foreach (var item in metadata.AnchorItems)
                {
                    item.ImageData = relations.Relationships
                        .FirstOrDefault(r => r.Id == item.BlipData.ImageId)?.Image;
                    item.ImagePath = relations.Relationships
                        .FirstOrDefault(r => r.Id == item.BlipData.ImageId)?.ImagePath;
                }
            }
            return metadata;
        }

        private ImageRelationships ExtractImageReferences(string extractDir)
        {
            string relsPath = Path.Combine(extractDir, "xl", "drawings", "_rels", "drawing1.xml.rels");
            FileInfo file = new FileInfo(relsPath);
            string newRelsPath = Path.Combine(extractDir, "xl", "drawings", "drawing1.xml");

            if (file.Exists) file.MoveTo(newRelsPath);

            var xmlDoc = XDocument.Load(newRelsPath);
            xmlDoc.Descendants().Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
            foreach (var node in xmlDoc.Descendants()) node.Name = node.Name.LocalName;
            xmlDoc.Save(newRelsPath);

            ImageRelationships relationships = XmlSerializer.Deserialize<ImageRelationships>(newRelsPath);
            string drawingXmlPath = Path.Combine(extractDir, "xl", "drawings", "drawing1.xml");

            foreach (var rel in relationships.Relationships)
            {
                string fullPath = ResolvePath(drawingXmlPath, rel.Target);
                rel.ImagePath = fullPath;
                using (var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read))
                {
                    rel.Image = Image.FromStream(stream);
                }
            }
            return relationships;
        }

        private string ResolvePath(string baseDir, string relativePath)
        {
            string[] segments = relativePath.Split('/');
            string currentPath = baseDir;
            foreach (var segment in segments)
            {
                if (segment == "..")
                {
                    currentPath = Path.GetDirectoryName(currentPath);
                }
                else
                {
                    currentPath = Path.Combine(currentPath, segment);
                }
            }
            return currentPath;
        }

2、XML解析实体类定义

  #region 数据模型
    [Serializable]
    public class ImageMetadata
    {
        [XmlElement("twoCellAnchor")]
        public List<AnchorItem> AnchorItems { get; set; }
    }

    public class ImageRelationships
    {
        [XmlElement("Relationship")]
        public List<ImageReference> Relationships { get; set; }
    }
    #endregion
    #region 子级结构
    public class AnchorItem
    {
        [XmlElement("from")]
        public Position StartPos { get; set; }
        [XmlElement("to")]
        public Position EndPos { get; set; }
        [XmlElement("pic")]
        public ImageData ImageData { get; set; }
    }

    public class ImageData
    {
        [XmlElement("nvPicPr")]
        public ImageProperties Properties { get; set; }
        [XmlElement("blipFill")]
        public BlipData BlipData { get; set; }
    }

    public class ImageProperties
    {
        [XmlAttribute("id")]
        public string ImageId { get; set; }
        [XmlAttribute("name")]
        public string ImageName { get; set; }
        public Image Image { get; set; }
        public string ImagePath { get; set; }
    }

    public class BlipData
    {
        [XmlElement("blip")]
        public ImageReference Reference { get; set; }
    }

    public class ImageReference
    {
        [XmlAttribute("embed", Namespace = "http://schemas.openxmlformats.org/officeDocument/2006/relationships")]
        public string ImageId { get; set; }
    }

    public class Position
    {
        [XmlAttribute("col")]
        public int Column { get; set; }
        [XmlAttribute("row")]
        public int Row { get; set; }
    }
    #endregion

3、NPOI数据读取实现

private static ISheet ReadExcelSheet(string filePath)
        {
            IWorkbook workbook;
            string extension = Path.GetExtension(filePath).ToLower();
            using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                if (extension == ".xlsx") workbook = new XSSFWorkbook(stream);
                else if (extension == ".xls") workbook = new HSSFWorkbook(stream);
                else return null;

                return workbook.GetSheetAt(0);
            }
        }

        public static DataTable ReadExcelData(string filePath, ImageMetadata imageMeta, ref string errorMsg)
        {
            try
            {
                ISheet sheet = ReadExcelSheet(filePath);
                DataTable resultTable = new DataTable();
                resultTable.Columns.Add("ID", typeof(string));
                resultTable.Columns.Add("InternalCode", typeof(string));
                resultTable.Columns.Add("ExternalCode", typeof(string));
                resultTable.Columns.Add("ChineseName", typeof(string));
                resultTable.Columns.Add("EnglishName", typeof(string));
                resultTable.Columns.Add("ProductType", typeof(string));
                resultTable.Columns.Add("Unit", typeof(string));
                resultTable.Columns.Add("IsPurchased", typeof(string));
                resultTable.Columns.Add("IsTemporary", typeof(string));
                resultTable.Columns.Add("Price", typeof(decimal));
                resultTable.Columns.Add("Image", typeof(Image));
                resultTable.Columns.Add("Dimensions", typeof(string));
                resultTable.Columns.Add("Weight", typeof(string));
                resultTable.Columns.Add("ImagePath", typeof(string));

                for (int rowIndex = 1; rowIndex <= sheet.LastRowNum; rowIndex++)
                {
                    DataRow newRow = resultTable.NewRow();
                    IRow row = sheet.GetRow(rowIndex);
                    if (row == null) continue;

                    string internalCode = GetCellValue(row.GetCell(0));
                    if (string.IsNullOrEmpty(internalCode))
                    {
                        errorMsg += $"第{rowIndex}行【内部编码】不能为空!";
                        break;
                    }
                    newRow["InternalCode"] = internalCode;

                    string externalCode = GetCellValue(row.GetCell(1));
                    if (string.IsNullOrEmpty(externalCode))
                    {
                        errorMsg += $"第{rowIndex}行【外部编码】不能为空!";
                        break;
                    }
                    newRow["ExternalCode"] = externalCode;

                    // ...其他字段校验逻辑省略...

                    if (imageMeta != null && imageMeta.AnchorItems.Count > 0)
                    {
                        var matchingImages = imageMeta.AnchorItems
                            .Where(item => rowIndex >= item.StartPos.Row && 
                                         9 >= item.StartPos.Column && 
                                         rowIndex <= item.EndPos.Row && 
                                         9 <= item.EndPos.Column)
                            .ToList();

                        if (matchingImages.Count > 0)
                        {
                            newRow["Image"] = matchingImages[0].ImageData.Properties.Image;
                            newRow["ImagePath"] = matchingImages[0].ImageData.Properties.ImagePath;
                        }
                    }

                    resultTable.Rows.Add(newRow);
                }
                return resultTable;
            }
            catch (Exception ex)
            {
                LogHelper.WriteError(ex.ToString());
                throw;
            }
        }

4、数据持久化处理

  private void SaveButton_Click(object sender, EventArgs e)
        {
            List<ProductModel> productList = new List<ProductModel>();
            foreach (DataRow row in dataResult.Rows)
            {
                ProductModel model = new ProductModel();
                // 数据映射逻辑
                productList.Add(model);
            }

            if (productList.Count == 0)
            {
                Common.ShowInfoTip("未检测到可保存数据");
                return;
            }

            LoadingForm.Show(this, () => UploadProcess(productList), () => this.Close());
        }

        private void UploadProcess(List<ProductModel> products)
        {
            List<DatabaseCommand> commands = new List<DatabaseCommand>();
            foreach (var product in products)
            {
                product.ImagePath = FileUploader.UploadFile(product.ImagePath);
                commands.Add(ProductBusiness.AddCommand(product));
            }

            if (DatabaseTransaction.Execute(commands))
            {
                Common.ShowSuccessTip("数据导入完成");
                dataResult = null;
            }
            else
            {
                Common.ShowErrorDialog("数据保存失败");
            }
        }

操作流程:

将Excel文件后缀修改为.zip并解压,得到包含以下结构的文件夹:

其中xl/drawings/drawing1.xml文件记录了图片位置信息和标识符,对应xl/drawings/_rels/drawing1.xml.rels文件中的路径映射关系:

通过解析路径定位到xl/media/image1.png文件:

标签: C# WinForm

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。