设为首页收藏本站Access中国

Office中国论坛/Access中国论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

返回列表 发新帖
查看: 10116|回复: 8
打印 上一主题 下一主题

【作业】03课-紫电

[复制链接]
跳转到指定楼层
1#
发表于 2014-3-16 04:53:36 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 紫电 于 2014-3-17 14:36 编辑

一、效果演示
       为了减小图片大小,图中,我只演示了一个单元格被修改后,程序插入批注的效果。实际上功能开启状态下,可以在多个单元格、多个工作表、多个工作簿上都起作用,且删除、插入等各种危险操作,均不会影响其效果。


二、技术剖析
1、基本原理
     使用字典获取工作表中的初始值或批注值,作为单元格的初始值。当工作表Change事件被触发时,遍历改变的单元格,与字典中的初值进行比较,如果不相符插入批注;相符,删除批注。
2、多工作表、多工作簿

为了实现在多个工作表、工作簿上都能实现增加批注记录原始数据的功能,需要订阅Excel.Application中的WorkbookOpen、WorkbookActivate、 SheetActivate事件,也就是进行事件委托。为避免代码集中在ThisAddin或者Ribbon中,这里我新建了了一个cs文件另外构造类,这部分初始化工作在构造函数中进行。一般情况下,通过以上三个事件之一 调用GetActiveWorksheet()实现字段初始化、工作表事件的委托。由于调试时不会触发这三个事件,所以构造函数中需要额外增加一个GetActiveWorksheet()进行初始化。


  1.         #region 构造函数
  2.         public MarkChangedCells(Excel.Application app)
  3.         {
  4.             xlsApp = app;
  5.             GetActiveWorksheet();//设置工作表对象
  6.             xlsApp.WorkbookOpen += xlsApp_WorkbookOpen;//打开工作簿
  7.             xlsApp.WorkbookActivate += xlsApp_WorkbookActivate;//激活工作簿
  8.             xlsApp.SheetActivate += xlsApp_SheetActivate;//切换工作表
  9.         }
  10.         #endregion
复制代码


3、避免工作表事件change多次被运行

问题:调试过程中,监视到工作表的Change事件,会在切换工作表之后,被多次调用,禁用事件触发仍然不起作用。
分析:堆中存在多个订阅事件的方法,没有被回收。
方案:取消事件委托之后再重新设置工作表对象,避免产生垃圾。

  1.         /// <summary>
  2.         /// 获取活动工作表,并关联事件,初始化字典
  3.         /// </summary>
  4.         /// <returns>是否成功获取工作表</returns>
  5.         bool GetActiveWorksheet()
  6.         {
  7.             try
  8.             {
  9.                 if (null != MySh)
  10.                 {
  11.                     MySh.Change -= MySh_Change;//解除事件,否则会多次执行Change事件
  12.                 }               
  13.                 MySh = xlsApp.ActiveSheet;//启动时,初始化FirstSh
  14.                 MySh.Change += MySh_Change;//监视事件
  15.                 m_NickNameConvert = new RangeNickName(MySh);//重置名称定义
  16.                 InitiateMyShData(MySh);// 初始化字典
  17.                 return true;
  18.             }
  19.             catch (Exception)
  20.             {
  21.                 MySh = null;//如果获取到的是Chart,则放弃操作
  22.                 return false;
  23.             }            
  24.         }
复制代码


4、复制、剪切、插入、删除单元格,初始值错位。

      起初使用的字典结构是Range.Address作为Key,Range.Value作为Value。分析可知,复制、剪切、插入、删除单元格之后,单元格实际情况与字典中的情况会不一致。比如插入了一行,字典中的Range.Address不会向下偏移一行,因此新插入行下面添加的批注都是错误的。为了修正这个漏洞,我重新调整了字典结构。具体措施如下:
(1)、使用GUID+Range.Value构造初始值字典,GUID为单元格别名(需要做简单字符串处理)。
(2)、再次构建一个字典,加载Excel名称定义(即单元格别名),体现GUID和Range.Address的对应关系。
(3)、初始值字典在加载单元格初始值、校验单元格是否改变时,通过调用类RangeNickName来实现。使用Range.Address获取GUID(单元格别名),判断单元格别名对应的单元格与Range是否属于同一单元格,以此来确定是否已经进行了剪切、插入、删除单元格操作。
(4)、提示复制操作,可以选择覆盖、修改粘贴区域,以此来避免产生大量的错误批注!
由于代码较多,以下只贴出部分核心代码。
  1.         /// <summary>
  2.         /// 检测别名是否存在,即是否修改过工作表
  3.         /// </summary>
  4.         /// <param name="Rng">要检测的单元格</param>
  5.         /// <returns>存在返回名称定义检测结果,否则返回空字符串</returns>
  6.         public string NickName(Excel.Range Rng)
  7.         {
  8.             InitiateNames(m_TargetSh);// 强制刷新字典,防止插入删除操作导致的错位
  9.             string sRefersTo = "="+ m_TargetSh.Name+"!" + Rng.Address;
  10.             string sGuid ;
  11.             if (m_Names_Sheet.ContainsKey(sRefersTo))
  12.             {
  13.                 sGuid = m_Names_Sheet[sRefersTo];
  14.             }
  15.             else
  16.             {
  17.                 sGuid = "";
  18.             }
  19.             return sGuid;
  20.         }


  21.         /// <summary>
  22.         /// 向工作表中添加单元格别名,返回别名
  23.         /// </summary>
  24.         /// <param name="Rng">需要添加别名的单个单元格</param>
  25.         /// <returns>返回别名</returns>
  26.         public string AddRangeNickName(Excel.Range Rng, bool Visible = false)
  27.         {
  28.             string sGuid;//获取GUID
  29.             InitiateNames(m_TargetSh);// 强制刷新字典,防止插入删除操作导致的错位
  30.             string sRefersTo = "=" + m_TargetSh.Name + "!" + Rng.Address;
  31.             if (!m_Names_Sheet.ContainsKey(sRefersTo))
  32.             {
  33.                 sGuid = AddNickName(sRefersTo, Visible);//添加名称
  34.             }
  35.             else
  36.             {
  37.                 sGuid = m_Names_Sheet[sRefersTo];//返回原有的名称定义
  38.             }      
  39.             return sGuid;//返回别名
  40.         }
复制代码

5、用户自定义开关此功能

     创建一个Ribbon,使用Checkbox,开关此功能。开就是new,关需要使用到手动析,区别于VB的是,设为null是不行的。注册表功能不再赘述,详见代码。
  1.         public void Dispose()
  2.         {            
  3.             if (null != MySh)
  4.             {
  5.                 MySh.Change -= MySh_Change;//解除事件,否则会多次执行Change事件
  6.             }   
  7.             MySh = null;
  8.             xlsApp = null;
  9.             MyShData = null;
  10.             GC.SuppressFinalize(this);//不需要再调用本对象的Finalize方法
  11.         }
复制代码




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

评分

参与人数 1V币 +2 收起 理由
faunus + 2 (V币)课程报名、录像学习、代码练习跟贴.

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏2 分享分享 分享淘帖 订阅订阅
2#
发表于 2014-3-17 12:08:12 | 只看该作者
优秀作业,把代码、原时,关键部分的说明描述下就更完关。
3#
 楼主| 发表于 2014-3-17 12:27:30 | 只看该作者
faunus 发表于 2014-3-17 12:08
优秀作业,把代码、原时,关键部分的说明描述下就更完关。

好的
4#
发表于 2014-3-17 22:36:43 | 只看该作者
呵呵,不错,还没有弄清楚什么原理,委托弄不明白,太笨了。可是发现一个小BUG,就是工作表的名称中有空格时会出错
5#
发表于 2014-3-17 22:37:28 | 只看该作者
虽说是用的GUID生成的不会有重复的,可是没有考虑好工作表本身的问题
6#
发表于 2014-3-17 22:38:54 | 只看该作者
要是每修改一次就加一次并且有时间就更好啦
7#
 楼主| 发表于 2014-3-18 14:14:04 | 只看该作者
/kuk心如止水 发表于 2014-3-17 22:38
要是每修改一次就加一次并且有时间就更好啦

这个简单,赋值的时候一加就好了,几行代码的事情
8#
 楼主| 发表于 2014-3-18 14:14:54 | 只看该作者
/kuk心如止水 发表于 2014-3-17 22:36
呵呵,不错,还没有弄清楚什么原理,委托弄不明白,太笨了。可是发现一个小BUG,就是工作表的名称中有空格 ...

不应该啊。。。。
总体来说这个程序还是不行,初始化载入太慢了只能是娱乐
9#
发表于 2016-4-26 16:29:44 | 只看该作者
优秀作业,把代码、原时,关键部分的说明描述下就更完关。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )  

GMT+8, 2024-5-5 21:31 , Processed in 0.097461 second(s), 34 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表