在传统的VBA开发中,若是用的是普通加载项方法,是可以存储数据在xlam上的,若用的是Com加载项方法同时是Addins程序级别的项目开发的,配置文件没法保存到工作薄中,一般另外用配置文件来存放供调用。

但无论以上两种方式都会带来一点缺陷,若用户配置好自己的数据,这些配置数据只能保存到自己电脑上,无法在文件共享给其他人使用时,配置文件一并传递过去。

当然一个折衷的方式是,在Excel文件中新建一个工作表并隐藏它来实现配置数据跟着工作薄一起带走。

这种方式有一缺点是,在用户工作薄里进行数据操作,用户很容易破坏到这些数据,就算深度隐藏工作表,仍然会轻松地用VBA方法重新将其显示出来,特别对一些敏感配置信息的保存非常不利。

针对以上的问题,是否有一种完美的解决办法呢?既能开发xlam或AddIns程序级的插件,同时又可以让配置文件信息跟着用户的文档走。
(如果是xlsm或文档型VSTO项目可以做到数据在文件中,但代码也仅能对此文档有效,对其他文档无效,也不是此处需要的完美解决方案)

大部分人能想到的方案是在自定义属性里CustomDocumentProperties 添加配置信息,但自定义属性,它的容量有限,且内容存储仅为文本型且有长度限制。

 
自定义属性

当需要存储复杂的结构如图片二进制数据时,用自定义属性就无能为力了,像Excel催化剂在上一波中提及的PictureBox关闭后不能存储的问题,如果需要存储其图片信息,供下一次打开文件时重新以PictureBox写入并关联对应的事件,这种方法显然仍然不符合要求。

使用CustomXMLPart对象保存配置信息

在xlsx版Excel文件中,区别于传统的xls文件,其文件本质是xml文件集合,在xlsx版文件结构中,除去工作表外,有另外一个对象同样可以存储数据,其存储数据的要求只要是xml格式的数据即可。

且此对象不止于在VSTO内使用,在VBA上也同样有此对象可供计用。

 
CustomXMLPart对象模型

虽然有此对象,但其要求的数据类型是xml,在VBA环境里操作xml将是个非常痛苦的事情,但在VSTO上操作,简直不能再简单的事情。

使用方式大概是先Add一个CustomXMLPart,然后使用LoadXML方法即可把xml内容加载到此对象中,下次访问时,只需访问其XML属性,即可将xml内容取出(文本格式)。

在.net环境下,特别是Ado.Net中,只需一个方法即可将一个DataTable转换为XML或将一个XML文件还原为DataTable对象,数据配置文件,无论多复杂,都可以用DataTable很轻松地管理数据,包括图片格式的二进制字段数据。

有以上.Net天然地对XML友好使用体验,在Excel上使用CustomXMLPart对象就变得十分轻松自在。

具体场景分享

在Excel催化剂的功能中,有好几个经典功能用上了CustomXMLPart存储配置信息,跟随文档传输的安全。

  • 插入图片功能,将PictureBox容器及相关属性序列化为CustomXMLPart,如图片对象,工作表中插入的单元格位置信息等。
  • 数据有效性验证功能中,验证规则及验证的单元格范围信息
  • 数据辅助录入功能中,辅助数据源及对应的录入单元格范围信息
  • 多级层级联动功能,多级数据源及对应的录入单元格范围信息。

核心代码分享

绑定事件,根据需要,绑定打开、关闭文件,或激活、失去激活状态事件

            Common.ExcelApp.WorkbookOpen += ExcelApp_WorkbookOpenLoadXMLPart;

            Common.ExcelApp.WorkbookBeforeClose += ExcelApp_WorkbookBeforeCloseSerialXML;
Common.ExcelApp.WorkbookActivate += ExcelApp_WorkbookActivateLoadXMLPart;
Common.ExcelApp.WorkbookDeactivate += ExcelApp_WorkbookDeactivateSerialXML;

