この投稿が長くなって申し訳ありませんが、私が達成しようとしていることを伝えるには背景が必要だと思います.
私は古い Excel UDF アドインを更新する任務を負っています (以前は、Steve Dalton の JNI に関する本の API コードと、考えられるほぼすべてのテクノロジを使用していました)。これらの関数は、Excel の計算モデルとは非常に相容れませんでした。これらの関数は、いくつかの範囲を取り、(別のスレッドで) それらにデータを書き戻し、同時にユーザーがこのデータを編集できるようにしました (その後、保存され、リモート サーバーにアップロードされました)。これらすべてにより、ロードが非常に遅くなりましたが、必要に応じてデータを変更するために必要な柔軟性がユーザーに与えられました.
C# に移行しました (WSDL 経由でリモート Java サーバーからデータを取得します) が、ほとんどの関数が 50 回以上呼び出されており、元のアドインよりもはるかに遅く実行されていることがわかりました (これは、 Extensibility.IDTExtensibility2 – したがって、ここでは VSTO トリックは使用できません)。
必死になって、UDF のスワスを配列関数に書き直して、入力を受け入れないようにすることにしました (Excel は配列の上書きについて文句を言うでしょう)。これは明らかに数桁高速ですが、ユーザーよりも重要な要件がありません出力データを変更できます。
Excel が before-edit-is-committed 検証コールバック イベントを提供していないことに気付きました (Worksheet.OnEntry をいじって VBComponent を追加しましたが、配列またはリスト データ検証の上書きに関するエラーの前にトリガーされません)。
グローバルキーボードフックを実装するのは簡単だと思ったので、編集を傍受するためにいくつかのウィンドウフォームを書き始めました(単一のフォームエントリ用のTextBoxと、リストデータ検証を備えたセル用のComboBoxのみ)。また、クリップボードから選択した範囲。
現在、これらはすべてカスタム コンテキスト メニュー エントリ (またはダブルクリック) から実行されており、ユーザーには受け入れられません。少なくとも、F2、Ctl+V、およびアクティブ セルでの直接入力をインターセプトできる必要があります。 . しかし、オートメーション アドイン内でグローバル キーボード フックを登録する方法がわかりません。
私の質問は次のとおりです。すべての編集試行を傍受して、独自の処理を提供することは可能ですか? または、それが失敗した場合、グローバル キーボード フックを登録して、F2、Ctl+V、およびアクティブ セルでの直接入力をインターセプトするにはどうすればよいですか?
ここで見つかったフックを試しました WPF / C# でグローバルキーボードフック (WH_KEYBOARD_LL) を使用していますが、このコンテキストで App.xaml + App.xaml.cs が機能していません (これは、C# と一般的な Windows プログラミングとの最初の出会いです) 、そのため、誰かが App.xaml + App.xaml.cs 構成 () について説明する必要があるだけかもしれません。
ご注意ください; これは VSTO アドインではなく、Extensibility.IDTExtensibility2 を使用して実装されています。
更新編集: @TimWilliams と @CharlesWilliams は、関数の引数で読み取り/書き込みを行った以前のバージョンで、呼び出しが何度も繰り返された理由を尋ねました。
すべての関数には必須の id キー パラメーターがあり、ほとんどの関数は日付または日付範囲も取ります。次のようになります (非常に大きなワークブックで、グラフを含む 30 シートまで):
ワークブックが最初に読み込まれると、関数は古い (以前に保存された) 値ですべての起動を呼び出しますが、各関数の C# の最初の行は、モデルが読み込まれたかどうかを確認するためのバッキング モデルに対する (クイック) テストであるため、これらは無視されます。関数の引数は検査/非整列化されません。
ユーザーは、Web サービスまたは以前にシリアライズされたファイルを介してモデルをロードすることを選択します。
ビジュアル アップデートは無効になり、セットアップ シートにはいくつかのデータが順番に入力されます。いくつかの重要な日付 (開始日と終了日 - 異なるシートのさまざまな日付範囲は EMONTH +/- 12 で計算されます)、その他の静的データ (作成者/編集者名など)、最後に必須のキー ID (モデル データを識別する) )
現在、オートメーション アドインの各関数のメソッドには id キーがあるため、データが見つかった場合は返され、それ以外の場合は関数パラメーターの既定値が使用されます。(注: モデルは、関数によって要求されたオブジェクトのフィールドのコピーを保持するため、何が出力されたかがわかります。さらに呼び出す場合、データがキャッシュ レイヤーに存在する場合は更新されます (元のモデル データまたは以前のモデル データの手動ロール クローン)。キャッシュ値) を受信関数パラメーターと一緒に使用する - キャッシュ レイヤーとモデル データの差分は後で Web サービスにアップロードされます) - よく説明されていませんが、「キャッシュ」は実際にはモデル データと同じデータ構造のラッパーです
データが返された場合 (最初の読み込みを示します)、そのデータは、指定された範囲にデータを書き戻すワーカー スレッドによって提供されるブロッキング キューに入れられます。
最悪のパフォーマンスは、依存する関数の非常に長いチェーン (Excel を混乱させますか?) と、関数が依存関係の前に呼び出されるように見えるという事実に起因しているようです。
DATE_RANGE が An=EMONTH(An-1,12) のチェーン A1-An である場合、A1 は設定ページからの定数 LAST_DATE であり、既に入力されています。
次に、名前付きセル MODEL_ID が入力されると fn(MODEL_ID, DATE_RANGE) が呼び出されますが、DATE_RANGE の値が正しくなく、各 EMONTH が完了するたびに fn が繰り返し呼び出され、関数メソッドが範囲を日付に変換しようとします (無効な日付の場合は早期に戻ります)。 )。その間、ワーカー スレッドはアプリケーション ビジー例外のスローを開始します (したがって、範囲書き込みを再キューイングし、250 ミリ秒の任意の期間スリープします)。最終的には争いは収まりますが、最初にコーヒーを作って飲み始めるチャンスがあります (おそらく豆も挽くことができます)。
この恐ろしいコードを書いたので、セットアップ シートに日付を書き込み、MODEL_ID を更新する前に計算が停止するのを待つことを検討しました。これにより、関数呼び出しの数を減らすことができます。ただし、編集のみを傍受し、それらの更新をモデルに保持し、対応する範囲をダーティとしてマークすると、はるかにきれいに見えました。
利用可能なオプションはどちらかだと思います。
- 編集インターセプト バージョンでは、パラメーター化された C# コマンドにコールバックするすべての可能な ASCII 関数の呼び出しで vb フック OnKey を試してください (VB コードは少なくともループで生成できます)。
- 編集インターセプト バージョンを VSTO アドインとして試してください (これにより、キー バインディングが得られるはずです)。
- ExcelDNA を使用してください - (以前の) 読み取り/書き込み範囲パラメーター バージョンが魅力的に見えます (これは、適切なパフォーマンスを証明する可能性があります (これは、Excel 処理コードの論理的なバグを示している可能性があります)。
(長文で分かりづらく申し訳ありません)