全局键盘挂钩(Excel)自动插件(不是VSTO)

道歉的这篇文章的长度,但我认为背景是必要的,以传达我想要实现的。

我负责更新一个旧的Excel UDF插件(它以前使用Steve Dalton的书中的JNI代码,几乎可以想象得到其他技术)。 这些函数对Excel的计算模型非常不感兴趣 – 这些函数需要几个范围,并在允许用户编辑这些数据(然后保存并上传到远程服务器)的同时(在另一个线程中)写回数据。 所有这些使得加载速度非常缓慢,但是给用户提供了必要的灵活性来根据需要改变数据。

我已经将它迁移到C#(它通过WSDL从远程Java服务器获取数据),但是我发现大多数函数被调用了50次以上,并且运行速度远远低于原始插件(这是一个使用自动插件Extensibility.IDTExtensibility2 – 所以没有VSTO技巧可用)。

在绝望的情况下,我决定尝试将UDF的块重写为数组函数,而不是接受input(Excel会抱怨覆盖数组) – 显然这比现在的用户快了几个数量级,但是缺less关键要求能够修改输出数据。

实现Excel没有提供任何之前编辑是提交validationcallback事件(我玩过Worksheet.OnEntry并添加了一个VBComponent,但它没有被触发之前有关覆盖数组或列表数据validation的错误)。

我认为这将是足够简单的实现一个全局的键盘钩子,所以开始编写一些Windows窗体来拦截编辑(只是一个文本框单个表单条目和一个combobox单元格与列表数据validation),也得到它从剪贴板复制数据所选范围。

目前,所有这一切都是由定制的上下文菜单项(或双击)驱动的,这将不会被用户所接受 – 我必须至less能够拦截F2,Ctl + V并直接在活动单元格上打字。 但我不知道如何在自动插件中注册全局键盘钩子。

所以我的问题是, 是否有可能拦截每一个编辑尝试,并提供我自己的处理? 或者失败了,我如何注册一个全局的键盘钩子来拦截F2,Ctl + V并直接在活动单元格上打字?

我试过在这里find的钩子在WPF / C#中使用全局键盘钩子(WH_KEYBOARD_LL),但无法获得App.xaml + App.xaml.cs在这种情况下工作(这是我第一次遇到C#和Windows编程一般) ,所以可能有人需要启发我的App.xaml + App.xaml.csconfiguration()。

请注意; 这不是一个VSTO插件,它使用Extensibility.IDTExtensibility2实现。

更新编辑: @TimWilliams和@CharlesWilliams询问为什么我以前的版本 ,它读写它的函数的参数,有这么多重复的调用。

所有的function都有一个强制性的ID键参数,大部分也取一个date或date范围,下面是发生了什么(在相当大的工作簿〜30张图):

  1. 当第一次加载工作簿时,函数会调用所有的过期值(先前保存的)值,但是这些值都被忽略,因为每个函数中的C#的第一行是对备份模型的(快速)testing,以查看模型是否已经加载,没有函数参数被检查/解组。

  2. 用户select通过web服务或以前的序列化文件加载模型。

  3. 可视化更新被禁用,并且设置表按照顺序填充一些数据; 一些关键的date(date的开始和结束 – 不同的date范围在不同的纸张上用EMONTH +/- 12计算),一些其他的静态数据(作者/编辑者名字等等),最后是强制性的键ID(标识模型数据)

  4. 现在,自动化插件中的每个函数的方法都有id键,所以如果find数据,它将返回,否则将使用来自函数参数的默认值。 (注意:模型维护函数所请求的对象字段的副本,以便知道输出的内容。对于进一步的调用,如果数据存在于caching层中,它将被更新(原始模型数据或先前caching值)与传入的函数参数 – 高速caching层和模型数据的差异后来上传到web服务) – 没有很好地解释“caching”实际上是一个包装模型数据相同的数据结构

  5. 如果返回数据(表示第一次加载),则将其放到由工作线程提供服务的阻塞队列中,该工作线程将数据写回指定的范围。

糟糕的performance似乎源于非常长的依赖函数链(混淆Excel?),以及这些函数似乎在它们的依赖关系之前被调用的事实,例如

给定DATE_RANGE是An = EMONTH(An-1,12)的链A1-An,其中A1是来自已经填充的设置页面的常量LAST_DATE

然后,一旦命名的单元格MODEL_ID被填充,但是DATE_RANGE具有不正确的值,则调用fn(MODEL_ID,DATE_RANGE),并且在每个EMONTH完成时重复调用fn,并且函数方法尝试将范围转换为date(如果无效date则返回早期)。 与此同时,工作线程开始抛出应用程序繁忙的exception(因此重新排队范围写入和睡眠任意期限为250毫秒)。 最终争论会消退,但是你将有机会首先喝咖啡(甚至可能磨豆子)。

写了这个可怕的代码之后,我考虑把date写入设置表,然后等待计算停止,然后再更新MODEL_ID – 这样可以减less函数调用次数。 不过,只截取编辑,在模型中保留这些更新,并将相应的范围标记为脏似乎更清洁。

我认为可用的select是,

  • 在编辑拦截版本中,通过调用每个可能的ASCII函数来调用vb钩子OnKey来callback参数化的C#命令(VB代码至less可以在循环中生成)
  • 尝试编辑拦截版本作为VSTO插件(这应该给我的关键绑定)
  • 使用ExcelDNA – 它((以前的)读写范围参数版本(这可能certificate是足够的性能(这可能表示我的Excel处理代码中的逻辑错误))看起来很诱人。

(再次抱歉的长度和缺乏清晰度)

你应该能够解决你的计算链问题,因为它听起来像是一系列连续的步骤。
如果使用C ++ / XLL,则可以使函数参数typesP保证它们在传递给UDF之前通过Excel进行计算。 如果参数被定义为Object以外的任何东西,我认为Ex cel DNA / addin Express应该具有相同的效果。

Excel计算由前一个最终计算序列设置的LIFO序列中的单元格以及任何已input/更改的单元格:因此最后一个更改的公式将首先被计算。
所以你应该按照相反的顺序在DATE_RANGE链中input公式(链中的最后一个)
推测在这个过程的开始,你已经切换到手动计算模式。 所以这可能就像写出设置表和date一样简单,然后强制计算(Application.calculate),然后更新MODEL_ID,然后强制执行另一个计算。

当然,使用Excel DNA,每个函数调用的开销总是要低得多。
http://fastexcel.wordpress.com/2011/07/07/excel-udf-technology-choices-snakes-ladders-with-vba-vb6-net-c-com-xll-interop/

我不能帮助你使用全局键盘钩子,但是你真的应该看看Excel DNA或者Addin Express来显着提高你的C#UDF的性能(他们使用比C#自动化快得多的XLL C API来接口.NET) 。
Excel DNA和Addin Express在他们的支持论坛上都有讨论如何将数据从UDF重写回其他范围的线程。 IIRC Excel DNA讨论了单独的线程方法,Addin Express讨论了使用命令等价types的UDF来触发一个隐藏的XLM函数

个人而言,我认为在所有情况下(多个工作簿打开,VBA,DDE等),使您的全局键盘钩子方法工作起来不显眼且高效是非常困难的。