C#实现数据导出任一Word图表的通用呈现方法及一些体会

简介: C#实现数据导出任一Word图表的通用呈现方法及一些体会

疲惫的修改

应人才测评产品的需求,导出测评报告是其中一个重要的环节,报告的文件类型也多种多样,其中WORD输出也扮演了一个重要的角色。

实现方法比较简单,结合分析结果数据,通过WORD模板文件进行替换输出。在实现的过程中,图表的设计是必不可少的,根据初次产品的设计方案,图表采用微软Chart图表控件进行开发,采用雷达图进行呈现。使用该控件首先要引入 System.Web.DataVisualization.dll 程序集,通过定义 System.Web.UI.DataVisualization.Charting.Chart 类来实现,本来采用该开发方案的初衷是觉得都是微软的技术,图表的呈现类型也比较丰富,可在实际的开发中,情况没有想像的那么顺利,提供的技术文档非常有限,各种百度也是凤毛麟角,经过努力与探索,最终还是实现了需求。

但后来由于种种原因,图表要求采用饼状3D图进行呈现,虽然已经有了第一次的经验,但细节的变化,不得不再次进行探索和学习,可当需求再次改变的时候,我决定游说产品设计和改变设计思路。

新的思路

由于引入 Microsoft.Office.Interop.Word 程序集进行开发,因此在Word上的所有操作都能用程序去实现,其内置的图表功能也不例外,通过演练和内部讨论,图形化的呈现基本能够满足需求。

通用性

举例,我们在Word中插入一个图表并选择雷达图,如下图:

插入后,我们看到 Word 会自动弹出一个微缩版的 Excel 应用,改变其中的项和系列值,图表就会对应的产生变化。  

我们右击雷达图,选择更改图表类型为饼图,如下图:

可以看到饼图按照EXCEL数据中的系列1数据进行呈现,也不会因为系列2的数据存在而出现错误。由此可以分析出,控制好这个 Excel 的数据应用即可按照我们的设计实现任一图表的输出。

设计方案

(1)负责具体业务的应用程序,输出后的数据,存入一个二维字符串数组里,模拟 Excel 数据存储模式。

(2)考虑未来的扩展性,将二维数组转化为Json数据格式,并添加一个查找关键字节点,假设为“ t:chart1”。

(3)在 Word 模板设计图表,图表的标题设置为Json对应的查找关键字,即“ t:chart1”。

(4)编写数据导出EXCEL方法,传递JSON字符串参数,读取Word模板文件,遍历模板文件中的图表对象,并按查找关键字与图表的标题进行对比,匹配成功,则将JSON中数组转化为图表需要的EXCEL数组形式,到此输出完毕。

为什么用 Json 过渡

我们的云架构里设计了一个 Office 计算中心,在某些环境下,比如 Linux 中需要这种方式传递并返回值,以达到导入导出Office文件的目的。所以大家要根据实际的应用进行设计,这里仅作为参考。

关键代码实现

开发环境

操作系统:Windows Server 2019 DataCenter

开发工具:VisualStudio2019

框架及语言:.net 4.7.1     C#

服务上需要安装 Office 2016或以上

现在开始!

在此我们以最易懂的代码形式举例,假设文件模板中的图表为条状图,关键查找字(图表标题)设为 “ t:chart1”,如下图:

(1)创建二维数组

            //定义二维字符串数组,第一列为项目名称,第二列为值
            string[,] chart1 = new string[11, 2];
            chart1[0, 0] = "项";
            chart1[0, 1] = "值";
            chart1[1, 0] = "全局观";
            chart1[2, 0] = "影响力";
            chart1[3, 0] = "公正性";
            chart1[4, 0] = "果敢性";
            chart1[5, 0] = "执行力";
            chart1[6, 0] = "人际理解";
            chart1[7, 0] = "成就意识";
            chart1[8, 0] = "创新意识";
            chart1[9, 0] = "情绪控制";
            chart1[10, 0] = "学习发展";
            Random rnd = new Random();
            for (int si = 1; si <= 10; si++)
            {
                chart1[si, 0] = rnd.NextDouble().ToString(); //循环赋值随机浮点数
            }

(2)二维数组转Json格式

