#0. はじめに
DrawingDocumentのBrowserNodeには、難があります。
これらNodeをmouseで選択した時、SelectSetに入るObjectの型はunknownなのです。
突然で「何を言っているんだ・・・」と思うかもしれませんが、DrawingView内の要素(線)をいじろうとすると、すぐにわかります。
手作業ではBrowserNode単位で選択 → 線種変更できるのですが、現状のAPIで自動化しようとすると、全DrawingCurveについて1つずつ処理しないといけないので、ものすごく遅くなります。
「線種変更は手作業で右クリック
→プロパティー
するから、せめて条件に当てはまる項目を自動選択出来れば・・・」というのが、発端です。
#1. BrowserNode.DoSelect()
普通に考えれば、
InventorApplication.ActiveDocument.SelectSet.SelectMultiple(collection);
すれば良いだけの話です。でも、このcollectionに入れる要素の型が公開されていないので、この方法では選択できません。
SelectSetに頼らない、もう1つの選択方法があります。それは、DoSelect()です。
BrowserNodesを辿り、目的のBrowserNodeを見つけてnode.DoSelect()すれば、選択できます。
しかし、この方法では、前回選択した内容がクリアされる == 常に最後の1つしか選択されないという欠点があります。
#2. Control key
しかし、私は発見しました。keyboard(そう、物理キーボードです)のControl keyを押しっぱなしにしながら、DoSelect()を複数回呼び出すと、全てのObjectが選択されるのです。
これは、DoSelect()がまさにmouseの左クリックをエミュレートしているからです。(というか、実際は逆で、OnMouseClickがDoSelect()を呼んでいるのでしょう)
ここまで来れば、やることは1つです。あたかもControl keyが押されているかのごとく、振舞えば良いのです。
#3. GetKeyboardState / SetKeyboardState
SendMessage, PostMessage, WH_KEYBOARD_LLなどと遠回りをしましたが、最終的にはGetKeyboardStateとSetKeyboardStateを使って、Control keyを押している振りをさせることに成功しました。
internal class NativeMethods
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool GetKeyboardState(byte[] lpKeyState);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool SetKeyboardState(byte[] lpKeyState);
}
const int VK_CONTROL = 0x11;
void foo()
{
byte[] keys = new byte[256];
byte savedControlKey;
// Controlキーを押しっぱなしにする
NativeMethods.GetKeyboardState(keys);
savedControlKey = keys[VK_CONTROL];
keys[VK_CONTROL] = 0x80;
NativeMethods.SetKeyboardState(keys);
try
{
//
// ループか何かで、複数回BrowserNode.DoSelect()する
//
}
finally
{
// Controlキーを復元する
keys[VK_CONTROL] = savedControlKey;
NativeMethods.SetKeyboardState(keys);
}
}
実行時にユーザーがControl Keyを押している可能性も考慮して、変更前の状態を保管しておいて、作業終了後に元に戻しています。
まずはこの実装で問題ないと思いますが、
- 処理時間が長い
- 実行頻度が非常に高い (例えば、キーを1文字入力ごとに実行)
などの場合は、書き戻す前にもう一度GetkeyboardState()した方が良いかもしれません。
その場合、物理Control keyの状態が変化した場合はどうするんだ、と言われるとその通りで、厳密にはGetAsyncKeyState(VK_CONTROL)で状態を確認した方が良いのでしょう。
(GetAsyncKeyState()は、SetKeyboardState()の影響を受けないようです)
また別の問題として、DoSelect()するループ中に、物理Control keyをdown→upされると、状態が解除されてしまいます。
これらの問題を避けるには、DoSelect()の直前/直後で、Control keyを設定/解除すれば良いと思いますが、そこまでするかどうかは皆様の判断にお任せします。
#4. DoSelect()を確実に実行するための補足 (19.06.01)
DrawingDocumentを開きたての状態で下層のnodeをDoSelect()しても、選択されないことがあります。遅延バインディングをしているのか、nodeを開かないと選択できないようです。一旦開けば、後は閉じても選択できます。
とりあえず、以下のような回避方法を考えました。
if (node.Parent is BrowserNode parentNode)
{
// BrowserNodeを開く
parentNode.Expanded = true;
}
node.DoSelect();
選択した後にnodeが(厳密には親nodeが)開きっぱなしになるのですが、私は自分用AddInなので、別に良しとしています。
parentNode.Expanded = false
しても、__親の親__は閉じません。
なので、厳密に実行前の状況を保管したいのなら、親を再帰的に辿りながら、node
がkeyでnode.Expanded
がvalueのDictionaryを作り、最後にforeachで復帰すると良いでしょう。
#99. 親の記事に戻る
Autodesk Inventor API Hacking (概略)