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

news2024/11/26 22:47:09

疲惫的修改

应人才测评产品的需求,导出测评报告是其中一个重要的环节,报告的文件类型也多种多样,其中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、提升设计能力、业务处理能力和总结学习方法尤为关键。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1122481.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 第二章 线程同步精要

并发编程有两种基本模型&#xff0c;一种是message passing&#xff0c;另一种是shared memory。在分布式系统中&#xff0c;运行在多台机器上的多个进程的并行编程只有一种实用模型&#xff1a;message passing。在单机上&#xff0c;我们也可以照搬message passing作为多个进…

【JavaEE】初识计算机网络(TCP/IP五层模型及封装和分用)

一、 网络通信基础 网络互连的目的是进行网络通信&#xff0c;也即是网络数据传输&#xff0c;更具体一点&#xff0c;是网络主机中的不同进程间&#xff0c;基于网络传输数据。 那么&#xff0c;在组建的网络中&#xff0c;如何判断到底是从哪台主机&#xff0c;将数据传输到…

仿CSGO盲盒开箱源码 盲盒商城源码 盲盒开箱源码 潮物盲盒商城源码

仿CSGO盲盒开箱源码 盲盒商城源码 盲盒开箱源码 潮物盲盒商城源码 测试环境&#xff1a;宝塔、Linux、PHP7.2、MySQL5.6 根目录 public&#xff0c;伪静态 thinkphp&#xff0c;php需要Redis扩展 后台&#xff1a;/stf 账号&#xff1a;admin 密码&#xff1a;123123 *后台…

【量化交易笔记】11.移动平均交易策略

概述 上一节我们建立了最最简单的交易策略&#xff0c;尽管有了盈利&#xff0c;但实际操作上是不可行的。本节将运用移动平均指标&#xff0c;包括单一移动平均策略和双移动平均策略&#xff0c;来建立经典的移动平均策略。 数据采集处理 本文采用上一节的相同数据&#xf…

Python基础入门例程1-NP1 Hello World!

描述 将字符串 Hello World! 存储到变量str中&#xff0c;再使用print语句将其打印出来。 输入描述&#xff1a; 无 输出描述&#xff1a; 一行输出字符串Hello World! 解答&#xff1a; str "Hello World!" print(str) 解释说明&#xff1a; 赋值变量&…

AD9371 官方例程HDL详解之JESD204B TX_CLK生成 (二)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

[H5动画制作系列]雪花随机产生飘落