这里引入 Newtonsoft.Json.dll 程序集进行操作,代码如下:

    StringWriter sw = new StringWriter();   //using System.IO
    using (Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter(sw))
            {
                writer.Formatting = Newtonsoft.Json.Formatting.Indented;
                writer.WriteStartObject();
                //t:chart1  转化数组chart1 为 json 对象
                writer.WritePropertyName("t:chart1");
                writer.WriteStartArray();
                writer.WriteStartObject();
                writer.WritePropertyName("col1");
                writer.WriteValue(chart1[0, 0]);
                writer.WritePropertyName("col2");
                writer.WriteValue(chart1[0, 1]);
                writer.WriteEndObject();
                for (int r = chart1.GetLength(0) - 1; r > 0; r--)
                {
                    writer.WriteStartObject();
                    //循环写入列2的具体值
                    for (int c = 0; c < 2; c++)
                    {
                        writer.WritePropertyName("col" + (c + 1).ToString());
                        writer.WriteValue(chart1[r, c]);
                    }
                    writer.WriteEndObject();
                }
                writer.WriteEndArray();
                //t:chart1
                writer.WriteEndObject();
                writer.Flush();
            }
            sw.Close();
            string jsonContent = sw.GetStringBuilder().ToString();  //得到最终json字串

转化成功的样例如下:

{
"t:chart1": [
    {
      "col1": "项",
      "col2": "值"
    },
    {
      "col1": "学习发展",
      "col2": "4.1"
    },
    {
      "col1": "情绪控制",
      "col2": "5"
    },
    {
      "col1": "创新意识",
      "col2": "5.1"
    },
    {
      "col1": "成就意识",
      "col2": "4.8"
    },
    {
      "col1": "人际理解",
      "col2": "4"
    },
    {
      "col1": "执行力",
      "col2": "5"
    },
    {
      "col1": "果敢性",
      "col2": "5.7"
    },
    {
      "col1": "公正性",
      "col2": "4.5"
    },
    {
      "col1": "影响力",
      "col2": "4.7"
    },
    {
      "col1": "全局观",
      "col2": "4.2"
    }
  ]
}

 

(3)查找图表且替换数据

本代码程序只是示例片断,非完整程序,仅供参考。

一些引用
using Word=Microsoft.Office.Interop.Word;
using Newtonsoft.Json.Linq;
转换 json 字符串为 json 对象
Newtonsoft.Json.Linq.JObject jObject = null;
            if (jsonContent != "")
            {
                try
                {
                    jObject = Newtonsoft.Json.Linq.JObject.Parse(jsonContent); //转换为json对象
                }
                catch (Exception e)
                {
                    resultReport += "create json object  fail.<br>";  //失败记入调试报告
                }
            }
初始化 Word 应用程序
      Word.Application WordApp=new Word.Application();
      //创建一个名为WordDoc的文档对象
      WordApp.DisplayAlerts=Word.WdAlertLevel.wdAlertsNone;  //禁止一切提示警告
            //打开 filename 的文件
      Word.Document WordDoc=WordApp.Documents.Open(ref filename,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing,ref Nothing);
            //禁用拼写检查
            WordDoc.SpellingChecked = false;
            WordDoc.ShowSpellingErrors = false;
遍历word 里的 shapes
for (int i = 1; i <= WordDoc.InlineShapes.Count; i++)
{
  Word.InlineShape shape = WordDoc.InlineShapes[i];  //得到 shape对象
   //遍历 json 对象
  foreach (var item in jObject)
  {
    string tcmd = item.Key.ToString();   //取关键字
    //如果 shape 包含图表,则继续
    if (shape.HasChart == Microsoft.Office.Core.MsoTriState.msoTrue)
    {
        //如果图表未设置标题,则短路
        if (shape.Chart.HasTitle == false)
        {
            continue;
        }
        //获取图表的title
        string _name = shape.Chart.ChartTitle.Text.Trim().ToLower();
        if (_name.IndexOf(tcmd) != -1) //如果包含关键字则继续
        {
            //替换掉关键字,保留下来的是真正的标题
           shape.Chart.ChartTitle.Text = shape.Chart.ChartTitle.Text.Replace(tcmd, "");
            //这是一个玄机,否则会报错,目前我是这样的解决,A1:Z100,先赋值为空串
           shape.Chart.ChartData.Workbook.Worksheets[1].Range("A1:Z100").Value = "";
            //计算最后的单元格地址
           string lastcellAddress = "$" + ((char)(64 + jObject[tcmd][0].Count())).ToString() + "$" + jObject[tcmd].Count().ToString();
           //获得最终地址字串 
           string sourceDataAddress = "='Sheet1'!$A$1:" + lastcellAddress;
           //遍历json对象节点里的数组
           for (int i = 0; i < jObject[tcmd].Count(); i++)
           {
                List<JToken> tokens = jObject[tcmd][i].ToList();
                int k = 0;
                foreach (JToken jToken in tokens)
                {
                  //为每一个单元格赋值
                  string celladdress = ((char)(65 + k)).ToString() + (i + 1).ToString();                                shape.Chart.ChartData.Workbook.Worksheets[1].Range(celladdress).Value = jToken.ToArray()[0].ToString();
                  k++;
                }
           }
           shape.Chart.SetSourceData(sourceDataAddress);  //设置更新图表的数据源
           break;
         } // index of name
      } // has chart
   }//foreach tcmd
} //WordDoc.InlineShapes