使用上一波的图片功能示例
打开文件时,运行图片CustomXMLPart的反序列化

        private void ExcelApp_WorkbookOpenLoadXMLPart(Excel.Workbook wb)
{
LoadXMLPartOfPictureBoxInfo(wb); } private static void LoadXMLPartOfPictureBoxInfo(Excel.Workbook Wb)
{
try
{
Common.ExcelApp.ScreenUpdating = false;
var prop = Utilities.CustomXMLPartUtility.GetCustomPropertyByPropName(Wb, pictureBoxInfoXmlPartIdName); if (prop != null)
{
DataSet.MemoryDataset.PictureBoxInfoDataTable pictureBoxInfos = new DataSet.MemoryDataset.PictureBoxInfoDataTable(); Utilities.CustomXMLPartUtility.FillTableFromXMLPart(Wb, pictureBoxInfos, prop); foreach (var grp in pictureBoxInfos.Rows.Cast<DataSet.MemoryDataset.PictureBoxInfoRow>().GroupBy(s => s.PicSheetName))
{
Excel.Worksheet sht = Common.ExcelApp.ActiveWorkbook.Worksheets[grp.Key]; Worksheet vstoSht = Globals.Factory.GetVstoObject(sht); foreach (var row in grp.Select(s => s))
{
Excel.Range dstRange = sht.Range[row.PicTopLeftCellAddress, row.PicBottomRightCellAddress];
Image image = Image.FromStream(new MemoryStream(row.PictureImageBytes));
ShapeArrange.AddPictureBoxToVstoWorkSheet(
dstRange: dstRange,
img: image,
fileExt: row.PicExt,
hasBordersMargin: row.PicIsLookupFromRangValue,
isLookupFromRangValue: row.PicIsLookupFromRangValue,
offsetRow: row.PicOffsetRow,
offsetCol: row.PicOffsetCol);//
} }
}
}
catch
{ }
finally
{
Common.ExcelApp.ScreenUpdating = true;
}
}

关闭Excel文件时,将配置信息序列化到XML中存储

   private static void SerialXMLOfPictureBox(Excel.Workbook Wb)
{
try
{
Common.ExcelApp.ScreenUpdating = false;
bool isExistsPictureBox = false;
if (Globals.Factory.HasVstoObject(Wb))//是否是vsto工作薄
{
isExistsPictureBox = CheckExistingPictrueBox(Wb, isExistsPictureBox); if (isExistsPictureBox == true)
{
//是否保存工作薄
if (Wb.Saved == false)
{
if (MessageBox.Show(text: "检测到有未保存的内容,请确认是否保存修改内容。\n" +
"点击【是】保存修改内容并退出此工作薄,下次打开此工作薄时,工具插入的图片的双击、右键鼠标的操作仍然有效。\n" +
"单击【否】不保存修改内容退出此工作薄,且在下次打开此工作薄时,工具插入的图片的双击、右键鼠标的操作会失效。",
caption: "未保存内容确认", buttons: MessageBoxButtons.YesNo) == DialogResult.Yes)
{
Wb.Save();
}
else
{
return;
}
}
//这里和用户交互,确定是否需要删除图片
bool isReserveOlePictures = false;
if (MessageBox.Show(text: "文档是否需要共享给其他没有安装【Excel催化剂】插件的人查阅?\n" +
"点击【是】将保留工作薄中的图片,共享给其他人时,就算没安装【Excel催化剂】插件,插件插入的图片仍可查阅,但双击、右键鼠标的操作会失效。\n" +
"单击【否】将删除工作薄中的图片,在安装【Excel催化剂】插件的电脑重新打开此工作薄时,插件插入的图片重新生成,减少存储一份图片副本,对Excel文件大小有要求时选择此项。",
caption: "未保存内容确认", buttons: MessageBoxButtons.YesNo) == DialogResult.Yes)
{
isReserveOlePictures = true;
} DataSet.MemoryDataset.PictureBoxInfoDataTable pictureBoxTable = new DataSet.MemoryDataset.PictureBoxInfoDataTable();
//开始遍历工作表
foreach (Excel.Worksheet sht in Wb.Worksheets)
{
if (Globals.Factory.HasVstoObject(sht))
{
ShapeArrange.AddSerializePictureBoxsToDataTable(sht, pictureBoxTable, isReserveOlePictures);//添加图片信息至dataTable里
}
}
//序列化到xml文件中
string xmlString = Utilities.CustomXMLPartUtility.SerialDataTableToXMLString(pictureBoxTable); Utilities.CustomXMLPartUtility.AddOrModifyCustomXMLPart(Wb, xmlString, pictureBoxInfoXmlPartIdName); }
else
{
try
{
DocumentProperty prop = Common.ExcelApp.ActiveWorkbook.CustomDocumentProperties[pictureBoxInfoXmlPartIdName];
CustomXMLPart xmlPart = Wb.CustomXMLParts.SelectByID(prop.Value);
xmlPart.Delete();
}
catch
{ }
} Wb.Save();
}
}
catch (Exception)
{ }
finally
{
Common.ExcelApp.ScreenUpdating = true;
}
}