雪花图片参考: 全局代码: var max120; var index0; 第一帧代码: index; if(index<max){posX550*Math.random();posY220*Math.random()-100;scale0.8*Math.random()0.2;var snowflakenew lib.snowlink();snowflake.xposX;snowflake.yposY;snowflake.scaleXscale;snowflake…

SpringBoot 实体参数(用于请求参数比较多时使用)

字段必须和传参时一致&#xff0c;否则为null&#xff0c; 使用AITINS可以快速生成&#xff0c;SET GET方法 public class User {//字段必须和传参时一致&#xff0c;否则为nullprivate String user;private String password;public String getUser() {return user;}public vo…

使用screen实现服务器代码一直运行

1.安装screen sudo apt install screen 2.创建一个screen&#xff08;创建一个名为chatglm的新的链接&#xff0c;用来一直运行 screen -S chatglm 3.查看进程列表 screen -ls 创建之后&#xff0c;就可以在当前窗口利用cd命令进入要执行的项目中&#xff0c;开始执行&#xf…

MIT 6.s081操作系统实验 Lab2: system calls

文章目录 1 System call tracing1.1 主要思路1.2 系统调用流程 2 Sysinfo2.1 kernel/kalloc.c 此文件用于实现分配物理空间的函数2.1.1 结构体定义2.1.2 空闲链表初始化2.1.3 内存的分配和释放2.1.4 获取空闲内存字节数 2.2 kernel/proc.c 此文件用于进程管理2.3 sys_Sysinfo2.…

Openssl数据安全传输平台006:粘包的处理-代码框架及实现-TcpSocket.cpp

文章目录 0. 代码仓库1. TCP通信粘包问题2. 粘包、拆包表现形式2.1 正常情况2.2 两个包合并成一个包2.3 出现了拆包 3. 粘包的处理-参考仓库中的文件TcpSocket.cpp3.1 发送数据时候的处理3.2 接收数据时候的处理 0. 代码仓库 https://github.com/Chufeng-Jiang/OpenSSL_Secure_…

互联网Java工程师面试题·Java 面试篇·第四弹

目录 59、我们能自己写一个容器类&#xff0c;然后使用 for-each 循环码&#xff1f; 60、ArrayList 和 HashMap 的默认大小是多数&#xff1f; 61、有没有可能两个不相等的对象有有相同的 hashcode&#xff1f; 62、两个相同的对象会有不同的的 hash code 吗&#xff1f; …

有限小数题解(进制转换+某进制判断是否为无限小数)

给定一个 A 进制下的分数 a/b&#xff0c; 小蓝想把它化为 B 进制下的小数 c。 现在他想知道这个小数是不是一个有限小数。 Input 输入共一行&#xff0c;包含四个数 a, b, A, B&#xff0c;表示该分数和两种进制。 其中 A, B 使用十进制表示&#xff0c; a, b 中大于 9 的数…

2023最新UI酒桌喝酒游戏小程序源码 娱乐小程序源码 带流量主

2023最新UI酒桌喝酒游戏小程序源码 娱乐小程序源码 带流量主 修改增加了广告位&#xff0c;根据文档直接替换&#xff0c;原版本没有广告位 直接上传源码到开发者端即可 通过后改广告代码&#xff0c;然后关闭广告展示提交&#xff0c;通过后打开即可 无广告引流 流量主版…

【JavaEE】网络编程(网络编程基础、Socket套接字)

一、网络编程基础 1.1、什么是网络编程&#xff1f; 网络编程&#xff0c;指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff08;或称为网络数据传输&#xff09; 注意&#xff1a;我们只要满足进程不同就行&#xff1b;所以即便是同一…

2023.10.19 关于设计模式 —— 单例模式

目录 引言 单例模式 饿汉模式 懒汉模式 懒汉模式线程安全问题 分析原因 引言 设计模式为编写代码的 约定 和 规范 阅读下面文章前建议点击下方链接明白 对象 和 类对象 对象和类对象 单例模式 单个实例&#xff08;对象&#xff09;在某些场景中有特定的类&#xff0c;…

VS采用nuget配置OpenGL

参考&#xff1a;如何配置Opengl编程环境_opengl配置_知心宝贝的博客-CSDN博客 这应该是最快的办法了&#xff0c;直接用nuget配置。 1.打开NuGet包管理器 2.搜索glew、glfw、glm、freeglut并点击安装即可 3.测试代码 能正常运行说明配置成功了 #include <GL/glew.h>…

Windows 安装 Java

1. 安装 JDK 从 Oracle 的官网下载的 JDK&#xff0c;例如 JDK 21 双击下载得到的 msi 文件&#xff0c;开始安装 JDK 选择要安装的文件路径&#xff08;我一般都默认&#xff09;&#xff1a; 等待安装&#xff1a; 安装完成&#xff1a; 2. 验证是否安装成功 2.1. 打开 cmd…

小插曲 -- 使用Visual Studio Code远程连接香橙派

在之前的学习中&#xff0c;代码的修改和保存都依赖于“vi”指令&#xff0c;而不得不承认vi指令的编辑界面非常原始&#xff0c;所以&#xff0c;如果可以将代码编辑放到更友好的环境里进行无疑是一件大快人心的事情。 本节介绍如何通过Visual Studio Code来进行远程连接: Vi…

Kubernetes 通过 Deployment 部署Jupyterlab

概要 在Kubernetes上部署jupyterlab服务&#xff0c;链接Kubernetes集群内的MySQL&#xff0c;实现简单的数据开发功能。 前置条件 镜像准备&#xff1a;自定义Docker镜像--Jupyterlab-CSDN博客 MySQL-Statefulset准备&#xff1a;StatefulSet 简单实践 Kubernetes-CSDN博客…