Excel、Exchange和C#
摘要:Eric Gunnerson 将向您介绍如何使用 Outlook、Excel 和 C# 创建自定义的日历,该日历可以提供适用于短期项目和长期项目的清晰明了的版式。
下载 csharp05152003_sample.exe 示例文件(英文)。
虽然一月份已经过去了,我还是决定为您介绍这个迟到的新年解决方案。我决定不再谈论我的下一个专栏要说什么,因为每提到一个主题似乎就预示着我将来不会说到它。所以,这个月我将不谈论 DirectX。(如果下次谈到它,就是违背诺言了。)
在开始之前,先简短回顾一下上月专栏的内容。虽然我使用了 NUnit 来完成我的单元测试,但您也可以使用 csunit 或 .NETUnit 来完成您的单元测试。有关详细信息,请参阅 C# 工具页面(英文)。
c# 程序管理组最近开始使用 Microsoft Outlook® 的日历,来安排我们各项活动的日程,所以我们全都知道下一次讨论安排在什么时间、小组成员何时休假以及要召开的会议安排在什么时间。查看短期日程安排时,这个日历非常好用,但要查看未来几个月的日程安排时,它就不那么管用了。我查找了适用于查看长期日程安排的实用程序,但是没有找到。
看来,应该好好利用 MSDN 了。我写了一些用于访问电子邮件的代码,这些代码看起来相当简单,但是我需要一种方法来创建并打印日程安排网格。网格很容易画,但是要实现跨多个页面打印并不容易,所以我开始寻找可以打印矩形网格的程序,并了解如何跨页打印。看起来 Microsoft Excel 是比较理想的选择。
要从 C# 访问 Outlook 和 Excel,需要使用 COM 互操作。要使用 COM 互操作,需要具备互操作程序集以从 C# 端进行引用。您可以从 C# 项目中引用适当的 COM 组件来生成程序集,也可以下载适用于所有 Microsoft Office 组件的互操作程序集。
如果需要将程序集安装到 GAC 中,则不能引用任何未签名的程序集,因此如果您的程序集需要使用 Office,就需要下载已签名的程序集(英文)。下载程序集之后,需要向已签名的程序集添加引用。我将从在 Excel 中创建工作表并设置单元格开始。
使用 Excel
创建项目后,我找到项目中的引用节点,浏览到 PIA 所在的目录,然后添加对 Excel 的引用。
现在我已准备好使用 Excel 开始工作,但要这样做,还需要了解 Excel 对象模型。遗憾的是,很难找到正确的信息,所以我尝试了两种方法。
第一种方法是使用对象浏览器,浏览互操作程序集中可用的对象。要了解可以使用哪些方法和属性,这是一个不错的方法。
第二个方法是在 Excel 中录制宏,让宏完成我需要的操作,然后将 VBA 代码作为要编写的 C# 代码的参考。通常这很容易完成,但是 C# 中的代码与 VBA 中的有些不同,所以我想简单介绍一下这种方法。
Excel 宏
我打算写一个“探测”应用程序,以了解如何完成我要在 Excel 中进行的操作。首先,启动 Excel 并使其可见,创建一个新的工作表,在其中一个单元格中放入值,然后设置单元格的背景颜色。
但在操作之前,我想简单介绍一下 Excel 对象模型。Excel 是最早提供对象模型的 Microsoft 应用程序之一,当时提供的几种选项现在仍在使用。这意味着有时用起来会不太方便。遇到这些情况时,我会指出来。
启动 Excel 很容易,使其可见也是如此:
using Microsoft.Office.Interop.Excel; using ExcelApplication = Microsoft.Office.Interop.Excel.Application; ExcelApplication excel = new ExcelApplication(); excel.Visible = true;
第一个 using 语句引用 Excel 对象和方法。但在 Windows 窗体应用程序中使用这个语句时,我发现 Excel 和 Windows 窗体都有 application 对象。我为 Excel 的 Application 定义了别名,而没有使用完全限定名称。在第二个 using 语句中,我将
ExcelApplication
作为 Excel Application 对象,然后我就可以使用它而不必使用完全限定名称。
我将需要的操作录制为 Excel 宏,如下所示:
Workbooks.Add Range("C6").Select ActiveCell.FormulaR1C1 = "Hello" Range("C6").Select With Selection.Interior .ColorIndex = 6 .Pattern = xlSolid End With
这看起来不太象 C# 代码。在 Excel 宏中,有一些特定的假设值和结果,因此我们必须进行一些转换。例如:
Workbooks.Add
转换为:
Workbook workbook = excel.Workbooks.Add(Missing.Value);
我怎么知道要这样转换呢?我首先查看 Application 对象,发现它有一个名为 workbooks 的属性可以返回 workbooks 对象(这并不奇怪)。所以,在 VBA 代码中有一个假设的“excel.”。我键入 Workbooks.Add(
时,IntelliSense® 提示我 add 方法接受一个名为 template 的参数。
但在 VBA 代码中并没有参数,显然,这是一个可选参数。我们使用的包装类仅定义了函数的一个版本,因此我们必须传递一个表示“使用默认值”的值,该值就是 system.reflection 命名空间中的 Missing.Value
。
下一步,在单元格 C6
中设置值。由于 VBA 代码中的 Workbooks
表示 C# 代码中的 excel.Workbooks
,因此我们可以尝试使用 excel.Range
来获取区域。遗憾的是,我们的尝试失败了。
实际上,在 Excel VBA 中编程时,根据您编写的内容,会有多个假设的前缀。如果您使用 Range
,那么实际上就是在使用 excel.ActiveSheet.Range
。因此,我们编写以下代码:
excel.ActiveSheet.Range("C4").Select();
至少我们可以尝试这样写,但是会发现这样不能编译。原来,excel.ActiveSheet
是某种类型的对象。我不能确定这是为什么,只能推测,它可能是工作表或其他对象,也可能只是最初设定的类型的对象。
所以,我们尝试:
((Worksheet) excel.ActiveSheet).Range("C4").Select();
这样会好一些,但在 Worksheet 类中没有 range 函数。range 在 VBA 领域里是一个属性,但是在 C# 中,它只是一个接受两个参数的方法。所以,我们得到以下代码:
((Worksheet) excel.ActiveSheet).get_Range("C4", Missing.Value).Select(); excel.ActiveCell.Value2 = "Hello";
为什么是 Value2
而不是 FormulaR1C1
?这也是我尚未查明的问题。
有两种方法可以使代码更简洁一些。第一种方法是将 Worksheet 对象存储在变量中,这样就可以避免类型转换;第二种方法是对 range 对象执行操作,而不是选择它并使用活动的单元格。
最后一步是保存工作表,可以通过调用 worksheet.saveas() 来完成。此方法接受十个参数,因此可以将其余参数作为 Missing.Value
传递。以下是最终的代码:
ExcelApplication excel = new ExcelApplication(); excel.Visible = true; excel.Workbooks.Add(Missing.Value); Worksheet worksheet = (Worksheet) excel.ActiveSheet; Range r = worksheet.get_Range("C6", Missing.Value); r.Value2 = "Hello"; r.Interior.ColorIndex = 6; worksheet.SaveAs(@"c:/ExcelExample.xls", Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value); excel.Quit();
创建一个工作表,设置一些值,然后保存并退出,共九行代码。真是好极了。这些代码保存在 excelexample 项目中。
使用电子邮件
要访问 Exchange 电子邮件,可以使用 Outlook 对象模型,也可以使用 CDO(协作数据对象,以前称为 MAPI)模型。因为我不关心图形的显示,所以我要使用 CDO。CDO 不是 Office 的一部分,所以没有 PIA。
我创建一个新项目,并添加对 COM 对象 Microsoft CDO 1.21 Library
的引用。然后编写以下代码,以获取收件箱中邮件的数量:
using MAPI; using System.Reflection; Session session = new Session(); session.Logon("Default Outlook Profile", Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value ); Folder folder = (Folder) session.Inbox; Messages messages = (Messages) folder.Messages; int messageCount = (int) messages.Count;
与 Excel 一样,MAPI/CDO 对象模型出现的很早,其中的每项内容都被定义为对象,甚至象文件夹中邮件数量都是如此。通常,我会编写 MAPI 对象的包装对象,这样就可以不进行类型转换就直接使用它们。我为文件夹和 Messages 集合编写了两个包装程序,您可以使用 foreach
对它们进行遍历。
上述准备工作完成后,我可以编写以下代码来查看收件箱中的所有邮件:
MapiFolder inbox = new MapiFolder(session.Inbox); int size = 0; int count = 0; foreach (MAPI.Message message in inbox.Messages) { size += (int) message.Size; count++; }
当我运行这段代码时,发现我的 Exchange 收件箱中有 2982 封邮件,占用的空间超过了 33 MB。
如果我要查看所有文件夹,我可以编写一个递归函数:
public int TraverseFolder(MapiFolder folder) { int size = 0; foreach (MapiFolder subFolder in folder) { size += TraverseFolder(subFolder); } foreach (MAPI.Message message in folder.Messages) { size += (int) message.Size; } return size; }
如果我运行这段代码,大约一分多钟以后,它就会告诉我,我的整个收件箱树占用了大约 88 MB 空间。我想我需要做些清理工作。
处理约会
起初,mapi 只是处理邮件。添加了其他类型的项后,它出现了一个问题。如果我的代码用于取回 message 项,而意外地取回了 appointment 项,代码将会中断。所以,如果我打开一个邮箱并找到 calendar 子文件夹,我将取回由邮件而不是由约会组成的文件夹。如果我要查找一个约会的主题,这样很有效,但是如果我要获取开始日期和结束日期,就比较困难了。
为解决这个问题,mapi 添加了一个名为 getdefaultfolder() 的新函数,我可以通过调用它来指定我真正需要的 appointmentitems 集合,而不是 messages 集合。因此,我可以编写以下代码:
public void TraverseCalendar(Session session) { Folder calendar = (Folder) session.GetDefaultFolder( ActMsgDefaultFolderTypes.ActMsgDefaultFolderCalendar); Messages messages = (Messages) calendar.Messages; AppointmentItem message = (AppointmentItem) messages.GetFirst(Missing.Value); while (message != null) { string subject = (string) message.Subject; message = (AppointmentItem) messages.GetNext(); } }
我没有编写 Appointments 集合的包装程序,这就是我编写的没有包装程序的代码。
这段代码运行良好,但还有一个缺点。我只能获取我的邮箱的默认文件夹,而不能获取其他人的邮箱的文件夹。您可能还记得,我的目标是查看其他人邮箱中的约会,而这个方法没有解决问题。
所以,我又回到 Google 进行更多的研究。结果是,除了邮件中特定的项外,还有一个包含此类型所有字段的 fields 项,这些字段按编号存储。因此,如果我知道正确的编号,我就可以获取特定字段的值。
下面是我最后编写的代码:
InfoStore infoStore = FindInfoStore(session, mailbox); MapiFolder rootFolder = new MapiFolder((Folder) infoStore.RootFolder); MapiFolder calendar = rootFolder.FindSubFolder("Calendar"); DateTime graphEndDate = graphStartDate + new TimeSpan(days, 0, 0, 0); foreach (MAPI.Message message in calendar.Messages) { DateTime startDate = (DateTime) GetFieldValue(message, 6291520); DateTime endDate = (DateTime) GetFieldValue(message, 6357056); if (endDate < graphStartDate) continue; if (startDate > graphEndDate) continue; if (startDate < graphStartDate) { startDate = graphStartDate; } if (endDate > graphEndDate) { endDate = graphEndDate; } int labelIndex = 0; try { labelIndex = (int) GetFieldValue(message, -2093678589); } catch (Exception e) { string s = e.Message; } Appointment appointment = new Appointment((string) message.Subject, labelIndex, startDate, endDate); appointments.Add(appointment); }
GetFieldValue() 将查找邮件的所有字段,以搜索特定编号的字段。最好将那些常数放入有着明确名称的静态常数中。
虽然不太漂亮,但它可以达到预期的目的。遗憾的是,我还不知道如何处理周期性的约会。有两种可能的选择:
- 尝试我用过的相同办法,并对存储周期性事件的对象进行解码。
- 不使用 CDO,而用其他方法处理 Exchange,例如 WebDAV。
把代码合在一起
处理 Excel 和 Exchange 之后,我开始编写真正的应用程序。具有挑战性的任务是解决如何在网格中完成约会的版式,这确实有些复杂,所以我写了一些单元测试来作为指导。
要编写单元测试,我需要针对某些内容进行测试。针对实时日历进行测试不太顺畅,因为各种约会时有时无。因此,我将日历操作抽象为 icalendar 接口,并创建了两个实现该接口的类。第一个类是真实的,使用了 CDO;第二个是虚拟的,我只在其中创建了用于测试的对象。
这样我就可以编写单元测试,以测试用于版式的代码,然后在 Excel 中执行排版。
我还为 Excel 对象编写了类似的接口和虚拟对象,但我选择了“手动验证”在 Excel 中创建的正确结果。
eric Gunnerson 是 Visual C# 组的程序经理,以前曾是 C# 语言设计组的成员,著有 A Programmer's Introduction to C#, 2nd Edition(英文)。他从事编程工作已经有很长时间,积累了丰富的编程经验,他知道 8 英寸磁盘,而且还曾经用一只手装过磁带。业余时间他一直研究雨燕的飞行速度。
相关文章:

这个宝藏工具,给你一种黑客般的感觉
明天要交作业了,吴检正在宿舍熬夜爆肝拼命敲代码,劈里啪啦的键盘声和咔咔的鼠标声格外嘈杂,室友陈琛瞥了一眼,背过身,沉沉睡去,留下他一人在深夜无尽的黑暗中,断断续续却又没有尽头的咔咔声中凌…

LSTM神经网络
LSTM是什么 LSTM即Long Short Memory Network,长短时记忆网络。它其实是属于RNN的一种变种,可以说它是为了克服RNN无法很好处理远距离依赖而提出的。 我们说RNN不能处理距离较远的序列是因为训练时很有可能会出现梯度消失,即通过下面的公式训…

用C#实现在PowerPoint文档中搜索文本
用编程的方式根据对象模型很容易实现在Word、Excel文档中搜索文本,在PowerPoint里面也同样如此,使用对象模型有助于我们了解office的文档结构。 搜索的思路和方法基本是一样的,用PowerPoint应用程序对象打开指定的文档,用文档对象…

雷军的最后一次 重 大 创 业
这是我人生最后一次重大创业项目。我愿意押上人生全部的声誉,亲自带队,为小米汽车而战!——雷军作者 | Carol头图 | 下载于视觉中国出品 | CSDN(ID:CSDNnews)3月29日19:30,小米春季新品发布会拉…

shell之冒号的作用
冒号: :在shell中是一种命令,意思是总是为真,但是却不做任何操作,即总是为真的空命令eg:[rootlocalhost ~]# ${abc:t1}-bash: t1: command not found[rootlocalhost ~]# : ${abc:t1}[rootlocalhost ~]# echo $abct1[ro…

关于epel源的配置
EPEL,即Extra Packages for Enterprise Linux,这个软件仓库里有很多非常常用的软件,而且是专门针对RHEL设计的,对RHEL标准yum源是一个很好的补充,完全免费使用,由Fedora项目维护,所以如果你使用的是RHEL&am…

C#如何在Form中嵌入并且操作Excel表格
网上比较多讲述如何操作excel表的文章,但都是启动excel的窗口来打开excel数据文件。有时候需要把excel表嵌入到自己程序的form中,给客户一个不用切换窗口的操作界面,似乎更好。这在vc中用ole技术很容易实现,但是在c#中方法就不一样…

自研芯片架构 ,这家中国公司发布DPU芯片计划
近日,专注于智能计算领域的DPU芯片和解决方案公司中科驭数发布了其下一代DPU芯片计划,将基于自研的KPU(Kernel Processing Unit)芯片架构,围绕网络协议处理、数据库和大数据处理加速、存储运算、安全加密运算等核心功能࿰…

QQ超时不能刷新好友接收发送信息
如果您在其它电脑登录时正常,只在本地出现此种情况,可能与您本机的设置及网络限制有关,建议您先确保安装的是我司的官方版本,然后再搜索并删除您的QQ号码文件夹(会丢失聊天记录,请注意备份)&…
JavaStuNote 4
装箱(inbox)和拆箱(outbox) 代表了类类型和基本类型之间的转换行为。 手动版本号: Integer b new Integer(10); Int a b.intValue; 自己主动版本号: Integer b30; Integer bnew Integer(30); Int ab; int a b.intValue(); 由于基本类型…

把Doc文档转换成HTML等其它格式
利用microsoft Word 9.0 Object Library可以在页面中对Doc文档进行格式转换。有关Word对象的一些方法可以参考Open和Save。下面是进行转换的代码[C#]: /// <summary>/// WordToHtml 的摘要说明。/// 首先要添加引用:Microsoft Word 9.0 Object Li…

如何学好Python?相信很多人都做错了!
Python入门从哪开始,以我的过往经验,发现很多的教程都是这样讲的:先介绍 Python 的基本语法规则、list、dict、tuple 等数据结构,然后再介绍字符串处理和正则表达式,介绍文件等IO操作.... 就这样一点一点往下说。然而这…

艾麦捷科技-铂金小猪新年致辞
2019独角兽企业重金招聘Python工程师标准>>> 光阴似箭,2013年是匆忙的一年,艾麦捷科技从最初的“笑谈”到现在上线,招财猪进销存管理软件从无到有,到现在多次更新,无一不是在匆忙中完成的,尽管很…

C#开源框架整理
Json.NET http://json.codeplex.com/ Json.Net 是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单。通过Linq To JSON可以快速的读写Json,通过JsonSerializer可以序列化你的.Net对象。让你轻松实现.Net中所有类型(对象,基本数据类型 …

提供一个基于.NET的加密/解密算法
提供一个基于.NET SymmetricAlgorithm 类的、带私钥的加密/解密算法的包装类。使用方法: symmcrypto de new SymmCrypto(SymmCrypto.SymmProvEnum.DES);Response.Write(x.Decrypting(de.Encrypting("ok","yyy"),"yyy")); 类的实现…

ubuntu修改IP
一、使用命令设置Ubuntu的ip地址1. 设置IP sudo ifconfig eth0 203.171.239.155 netmask 255.255.255.224 这样就算设置好了网卡eth0的IP地址和子网掩码2. 设置网关 sudo route add default gw 203.171.239.1293. 设置DNS 修改/etc/resolv.conf,在其中加入 nameserv…

Arm 十年重磅发布 v9 架构,不受 EAR 约束,未来将覆盖 3000 亿颗芯片
整理 | 伍杏玲出品 | AI科技大本营(ID:rgznai100)随着 AI、大数据、IoT 技术的相互渗透,人们对数据的要求也越来越高,如何提供普适专用、安全而强大的处理能力的架构?Arm 为这个答案准备十年。近日,Arm 推出…

HDU 6051 - If the starlight never fade | 2017 Multi-University Training Contest 2
/* HDU 6051 - If the starlight never fade [ 原根,欧拉函数 ] | 2017 Multi-University Training Contest 2 题意:给定 m,p, p 是素数设 f(i) 是 满足 (xy)^i ≡ x^i mod p 的 (x,y) 对数 且 1 ≤ x ≤ p-1 , 1 ≤ y ≤ m 求 ∑[1≤i≤p-1] i*f(i)限制ÿ…

干货!用 Python 快速构建神经网络
作者 | ZackSock责编 | 欧阳姝黎头图 | 下载于视觉中国前言机器学习一直是Python的一大热门方向,其中由神经网络算法衍生出来的深度学习在很多方面大放光彩。那神经网络到底是个个什么东西呢?说到神经网络很容易让人们联想到生物学中的神经网络ÿ…

才知道百度也提供了智能DNS服务 - 加速乐
http://jiasule.baidu.com/ 智能DNS 依托百度多年积累的高精度DNS识别库,平均只需5秒全球DNS服务器全部生效,百度蜘蛛1秒生效。抗攻击、无限解析记录,免费支持电信、联通、移动、铁通、教育网、国外、搜索引擎等分线路解析。 极致云加速 百度…

c#中结构与类的区别
类与结构的差别如何选择结构还是类一.类与结构的示例比较:结构示例:public struct Person {string Name;int height;int weightpublic bool overWeight(){//implement something}}类示例:public class TestTime {int hours;int mi…
Samba amp; Nginx - Resource temporarily unavailable
先说说本人的开发环境:Win7 Editplus VMware(CentosSambaNginx)。用Samba在Centos上把web文件夹(如www)共享,然后在Win7上訪问这个文件夹。之所以这么用的原因有: 习惯了Windows。效率比較高Editplus编辑器好用,相对于vi系列来说…

好多Javascript日期选择器呀-7
the Coolest DHTML Calendar 最特別的在於按下月份跟年份的加減按鈕不放,就可以選擇該項目。但實際上按著左鍵拖曳實在是一件很累的事,而且不懂電腦的 End-user 根本就不知道要按著不放,還得特地花時間去說明真的吃力不討好。 正好這次的專案…

话AI、学实践、探未来,亚马逊云科技AI在线大会报名开启!
Innovate 2021亚马逊云科技 AI 在线大会即将在 4 月 22 日举办。届时,亚马逊云科技大中华区产品部总经理顾凡,以及亚马逊云科技全球人工智能技术副总裁、杰出科学家Alex Smola将联袂为您献上精彩的主题演讲。大会开设六大分会场,可谓是别开生…

linux中的一些命令的想法
用户影子文件 ----shadow为什么要有影子文件因为Linux使用不可逆的加密算法来加密口令。加密算法不可逆的,因此***从密文处得不到明文,但/etc/passwd文件是全局可读的,而且加密算法是公开的,一旦用户有机会获取了/etc/passwd文件&…

vstpd服务
1、安装ftpyum install vsftpd -y systemctl start vsftpd systemctl stop firewalld systemctl enable vsftpd setenforce 0 #关闭selinux或者设置selinux不然会对试验造成影响 lftp ip ##能登陆并且显示,表示安装成功2、vsftpd文件信息/var/ftp …

LINQ to XML 建立,读取,增,删,改
LINQ to XML的出现使得我们再也不需要使用XMLDocument这样复杂的一个个的没有层次感的添加和删除。LINQ可以使的生成的XML文档在内存中错落有致。下面以一个小的例子说名LINQ to XML的简单应用。 需要添加必要的引用。System.XML.Linq , System.XML.Xpath使用XDocument 建立一个…

好多Javascript日期选择器呀-6
<script languagejavascript>var DS_x,DS_y; function dateSelector() //构造dateSelector对象,用来实现一个日历形式的日期输入框。{ var myDatenew Date(); this.yearmyDate.getFullYear(); //定义year属性,年份,默认值为当前系…

市值达 58 亿美元,吴恩达的在线教育平台 Coursera 正式上市
整理 | 寇雪芹出品 | AI科技大本营(ID:rgznai100)4 月 1 日,教育科技公司 Coursera 在纽约证券交易所上市,股票代码为 COUR。该股开盘价定为 39 美元 / 股,截至上市当日收盘,股价上涨至 45 美元 / 股&#…

软件包管理RPM
RPM 程序包管理器,可实现对程序包的安装、更新、查询和卸载操作,应用广泛下面通过实际操作来更好的理解RPM的功能安装程序:-i //安装数据包参数-v //显示安装过程-vv //显示更详细的安装信息-h //配合-v更加直观的显示程序安装过程&#…