在ADO.Net中进行DataTable的序列化和反序列化真的容易得很,直接贴源代码

        public static string SerialDataTableToXMLString(DataTable dt)
{
MemoryStream stream = new MemoryStream();
dt.WriteXml(stream, XmlWriteMode.WriteSchema);
stream.Position = 0;
StreamReader reader = new StreamReader(stream);
string xmlString = reader.ReadToEnd();
return xmlString;
} public static string SerialDataSetToXMLString(System.Data.DataSet ds)
{
MemoryStream stream = new MemoryStream();
ds.WriteXml(stream, XmlWriteMode.WriteSchema);
stream.Position = 0;
StreamReader reader = new StreamReader(stream);
string xmlString = reader.ReadToEnd();
return xmlString;
}

将XML加入到CustomXMLPart也是很容易的事情

        public static void AddOrModifyCustomXMLPart(Excel.Workbook Wb, string xmlString, string xmlPartIdName)
{
//先查找有没有自定义属性PictureBoxInfoXmlPartId,有的话就是上次已经保存过xmlpart,这次只需改写内容
DocumentProperty prop = GetCustomPropertyByPropName(Wb, xmlPartIdName);
CustomXMLPart customXMLPart;
if (prop != null)
{
CustomXMLPart oldCustomXMLPart = Wb.CustomXMLParts.SelectByID(prop.Value);
if (oldCustomXMLPart != null)
{
oldCustomXMLPart.Delete();
} customXMLPart = Wb.CustomXMLParts.Add(xmlString);
Wb.CustomDocumentProperties[xmlPartIdName].Value = customXMLPart.Id;
}
else
{
customXMLPart = Wb.CustomXMLParts.Add(xmlString);
Wb.CustomDocumentProperties.Add(
Name: xmlPartIdName,
LinkToContent: false,
Type: Microsoft.Office.Core.MsoDocProperties.msoPropertyTypeString,
Value: customXMLPart.Id);
} }

上述方法中,同样用到了自定义文档属性,用于存放customXMLPart的ID信息

 Wb.CustomDocumentProperties.Add(
Name: xmlPartIdName,
LinkToContent: false,
Type: Microsoft.Office.Core.MsoDocProperties.msoPropertyTypeString,
Value: customXMLPart.Id);

结语

本篇对Excel开发进行一个大胆的尝试和创新,使Addins程序级的插件项目,在维护用户配置文件信息时,有了更优的解决方案,特别是对于一些复杂的配置信息如图片等二进制数据,通过customXMLPart对象的方式存放,带来了极大的维护便利性。

技术交流QQ群

QQ群名:Excel催化剂开源讨论群, QQ群号:788145319

 
Excel催化剂开源讨论群二维码

关于Excel催化剂