小结

通过这种设计可以实现任意更换图表的类型,基本无需关注图表的实现原理,而让开发人员更多的关注于业务逻辑,当然这些图表的种类受限于Word的提供能力,如果能够满足需求,不失为一种解决思路。另外,我们可以继续扩展程序的功能,实现动态的图表添加或切换能力等。

一些体会

作为一名全程管理加全栈开发的 “野战军”,更多的时候考虑的是满足需求、稳定功能和控制各种成本,而无法深入地研究各项领域。随着年龄的增长,唯一能做到的就是业务经验弥补精力和学习时间的不足,还是有几点体会与大家共勉吧:

1、后悔学生时代没有端正态度和认识到认真学习的重要性,所谓书到用时方恨少,熟练掌握修炼数据结构与算法、数学等程序员的内功至关重要。

2、语言只是一种模型和工具而已,可先从一门语言入手到实际应用,抽象的来看所有语言总体上都是大同小异,后来会觉得学习一门新语言是一件非常有趣的事。

3、时间允许的情况下还是要深入掌握一些底层的技术开发和原理,这至少是一件非常有趣的事。

4、在工作中平衡最为关键,也包括人,换位思考很重要。

5、提升设计能力、业务处理能力和总结学习方法尤为关键。

以上就是自己一些体会,时间仓促,不妥之处还请大家批评指正讨论,程序员节就要到啦,祝咱们永远保持年轻的心,健康的心态,用智慧编写美好的人生!

相关文章
|
6天前
|
JSON 程序员 C#
使用 C# 比较两个对象是否相等的7个方法总结
比较对象是编程中的一项基本技能,在实际业务中经常碰到,比如在ERP系统中,企业的信息非常重要,每一次更新,都需要比较记录更新前后企业的信息,直接比较通常只能告诉我们它们是否指向同一个内存地址,那我们应该怎么办呢?分享 7 个方法给你!
|
9天前
|
C# UED SEO
C# 异步方法async / await任务超时处理
通过使用 `Task.WhenAny`和 `Task.Delay`方法,您可以在C#中有效地实现异步任务的超时处理机制。这种方法允许您在指定时间内等待任务完成,并在任务超时时采取适当的措施,如抛出异常或执行备用操作。希望本文提供的详细解释和代码示例能帮助您在实际项目中更好地处理异步任务超时问题,提升应用程序的可靠性和用户体验。
27 3
|
17天前
|
XML C# 开发工具
C# 删除Word文档中的段落
【11月更文挑战第3天】本文介绍了两种方法来操作 Word 文档:一是使用 `Microsoft.Office.Interop.Word` 库,适用于 Windows 环境下操作 Word 文档,需引用相应库并在代码中引入命名空间;二是使用 Open XML SDK,适用于处理 .docx 格式的文档,通过引用 `DocumentFormat.OpenXml` 库实现。文中提供了示例代码,展示了如何打开、删除段落并保存文档。
|
1月前
|
存储 C#
【C#】大批量判断文件是否存在的两种方法效率对比
【C#】大批量判断文件是否存在的两种方法效率对比
39 1
|
1月前
|
SQL 缓存 分布式计算
C#如何处理上亿级数据的查询效率
C#如何处理上亿级数据的查询效率
20 1
|
1月前
|
中间件 数据库连接 API
C#数据分表核心代码
C#数据分表核心代码
35 0
|
1月前
|
C#
C#的方法的参数传递
C#的方法的参数传递
15 0
|
1月前
|
数据可视化 程序员 C#
C#中windows应用窗体程序的输入输出方法实例
C#中windows应用窗体程序的输入输出方法实例
46 0
|
14天前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
29 3
|
1月前
|
安全 C# 数据安全/隐私保护
实现C#编程文件夹加锁保护
【10月更文挑战第16天】本文介绍了两种用 C# 实现文件夹保护的方法:一是通过设置文件系统权限,阻止普通用户访问;二是使用加密技术,对文件夹中的文件进行加密,防止未授权访问。提供了示例代码和使用方法,适用于不同安全需求的场景。
101 0