• 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)创建二维数组

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

    (2)二维数组转Json格式

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

    1. StringWriter sw = new StringWriter(); //using System.IO
    2. using (Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter(sw))
    3. {
    4. writer.Formatting = Newtonsoft.Json.Formatting.Indented;
    5. writer.WriteStartObject();
    6. //t:chart1 转化数组chart1 为 json 对象
    7. writer.WritePropertyName("t:chart1");
    8. writer.WriteStartArray();
    9. writer.WriteStartObject();
    10. writer.WritePropertyName("col1");
    11. writer.WriteValue(chart1[0, 0]);
    12. writer.WritePropertyName("col2");
    13. writer.WriteValue(chart1[0, 1]);
    14. writer.WriteEndObject();
    15. for (int r = chart1.GetLength(0) - 1; r > 0; r--)
    16. {
    17. writer.WriteStartObject();
    18. //循环写入列2的具体值
    19. for (int c = 0; c < 2; c++)
    20. {
    21. writer.WritePropertyName("col" + (c + 1).ToString());
    22. writer.WriteValue(chart1[r, c]);
    23. }
    24. writer.WriteEndObject();
    25. }
    26. writer.WriteEndArray();
    27. //t:chart1
    28. writer.WriteEndObject();
    29. writer.Flush();
    30. }
    31. sw.Close();
    32. string jsonContent = sw.GetStringBuilder().ToString(); //得到最终json字串

    转化成功的样例如下:

    1. {
    2. "t:chart1": [
    3. {
    4. "col1": "项",
    5. "col2": "值"
    6. },
    7. {
    8. "col1": "学习发展",
    9. "col2": "4.1"
    10. },
    11. {
    12. "col1": "情绪控制",
    13. "col2": "5"
    14. },
    15. {
    16. "col1": "创新意识",
    17. "col2": "5.1"
    18. },
    19. {
    20. "col1": "成就意识",
    21. "col2": "4.8"
    22. },
    23. {
    24. "col1": "人际理解",
    25. "col2": "4"
    26. },
    27. {
    28. "col1": "执行力",
    29. "col2": "5"
    30. },
    31. {
    32. "col1": "果敢性",
    33. "col2": "5.7"
    34. },
    35. {
    36. "col1": "公正性",
    37. "col2": "4.5"
    38. },
    39. {
    40. "col1": "影响力",
    41. "col2": "4.7"
    42. },
    43. {
    44. "col1": "全局观",
    45. "col2": "4.2"
    46. }
    47. ]
    48. }

     

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

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

    1. 一些引用
    2. using Word=Microsoft.Office.Interop.Word;
    3. using Newtonsoft.Json.Linq;
    4. 转换 json 字符串为 json 对象
    5. Newtonsoft.Json.Linq.JObject jObject = null;
    6. if (jsonContent != "")
    7. {
    8. try
    9. {
    10. jObject = Newtonsoft.Json.Linq.JObject.Parse(jsonContent); //转换为json对象
    11. }
    12. catch (Exception e)
    13. {
    14. resultReport += "create json object fail.
      "
      ; //失败记入调试报告
    15. }
    16. }
    17. 初始化 Word 应用程序
    18. Word.Application WordApp=new Word.Application();
    19. //创建一个名为WordDoc的文档对象
    20. WordApp.DisplayAlerts=Word.WdAlertLevel.wdAlertsNone; //禁止一切提示警告
    21. //打开 filename 的文件
    22. 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);
    23. //禁用拼写检查
    24. WordDoc.SpellingChecked = false;
    25. WordDoc.ShowSpellingErrors = false;
    26. 遍历word 里的 shapes
    27. for (int i = 1; i <= WordDoc.InlineShapes.Count; i++)
    28. {
    29. Word.InlineShape shape = WordDoc.InlineShapes[i]; //得到 shape对象
    30. //遍历 json 对象
    31. foreach (var item in jObject)
    32. {
    33. string tcmd = item.Key.ToString(); //取关键字
    34. //如果 shape 包含图表,则继续
    35. if (shape.HasChart == Microsoft.Office.Core.MsoTriState.msoTrue)
    36. {
    37. //如果图表未设置标题,则短路
    38. if (shape.Chart.HasTitle == false)
    39. {
    40. continue;
    41. }
    42. //获取图表的title
    43. string _name = shape.Chart.ChartTitle.Text.Trim().ToLower();
    44. if (_name.IndexOf(tcmd) != -1) //如果包含关键字则继续
    45. {
    46. //替换掉关键字,保留下来的是真正的标题
    47. shape.Chart.ChartTitle.Text = shape.Chart.ChartTitle.Text.Replace(tcmd, "");
    48. //这是一个玄机,否则会报错,目前我是这样的解决,A1:Z100,先赋值为空串
    49. shape.Chart.ChartData.Workbook.Worksheets[1].Range("A1:Z100").Value = "";
    50. //计算最后的单元格地址
    51. string lastcellAddress = "$" + ((char)(64 + jObject[tcmd][0].Count())).ToString() + "$" + jObject[tcmd].Count().ToString();
    52. //获得最终地址字串
    53. string sourceDataAddress = "='Sheet1'!$A$1:" + lastcellAddress;
    54. //遍历json对象节点里的数组
    55. for (int i = 0; i < jObject[tcmd].Count(); i++)
    56. {
    57. List tokens = jObject[tcmd][i].ToList();
    58. int k = 0;
    59. foreach (JToken jToken in tokens)
    60. {
    61. //为每一个单元格赋值
    62. string celladdress = ((char)(65 + k)).ToString() + (i + 1).ToString(); shape.Chart.ChartData.Workbook.Worksheets[1].Range(celladdress).Value = jToken.ToArray()[0].ToString();
    63. k++;
    64. }
    65. }
    66. shape.Chart.SetSourceData(sourceDataAddress); //设置更新图表的数据源
    67. break;
    68. } // index of name
    69. } // has chart
    70. }//foreach tcmd
    71. } //WordDoc.InlineShapes

     小结

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

    一些体会

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

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

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

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

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

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

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

  • 相关阅读:
    聚焦创新丨赛宁网安亮相2022未来网络发展大会成果展
    Python入门(二)
    macbook Safari 如何打开F12 Console 控制台 开发者工具 Developer Tools
    React-Native优质开源项目介绍
    对象数组转成strin再进行,隔开的字符串,包括赛选某个字段的子,或者求和,
    多模态学习(一) 初识
    (CPU/GPU)粒子继承贴图颜色发射
    top命令详细解读
    Kubernetes - Ingress暴露应用(四)
    Kotlin文件和类为什么不是一对一关系
  • 原文地址:https://blog.csdn.net/michaelline/article/details/133970876