Excel催化剂先是一微信公众号的名称,后来顺其名称,正式推出了Excel插件,插件将持续性地更新,更新的周期视本人的时间而定争取一周能够上线一个大功能模块。Excel催化剂插件承诺个人用户永久性免费使用!

Excel催化剂插件使用最新的布署技术,实现一次安装,日后所有更新自动更新完成,无需重复关注更新动态,手动下载安装包重新安装,只需一次安装即可随时保持最新版本!

Excel催化剂插件下载链接:https://pan.baidu.com/s/1Iz2_NZJ8v7C9eqhNjdnP3Q

 
联系作者
 
公众号

取名催化剂,因Excel本身的强大,并非所有人能够立马享受到,大部分人还是在被Excel软件所虐的阶段,就是头脑里很清晰想达到的效果,而且高手们也已经实现出来,就是自己怎么弄都弄不出来,或者更糟的是还不知道Excel能够做什么而停留在不断地重复、机械、手工地在做着数据,耗费着无数的青春年华岁月。所以催生了是否可以作为一种媒介,让广大的Excel用户们可以瞬间点燃Excel的爆点,无需苦苦地挣扎地没日没夜的技巧学习、高级复杂函数的烧脑,最终走向了从入门到放弃的道路。

最后Excel功能强大,其实还需树立一个观点,不是所有事情都要交给Excel去完成,也不是所有事情Excel都是十分胜任的,外面的世界仍然是一个广阔的世界,Excel只是其中一枚耀眼的明星,还有其他更多同样精彩强大的技术、工具等。*Excel催化剂也将借力这些其他技术,让Excel能够发挥更强大的爆发!

关于Excel催化剂作者

姓名:李伟坚,从事数据分析工作多年(BI方向),一名同样在路上的学习者。
服务过行业:零售特别是鞋服类的零售行业,电商(淘宝、天猫、京东、唯品会)

技术路线从一名普通用户,通过Excel软件的学习,从此走向数据世界,非科班IT专业人士。
历经重重难关,终于在数据的道路上达到技术平原期,学习众多的知识不再太吃力,同时也形成了自己的一套数据解决方案(数据采集、数据加工清洗、数据多维建模、数据报表展示等)。

擅长技术领域:Excel等Office家族软件、VBA&VSTO的二次开发、Sqlserver数据库技术、Sqlserver的商业智能BI技术、Powerbi技术、云服务器布署技术等等。

2018年开始职业生涯作了重大调整,从原来的正职工作,转为自由职业者,暂无固定收入,暂对前面道路不太明朗,苦重新回到正职工作,对Excel催化剂的运营和开发必定受到很大的影响(正职工作时间内不可能维护也不可能随便把工作时间内的成果公布于外,工作外的时间也十分有限,因已而立之年,家庭责任重大)。

