LoginSignup
0
0

More than 3 years have passed since last update.

Autodesk Inventor API Hacking (Dockable Windowでのキー入力)

Last updated at Posted at 2019-05-26

0. はじめに

近年のInventorでは簡単にDockable Windowを作成できるのですが、EnterキーとESCキーが入力できずにハマるというパターンがあると思います。
これについて、回避方法を説明します。

1. そもそも、この問題は何故発生する?

WinFormsを使って開発すると、それぞれのControlが特殊キー(EnterESC)を受け取った時に、親Formにお伺いを立てるわけですが、その近辺の処理がうまく出来ないと、この問題が発生するようです。

2. 回避策 ~ Formに配置しよう

この問題が発生するのは、DocableWindowにControlを直接登録した場合です。
逆を言えば、ControlをいったんFormに配置して、そのFormを登録すれば、問題をひとまずは回避できます。

public void Activate(Inventor.ApplicationAddInSite addInSiteObject, bool firstTime)
{
    DockableWindow dockWindow;

    // (中略)

    Form1 frm = new Form1();

    dockWindow.AddChild(frm.Handle);
    dockWindow.Visible = true;
    dockWindow.SetMinimumSize(200, 300);

    frm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
    frm.Show();
}

しかし、この方法では、DocableWindowがInventorとは別のWindowとして振舞おうとするので、微妙にフォーカスがらみで挙動がおかしくなります。
具体的には、InventorのWindowが例えばメモ帳の後ろにある場合、このDocableWindowをクリックしてもInventorが前面に来ません。

3. 回避策 ~ WM_GETDLGCODEに対し、DLGC_WANTALLKEYSを返そう

RichTextBoxでしか試していませんが、殆どの場合はこれで回避できると思います。

MyRichTextBox.cs
using System;

