首页 文章

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

提问于
浏览
0

对这篇文章的篇幅表示道歉,但我认为背景是传达我想要实现的目标的必要条件 .

我的任务是更新一个旧的Excel UDF插件(之前使用的是Steve Dalton与JNI的书中的api代码以及几乎所有其他可以想象的技术) . 这些函数对Excel的计算模型非常不同情 - 这些函数需要几个范围并将数据写回(在另一个线程中),同时允许用户编辑这些数据(然后保存并上传到远程服务器) . 所有这些使加载速度极慢,但为用户提供了根据需要更改数据的必要灵活性 .

我已经将它迁移到C#(它通过WSDL从远程Java服务器获取数据),但是我发现大多数函数被调用了50次并且它运行得远远慢于原始插件(它是使用可扩展性的自动化插件) .IDTExtensibility2 - 所以这里没有可用的VSTO技巧) .

绝望之下,我决定尝试将UDF重写为数组函数而不接受输入(Excel会抱怨覆盖数组) - 显然现在这个数字要快几个数量级,但是缺少关键要求而不是用户能够修改输出数据 .

实现Excel没有提供任何在编辑之前提交的验证回调事件(我使用了Worksheet.OnEntry并添加了一个VBComponent,但是在覆盖数组或列表数据验证的错误之前没有触发它) .

我假设它很简单,实现一个全局键盘钩子,所以开始编写一些窗体表格来拦截编辑(只有一个TextBox用于单个表单条目,一个ComboBox用于带有列表数据验证的单元格),并且它还将数据从剪贴板复制到选定的范围 .

目前所有这些都是来自定制的上下文菜单条目(或双击),用户不会接受 - 我必须至少能够截取F2,Ctl V并直接在活动单元格上输入 . 但我不知道如何在自动化插件中注册全局键盘钩子 .

So my question is; Is it possible to intercept every edit attempt and provide my own handling? Or failing that, how can I register a global keyboard hook to intercept F2, Ctl+V and direct typing on an active cell?

我已经尝试了这里找到的钩子Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C#但是无法让App.xaml App.xaml.cs在这个环境下工作(这是我第一次遇到C#和windows编程),所以可能有人只需要开导我wrt App.xaml App.xaml.cs配置() .

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

UPDATE EDIT: @TimWilliams和@CharlesWilliams询问为什么我的 previous version ,它对它进行了读写's functions'参数,有如此多的重复调用 .

所有函数都有一个强制的id键参数,大多数也采用日期或日期范围,以下是发生的事情(在相当大的工作簿~30张图表中):

  • 首次加载工作簿时,该函数使用陈旧(先前保存的)值调用所有fire,但忽略这些值,因为每个函数中的第一行C#是针对支持模型的(快速)测试,以查看模型是否已加载,没有检查/解组函数参数 .

  • 用户选择通过webservices或以前的序列化文件加载模型 .

  • 禁用可视更新,并按顺序使用一些数据填充设置表;一些关键日期(日期的开始和结束 - 不同工作表上的各种日期范围用EMONTH / - 12计算),一些其他静态数据(作者/编辑者名称等),最后是强制性密钥id(用于标识模型数据)

  • 现在自动化插件中的每个函数的方法都有id键,所以如果找到数据则返回,否则使用函数参数的默认值 . (注意:模型维护了函数请求的对象字段的副本,因此它知道输出了什么 . 对于进一步的调用,如果缓存层中存在数据,则更新(原始模型数据或之前的手动克隆)缓存值)与传入的函数参数 - 缓存层的差异和模型数据稍后上传到web服务) - 没有很好地解释“缓存”实际上是与模型数据相同的数据结构的包装器

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

糟糕的表现似乎源于很长时间依赖函数链(令人困惑的Excel?),以及函数似乎在它们的依赖之前被调用的事实,例如

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

然后在填充命名单元格MODEL_ID时调用fn(MODEL_ID,DATE_RANGE),但DATE_RANGE具有不正确的值,并且在每个EMONTH完成时重复调用fn,并且函数方法尝试将范围转换为日期(如果无效日期则返回到早期) . 同时,工作线程开始抛出应用程序繁忙异常(因此重新排队范围写入和休眠250ms的任意时间段) . 最终争论消退了,但你有机会先制作并开始喝咖啡(甚至可能会磨 beans 子) .

写完这个可怕的代码后,我考虑将日期写入设置表,然后在更新MODEL_ID之前等待计算停止 - 这将减少函数调用的数量 . 然而,只是拦截编辑,在模型中保存这些更新并将相应的范围标记为脏似乎更清晰 .

我认为可用的选项也是;

  • 在编辑拦截版本中,尝试使用vb钩子OnKey调用每个可能的ASCII函数来回调参数化的C#命令(VB代码至少可以在循环中生成)

  • 尝试编辑拦截版本作为VSTO插件(这应该给我键绑定)

  • 使用ExcelDNA - 它看起来很诱人(先前的)读写范围参数版本(可能证明性能足够(这可能表明我的Excel处理代码中存在逻辑错误) .

(再次为长度和缺乏清晰度道歉)

2 回答

  • 1

    您应该能够解决计算链问题,因为它听起来像是一组离散的连续步骤 .
    如果使用C / XLL,您将使函数参数类型为P,这将确保它们在传递给UDF之前由Excel计算 . 我认为如果将参数定义为除Object以外的任何参数,Ex cel DNA / addin Express应该具有相同的效果 .

    Excel计算LIFO序列中的单元格,该序列由先前的最终计算序列和已输入/更改的任何单元格设置:因此,首先计算最后更改的公式 .
    因此,您应该以相反的顺序在DATE_RANGE链中输入公式(首先在链中输入最后一个)
    据推测,您已在此过程开始时切换到手动计算模式 . 所以它可能就像写出设置表和日期一样简单,然后强制计算(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/

  • 1

    我无法帮助你使用全局键盘钩子,但你真的应该看看Excel DNA或Addin Express来显着提高你的C#UDF的性能(他们使用XLL C API将.NET与c#自动化相比更快) .
    Excel DNA和Addin Express在他们的支持论坛中也有讨论如何将数据从UDF重写回其他范围的线程 . IIRC Excel DNA讨论了单独的线程方法,Addin Express讨论了使用命令等效类型UDF来触发隐藏的XLM函数

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

相关问题