Office中国论坛/Access中国论坛

标题: 【作业】03课-紫电 [打印本页]

作者: 紫电    时间: 2014-3-16 04:53
标题: 【作业】03课-紫电
本帖最后由 紫电 于 2014-3-17 14:36 编辑

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

二、技术剖析
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.         }
复制代码





作者: faunus    时间: 2014-3-17 12:08
优秀作业,把代码、原时,关键部分的说明描述下就更完关。
作者: 紫电    时间: 2014-3-17 12:27
faunus 发表于 2014-3-17 12:08
优秀作业,把代码、原时,关键部分的说明描述下就更完关。

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

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

不应该啊。。。。
总体来说这个程序还是不行,初始化载入太慢了只能是娱乐

作者: 522650696    时间: 2016-4-26 16:29
优秀作业,把代码、原时,关键部分的说明描述下就更完关。




欢迎光临 Office中国论坛/Access中国论坛 (http://www.office-cn.net/) Powered by Discuz! X3.3