namespace InvAddIn
{
    class MyRichTextBox : System.Windows.Forms.RichTextBox
    {
        private const int WM_GETDLGCODE = 0x0087;
        private const int DLGC_WANTALLKEYS = 0x0004;

        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            switch (m.Msg)
            {
                case WM_GETDLGCODE:
                    if (m.LParam != null)
                        m.Result = (IntPtr)DLGC_WANTALLKEYS;
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    }
}

技術的な詳細は割愛しますが、RichTextBoxの代わりに上記のMyRichTextBoxを使うと、Enterキーをキャプチャーできるようになります。
実際の場面では、UserControlに配置して使うのでしょうから、そのUserControl自体にこの方法を適用すれば良いのでは? と思い試してみましたが、駄目でした。
ですので、UserControl内の全てのControlは、それぞれに対策が必要です。(もちろん、必要がある場合は、ですけど)

4. 回避策 ~ WindowsのEventをHookしよう

WM_GETDLGCODEに対応することで解決したかに思えたのですが、DataGridViewではうまく行きませんでした。
追いかけてみると、DataGridViewは、編集時にそのセルに重ねるように別のTextBoxを配置していたのです。
このTextBoxのWndProcに介入できないので、DataGridViewについては、大掛かりになりますがWindows自体のKey EventをHookします。
少しcodeが長いですが、お付き合いください。

MyDataGridView.cs
class MyDataGridView : System.Windows.Forms.DataGridView
{
#region Win32 API declare
    private static class NativeMethods
    {
        [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
        internal static extern System.IntPtr SetWindowsHookEx(int hookType, HookObject.HookHandler hookDelegate, System.IntPtr module, uint threadId);
        [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
        internal static extern bool UnhookWindowsHookEx(System.IntPtr hook);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        internal static extern int CallNextHookEx(System.IntPtr hook, int code, System.IntPtr wParam, System.IntPtr lParam);
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        internal static extern uint GetCurrentThreadId();
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        internal static extern System.IntPtr GetFocus();
        [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
        internal static extern System.IntPtr SendMessage(System.IntPtr hWnd, int Msg, System.IntPtr wParam, System.IntPtr lParam);
    }
    private const int WH_KEYBOARD = 2;
    private const int VK_CANCEL = 0x03;
    private const int VK_RETURN = 0x0d;
    private const int VK_ESCAPE = 0x1b;
    private const int WM_KEYDOWN = 0x0100;
#endregion
#region Key Hook
    private class HookObject
    {
        public delegate int HookHandler(int code, System.IntPtr wParam, System.IntPtr lParam);
        public HookHandler hookDelegate;
        public System.IntPtr hook;

        public void SetHook(HookHandler onHook)
        {
            // Check this control is in Visual Studio.
            bool inDesignMode;
            if (System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime)
            {
                inDesignMode = true;
            }
            else
            {
                using (var p = System.Diagnostics.Process.GetCurrentProcess())
                {
                    inDesignMode = (
                        p.ProcessName.Equals("DEVENV", System.StringComparison.OrdinalIgnoreCase) ||
                        p.ProcessName.Equals("XDesProc", System.StringComparison.OrdinalIgnoreCase)
                    );
                }
            }
            // Execute only outside of Visual Studio.
            if (!inDesignMode)
            {
                if (hook == System.IntPtr.Zero)
                {
                    hookDelegate = new HookHandler(onHook);
                    hook = NativeMethods.SetWindowsHookEx(WH_KEYBOARD, hookDelegate, System.IntPtr.Zero, NativeMethods.GetCurrentThreadId());
                    if (hook == System.IntPtr.Zero)
                    {
                        int errorCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                        throw new System.ComponentModel.Win32Exception(errorCode);
                    }
                }
            }
        }
        public void Unhook()
        {
            if (hook != System.IntPtr.Zero)
            {
                NativeMethods.UnhookWindowsHookEx(hook);
            }
        }
        public HookObject(HookHandler onHook)
        {
            SetHook(onHook);
        }

        ~HookObject()
        {
            Unhook();
        }

    }
    private static readonly HookObject hookObject = new HookObject(OnHookKey);

    private static System.IntPtr hwndKeyHook = System.IntPtr.Zero;

    private static int OnHookKey(int nCode, System.IntPtr wParam, System.IntPtr lParam)
    {
        if (-1 < nCode)
        {
            int vKey = (int)wParam;
            ulong keyFlag = (ulong)lParam;
            switch (vKey)
            {
                case VK_RETURN:
                case VK_CANCEL:
                case VK_ESCAPE:
                    if ((keyFlag & 0xc0000000) == 0 && hwndKeyHook == NativeMethods.GetFocus())
                    {
                        NativeMethods.SendMessage(hwndKeyHook, WM_KEYDOWN, wParam, lParam);
                        return 1;
                    }
                    break;
            }
        }
        return NativeMethods.CallNextHookEx(hookObject.hook, nCode, wParam, lParam);
    }
#endregion
#region Event
    private static void Controls_GotFocus(object sender, System.EventArgs e)
    {
        if (sender is System.Windows.Forms.Control control)
        {
            hwndKeyHook = control.Handle;
        }
    }

    private static void Controls_LostFocus(object sender, System.EventArgs e)
    {
        hwndKeyHook = System.IntPtr.Zero;
    }

    protected override void OnEditingControlShowing(System.Windows.Forms.DataGridViewEditingControlShowingEventArgs e)
    {
        if (e.Control is System.Windows.Forms.DataGridViewTextBoxEditingControl textBox)
        {
            textBox.GotFocus -= Controls_GotFocus;
            textBox.GotFocus += Controls_GotFocus;
            textBox.LostFocus -= Controls_LostFocus;
            textBox.LostFocus += Controls_LostFocus;
        }
        base.OnEditingControlShowing(e);
    }
#endregion
}

この対策をUserControlに適用すれば、全て解決するのでは? と思ったのですが、DataGridViewに対しては良かったものの、今度はRichTextBoxに対してうまく動作しませんでした。
もう少し突っ込んでいけば、UserControlだけの対応ですむ方法が見つかったのかもしれません。しかし、上述の通りRichTextBoxには既に回避策が見つかっていたので、個別対応することにしました。(この時点で、既に結構な日数を消費していたので、力尽きたのです)

上記code内で、AddInがunloadされるときにHookを解放しようとしています。(HookObjectのデストラクター)
しかし、実際にはこの解放するcodeは呼ばれていないようです。Inventorが正しい手順を踏んでAddInのtheradを終了させていないのかもしれません。
現時点では何の問題も発生していないので、WindowsがAddInのtheradが終了するときにunhookしてくれているようです。

5. 自作Controlを使う場合の注意点 (x32/x64問題)

通常は気にすることが無いのですが、今回の解決策のように標準のWinFormsではない自作Controlを使って開発しようとする場合は、対象プラットフォームAny CPUではなくx64を指定していると、ハマります。
具体的には、Visual StudioのデザイナーでControlを配置できなくなります。
これは、デザイナーが実際にControlをロード(実行)して画面上に配置するからで、x32アプリケーションであるVisual Studioはx64のカスタムControlを読み込めないからです。
解決策としては、デザイナーで作業する前にAny CPUにしてリビルドすると良いです。デザイナーで作業しない限りは、x64で作業して問題ありません。

99. 親の記事に戻る

Autodesk Inventor API Hacking (概略)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0