和广大拥护者一同期盼:Excel催化剂一直能运行下去,我所惠及的群体们能够给予支持(多留言鼓励下、转发下朋友圈推荐、小额打赏下和最重点的可以和所在公司及同行推荐推荐,让我的技术可以在贵司发挥价值,实现双赢(初步设想可以数据顾问的方式或一些小型项目开发的方式合作)。

技术交流QQ群

QQ群名:Excel催化剂开源讨论群, QQ群号:788145319

 
Excel催化剂开源讨论群二维码

关于Excel催化剂

Excel催化剂先是一微信公众号的名称,后来顺其名称,正式推出了Excel插件,插件将持续性地更新,更新的周期视本人的时间而定争取一周能够上线一个大功能模块。Excel催化剂插件承诺个人用户永久性免费使用!

Excel催化剂插件使用最新的布署技术,实现一次安装,日后所有更新自动更新完成,无需重复关注更新动态,手动下载安装包重新安装,只需一次安装即可随时保持最新版本!

Excel催化剂插件下载链接:https://pan.baidu.com/s/1Iz2_NZJ8v7C9eqhNjdnP3Q

 
联系作者
 
公众号

取名催化剂,因Excel本身的强大,并非所有人能够立马享受到,大部分人还是在被Excel软件所虐的阶段,就是头脑里很清晰想达到的效果,而且高手们也已经实现出来,就是自己怎么弄都弄不出来,或者更糟的是还不知道Excel能够做什么而停留在不断地重复、机械、手工地在做着数据,耗费着无数的青春年华岁月。所以催生了是否可以作为一种媒介,让广大的Excel用户们可以瞬间点燃Excel的爆点,无需苦苦地挣扎地没日没夜的技巧学习、高级复杂函数的烧脑,最终走向了从入门到放弃的道路。

最后Excel功能强大,其实还需树立一个观点,不是所有事情都要交给Excel去完成,也不是所有事情Excel都是十分胜任的,外面的世界仍然是一个广阔的世界,Excel只是其中一枚耀眼的明星,还有其他更多同样精彩强大的技术、工具等。*Excel催化剂也将借力这些其他技术,让Excel能够发挥更强大的爆发!

关于Excel催化剂作者

姓名:李伟坚,从事数据分析工作多年(BI方向),一名同样在路上的学习者。
服务过行业:零售特别是鞋服类的零售行业,电商(淘宝、天猫、京东、唯品会)

技术路线从一名普通用户,通过Excel软件的学习,从此走向数据世界,非科班IT专业人士。
历经重重难关,终于在数据的道路上达到技术平原期,学习众多的知识不再太吃力,同时也形成了自己的一套数据解决方案(数据采集、数据加工清洗、数据多维建模、数据报表展示等)。

擅长技术领域:Excel等Office家族软件、VBA&VSTO的二次开发、Sqlserver数据库技术、Sqlserver的商业智能BI技术、Powerbi技术、云服务器布署技术等等。

2018年开始职业生涯作了重大调整,从原来的正职工作,转为自由职业者,暂无固定收入,暂对前面道路不太明朗,苦重新回到正职工作,对Excel催化剂的运营和开发必定受到很大的影响(正职工作时间内不可能维护也不可能随便把工作时间内的成果公布于外,工作外的时间也十分有限,因已而立之年,家庭责任重大)。

和广大拥护者一同期盼:Excel催化剂一直能运行下去,我所惠及的群体们能够给予支持(多留言鼓励下、转发下朋友圈推荐、小额打赏下和最重点的可以和所在公司及同行推荐推荐,让我的技术可以在贵司发挥价值,实现双赢(初步设想可以数据顾问的方式或一些小型项目开发的方式合作)。

Excel催化剂开源第10波-VSTO开发之用户配置数据与工作薄文件一同存储的更多相关文章

  1. Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术

    Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术 Excel催化剂   2019.01.12 14:10* 字数 2948 阅读 41评论 0喜欢 0 编辑文章 在Excel催化剂的几大 ...

  2. Excel催化剂开源第16波-VSTO开发之脱离传统COM交互以提升性能

    在VSTO开发或其他COM技术开发过程中,甚至VBA也是,在和Excel交互中,难免会遇到性能瓶颈问题,COM技术的交互实在太慢,对大量数据读写等操作,耗时太长,容易卡用户界面以为是程序死机等等. 在 ...

  3. Excel催化剂开源第12波-VSTO开发遍历功能区所有菜单按钮及自定义函数清单

    在插件开发过程中,随着功能越来越多,用户找寻功能入口将变得越来越困难,在Excel催化剂 ,将采用遍历所有功能的方式,让用户可以轻松使用简单的查找功能找到想要功能所在位置,查找的范围有:功能按钮的显示 ...

  4. Excel催化剂开源第32波-VSTO开发的插件让WPS顺利调用的方法-注册表增加注册信息

    VSTO插件开发完成后,鉴于现在WPS用户也不少,很多时候用户没办法用OFFICE软件,只能在WPS环境下办公,VSTO开发的插件,只需增加一句注册表信息,即可让WPS识别到并调用VSTO开发的功能, ...

  5. Excel催化剂开源第14波-VSTO开发之单元格区域转DataTable

    在Excel开发过程中,大部分时候是和Range单元格区域打交道,在VBA开发中,大家都知道的一点是,不能动不动就去遍历所有单元格,那性能是非常糟糕的,很多时候,是需要把整个单元格区域装入数组中再作处 ...

  6. Excel催化剂开源第9波-VSTO开发图片插入功能,图片带事件

    图片插入功能,这个是Excel插件的一大刚需,但目前在VBA接口里开发,如果用Shapes.AddPicture方法插入的图片,没法对其添加事件,且图片插入后需等比例调整纵横比例特别麻烦,特别是对于插 ...

  7. Excel催化剂开源第8波-VSTO开发之异步调用方法

    在VSTO开发过程中,因其和普通的Winform开发有点差别,具体细节笔者也说不清楚,大概是VSTO的插件是寄生在Excel中,不属于独立的进程之类的,其异步方法调用时,未能如Winform那样直接用 ...

  8. Excel催化剂开源第7波-VSTO开发中Ribbon动态加载菜单

    在VS开发环境中,特别是VSTO的开发,微软已经现成地给开发者准备了设计器模式的功能区开发,相对传统的VBA.ExcelDna和其他方式的COM加载项开发来说,不需要手写xml功能区,直接类似拖拉窗体 ...

  9. Excel催化剂开源第22波-VSTO的帮助文档在哪里?

    Excel催化剂开源第22波-VSTO的帮助文档在哪里? Excel催化剂   2019.01.12 14:10 字数 2930 阅读 55评论 0喜欢 0 编辑文章 对于专业程序猿来说,查找文档不是 ...

随机推荐

  1. LCID

    Language Location (or type) Language ID Language tag Supported version Afar   0x1000 aa Release 9 Af ...

  2. SQL语法详解

    ALTER DATABASE修改数据库全局特性 ALTER DATABASE实际上是修改数据库目录中的dp.opt文件 ALTER TABLE修改表的结构 ALTER TABLE对表进行增删列,创建取 ...

  3. C++函数不写bool返回值,居然编译运行全部通过,但判断结果就不对了

    bool MyStart::IsCoorectParam(QString strParam) { if (strParam=="-aa" || strParam=="-b ...

  4. Windows下获取逻辑cpu数量和cpu核数量

    代码可在Windows NT下正常运行 具体API说明请参照如下文档: GetLogicalProcessorInformation 点击打开链接 点击打开链接 点击打开链接 typedef BOOL ...

  5. vuejs 使用less

    当所有东西都 准备好之后 : 第一步: 安装less依赖, npm install less less-loader --save 第二步: 修改webpack.config.js文件,配置loade ...

  6. centos安装最新版MySQL 8.0教程

    这篇教程是通过yum方式安装的 安装依赖 yum install libaio wget -y 检查MYSQL是否已安装 yum list installed | grep mysql 如果有先卸载 ...

  7. spring 5.x 系列第8篇 —— 整合Redis客户端 Jedis和Redisson (代码配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件和其映射类 2.2 ...

  8. MyBatis无限级分类实现的两种方法--自关联与map集合

    1.这回先创建数据库吧 下表cid是CategoryId的缩写,cname是CategoryName的缩写,pid是parentId的缩写 无限级分类一般都包含这三个属性,至少也要包含cid和pid才 ...

  9. Python 爬虫从入门到进阶之路(十一)

    之前的文章我们介绍了一下 Xpath 模块,接下来我们就利用 Xpath 模块爬取<糗事百科>的糗事. 之前我们已经利用 re 模块爬取过一次糗百,我们只需要在其基础上做一些修改就可以了, ...

  10. 微信小程序ES6方法Promise封装接口

    为何要封装接口? 有小程序开发的经验者,相信对微信API Request很熟悉了.对接接口时,有大部分的开发者都是直接调用request方法,去请求后台接口并渲染数据.诚然,直接使用api发起请求对接 ...