tab key handling in modeless forms

tab key handling in modeless forms

Anonymous
Not applicable
1,104 Views
1 Reply
Message 1 of 2

tab key handling in modeless forms

Anonymous
Not applicable

Hi all,

 

I'm working on an add-in for autodesk inventor. The requirement is to have a helper to support the creation of several sketches, based on a number of inputs.

 

the addin requires a modeless form to define such inputs, interact with the part and, when everything is set, to execute my custom code to generate a number of sketches.

 

I still have an open issue with the tab key handling of my modeless form. For some reason or the other, either solutions I found are not working.

 

I tried both SHOWing the form with the windowhandle solution and adding the ADN tab handler suggested, and neither work the way I expect. There should be a better way to do it, but I'm unable to find it.

 

I would welcome a sample (ideally, in C#) on how to properly transfer the handle of default key events to the underlying autodesk inventor application.

 

thanks,

 

 

links to the solutions I have found so far:

 

http://www.chemical-forum.com/thread-298019-1-1.html

 

http://adndevblog.typepad.com/manufacturing/2012/05/handling-tab-key-in-inventor-net-forms.html

 

forums.autodesk.com/t5/inventor-customization/can-t-tab-through-add-in-form/m-p/2528277/highlight/true#M29342

 

 

 

 

0 Likes
1,105 Views
1 Reply
Reply (1)
Message 2 of 2

Anonymous
Not applicable

At the end, I went through another route, I use a number of functions to hook on the keys of a normal form. That way I'm able to present forms from my addin, yet managing things like tabs etc...

This is the code I associated to my commandexecute

 

public static void BoxCommandControlButtonExecute()
        {
            
            var foilBoxForm = new FoilBoxForm();
            new ModelessHelper(foilBoxForm);
            foilBoxForm.ShowInTaskbar = false;
            foilBoxForm.Show(new WindowWrapper((IntPtr) AddInGlobal.InventorApp.MainFrameHWND));

        }
}

 

This is the code for modelesshelper

 

namespace FoilAddin
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    internal class ModelessHelper
    {
        /// <summary>
        ///     Other constants, uncategorized
        /// </summary>
        public const int GwlStyle = -16;

        public const int WsDisabled = 0x08000000;
        public const int UisSet = 1;
        public const int UisClear = 2;
        public const int UisInitialize = 3;
        public const int UisfHidefocus = 0x1;
        public const int UisfHideaccel = 0x2;

        /// <summary>
        ///     Constructor
        /// </summary>
        /// <param name="control">
        ///     Control to hook
        /// </param>
        public ModelessHelper(Form control)
        {
            this._mControl = control;
            this._mControl.HandleCreated += this.ControlHandleCreated;
            this._mControl.HandleDestroyed += this.ControlHandleDestroyed;
            this._mLocalWindowsHook.HookInvoked += this.LocalWindowsHookHookInvoked;
        }

        public static void TabToNextControl(Form form, bool forward)
        {
            Control curr = GetActiveControl(form);

            List<Control> flatcontrols = new List<Control>();

            CollectControls(form, flatcontrols);

            int controlIndex = flatcontrols.IndexOf(curr);
            if (controlIndex < 0)
            {
                return; // shouldn't happen
            }

            if (flatcontrols.Count > 0)
            {
                if (forward)
                {
                    controlIndex++;
                    if (controlIndex >= flatcontrols.Count)
                    {
                        controlIndex = 0;
                    }
                }
                else
                {
                    controlIndex--;
                    if (controlIndex < 0)
                    {
                        controlIndex = flatcontrols.Count - 1;
                    }
                }
                form.ActiveControl = flatcontrols[controlIndex];

                // Display keyboard focus indicator. There's no such indicator in modeless window.
                // Reference: .NET source, WinForms/Managed/System/WinForms/Control.cs, ProcessUICues()
                WindowsInterop.SendMessage(
                    form.Handle,
                    WindowMessage.WmChangeuistate,

                    // or WindowMessage.WM_UPDATEUISTATE ?
                    (IntPtr) (UisClear | (UisfHidefocus << 16)),
                    IntPtr.Zero);
            }
        }

        private static void CollectControls(Control c, List<Control> flatcontrols, int depth = 0)
        {
            if (c.Controls.Count == 0)
            {
                return;
            }

            // Copy controls to array and perform stable sort using TabIndex as a key
            var f1 = new Control[c.Controls.Count];
            c.Controls.CopyTo(f1, 0);
            IEnumerable<Control> f = f1.OrderBy(n => n.TabIndex);

            foreach (var control in f)
            {
                var isContainer = control is ContainerControl;
                if (!control.Enabled || !control.Visible)
                {
                    continue;
                }
                if (control.TabStop && control.CanSelect
                    && (!isContainer || (isContainer && !HasFocusableChild(control))))
                {
                    flatcontrols.Add(control);
                }
                CollectControls(control, flatcontrols, depth + 1);
            }
        }

        private static Control GetActiveControl(Control control)
        {
            while (control != null)
            {
                if (control is Form)
                {
                    control = (control as Form).ActiveControl;
                }
                else if ((control is ContainerControl) && HasFocusableChild(control))
                {
                    control = (control as ContainerControl).ActiveControl;
                }
                else
                {
                    return control;
                }
            }
            return null;
        }

        private static bool HasFocusableChild(Control control)
        {
            foreach (Control c in control.Controls)
            {
                if (c is ContainerControl || c is GroupBox)

                    // GroupBox is not ContainerControl, so precess it separately
                {
                    if (HasFocusableChild(c))
                    {
                        return true;
                    }
                }
                else
                {
                    if (c.CanSelect && c.TabStop)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        ///     LocalWindowsHook.HookInvoked event callback, called by LocalWindowsHook when a message is received.
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void LocalWindowsHookHookInvoked(object sender, HookEventArgs e)
        {
            if (e.HookCode >= 0 && new IntPtr(WindowMessage.PmRemove) == e.WParam)
            {
                var type = typeof(WindowsInterop.MSG);

                // Copy the message to a local int variable
                var msg = (uint) Marshal.ReadInt32(e.LParam, Marshal.OffsetOf(type, "Msg").ToInt32());
                var wParam = (uint) Marshal.ReadInt32(e.LParam, Marshal.OffsetOf(type, "wParam").ToInt32());
                var lParam = (uint) Marshal.ReadInt32(e.LParam, Marshal.OffsetOf(type, "lParam").ToInt32());
                var hwnd = Marshal.ReadIntPtr(e.LParam, Marshal.OffsetOf(type, "hwnd").ToInt32());

                // Don't translate non-input events. grab messages that are for this window.
                if (msg >= WindowMessage.WmKeyfirst && msg <= WindowMessage.WmKeylast
                    && WindowsInterop.GetForegroundWindow() == this._mControl.Handle)
                {
                    if (WindowsInterop.IsDialogMessage(this._mControl.Handle, e.LParam) != 0)
                    {
                        // The value returned from this hook is ignored, and it cannot be used to
                        // tell Windows the message has been handled. To avoid further processing,
                        // convert the message to WM_NULL before returning. If we'll disable these
                        // lines, system will start to produce "bell" sounds when hitting <Tab> key.
                        Marshal.WriteInt32(e.LParam, Marshal.OffsetOf(type, "Msg").ToInt32(), WindowMessage.WmNull);
                        Marshal.WriteInt32(e.LParam, Marshal.OffsetOf(type, "wParam").ToInt32(), 0);
                        Marshal.WriteInt32(e.LParam, Marshal.OffsetOf(type, "lParam").ToInt32(), 0);
                    }
                }
                else if (msg == WindowMessage.WmTimer)
                {
                    // WM_TIMER is always run by Autodesk Inventor, so we can rely on it. Check if we
                    // have some modal window running (by calling IsWindowEnabled() for application
                    // window) and disable/enable form's window.
                    var isMainWindowDisabled =
                        WindowsInterop.IsWindowEnabled((IntPtr) AddInGlobal.InventorApp.MainFrameHWND) == 0;
                    if (isMainWindowDisabled != this._mRunningModal)
                    {
                        this._mRunningModal = isMainWindowDisabled;
                        this.SetNativeEnabled(!this._mRunningModal);
                    }
                }
            }
        }

        private void SelectNextControl()
        {
            TabToNextControl(this._mControl, WindowsInterop.GetKeyState(Keys.ShiftKey) >= 0);
        }

        private void SetNativeEnabled(bool enabled)
        {
            WindowsInterop.SetWindowLong(
                this._mControl.Handle,
                GwlStyle,
                WindowsInterop.GetWindowLong(this._mControl.Handle, GwlStyle) & ~WsDisabled
                | (enabled ? 0 : WsDisabled));
        }

        private class Keys
        {
            public const int ShiftKey = 0x10;
        }

        /// <summary>
        ///     Virtual key codes
        /// </summary>
        private class VirtualKeys
        {
            public const int VkAbntC1 = 0xC1;
            public const int VkAbntC2 = 0xC2;
            public const int VkAdd = 0x6B;
            public const int VkAttn = 0xF6;
            public const int VkBack = 0x08;
            public const int VkCancel = 0x03;
            public const int VkClear = 0x0C;
            public const int VkCrsel = 0xF7;
            public const int VkDecimal = 0x6E;
            public const int VkDivide = 0x6F;
            public const int VkEreof = 0xF9;
            public const int VkEscape = 0x1B;
            public const int VkExecute = 0x2B;
            public const int VkExsel = 0xF8;
            public const int VkIcoClear = 0xE6;
            public const int VkIcoHelp = 0xE3;
            public const int VkKey0 = 0x30;
            public const int VkKey1 = 0x31;
            public const int VkKey2 = 0x32;
            public const int VkKey3 = 0x33;
            public const int VkKey4 = 0x34;
            public const int VkKey5 = 0x35;
            public const int VkKey6 = 0x36;
            public const int VkKey7 = 0x37;
            public const int VkKey8 = 0x38;
            public const int VkKey9 = 0x39;
            public const int VkKeyA = 0x41;
            public const int VkKeyB = 0x42;
            public const int VkKeyC = 0x43;
            public const int VkKeyD = 0x44;
            public const int VkKeyE = 0x45;
            public const int VkKeyF = 0x46;
            public const int VkKeyG = 0x47;
            public const int VkKeyH = 0x48;
            public const int VkKeyI = 0x49;
            public const int VkKeyJ = 0x4A;
            public const int VkKeyK = 0x4B;
            public const int VkKeyL = 0x4C;
            public const int VkKeyM = 0x4D;
            public const int VkKeyN = 0x4E;
            public const int VkKeyO = 0x4F;
            public const int VkKeyP = 0x50;
            public const int VkKeyQ = 0x51;
            public const int VkKeyR = 0x52;
            public const int VkKeyS = 0x53;
            public const int VkKeyT = 0x54;
            public const int VkKeyU = 0x55;
            public const int VkKeyV = 0x56;
            public const int VkKeyW = 0x57;
            public const int VkKeyX = 0x58;
            public const int VkKeyY = 0x59;
            public const int VkKeyZ = 0x5A;
            public const int VkMultiply = 0x6A;
            public const int VkNoname = 0xFC;
            public const int VkNumpad0 = 0x60;
            public const int VkNumpad1 = 0x61;
            public const int VkNumpad2 = 0x62;
            public const int VkNumpad3 = 0x63;
            public const int VkNumpad4 = 0x64;
            public const int VkNumpad5 = 0x65;
            public const int VkNumpad6 = 0x66;
            public const int VkNumpad7 = 0x67;
            public const int VkNumpad8 = 0x68;
            public const int VkNumpad9 = 0x69;
            public const int VkOem1 = 0xBA;
            public const int VkOem102 = 0xE2;
            public const int VkOem2 = 0xBF;
            public const int VkOem3 = 0xC0;
            public const int VkOem4 = 0xDB;
            public const int VkOem5 = 0xDC;
            public const int VkOem6 = 0xDD;
            public const int VkOem7 = 0xDE;
            public const int VkOem8 = 0xDF;
            public const int VkOemAttn = 0xF0;
            public const int VkOemAuto = 0xF3;
            public const int VkOemAx = 0xE1;
            public const int VkOemBacktab = 0xF5;
            public const int VkOemClear = 0xFE;
            public const int VkOemComma = 0xBC;
            public const int VkOemCopy = 0xF2;
            public const int VkOemCusel = 0xEF;
            public const int VkOemEnlw = 0xF4;
            public const int VkOemFinish = 0xF1;
            public const int VkOemFjLoya = 0x95;
            public const int VkOemFjMasshou = 0x93;
            public const int VkOemFjRoya = 0x96;
            public const int VkOemFjTouroku = 0x94;
            public const int VkOemJump = 0xEA;
            public const int VkOemMinus = 0xBD;
            public const int VkOemPa1 = 0xEB;
            public const int VkOemPa2 = 0xEC;
            public const int VkOemPa3 = 0xED;
            public const int VkOemPeriod = 0xBE;
            public const int VkOemPlus = 0xBB;
            public const int VkOemReset = 0xE9;
            public const int VkOemWsctrl = 0xEE;
            public const int VkPa1 = 0xFD;
            public const int VkPacket = 0xE7;
            public const int VkPlay = 0xFA;
            public const int VkProcesskey = 0xE5;
            public const int VkReturn = 0x0D;
            public const int VkSelect = 0x29;
            public const int VkSeparator = 0x6C;
            public const int VkSpace = 0x20;
            public const int VkSubtract = 0x6D;
            public const int VkTab = 0x09;
            public const int VkZoom = 0xFB;
            public const int VkNone = 0xFF;
            public const int VkAccept = 0x1E;
            public const int VkApps = 0x5D;
            public const int VkBrowserBack = 0xA6;
            public const int VkBrowserFavorites = 0xAB;
            public const int VkBrowserForward = 0xA7;
            public const int VkBrowserHome = 0xAC;
            public const int VkBrowserRefresh = 0xA8;
            public const int VkBrowserSearch = 0xAA;
            public const int VkBrowserStop = 0xA9;
            public const int VkCapital = 0x14;
            public const int VkConvert = 0x1C;
            public const int VkDelete = 0x2E;
            public const int VkDown = 0x28;
            public const int VkEnd = 0x23;
            public const int VkF1 = 0x70;
            public const int VkF10 = 0x79;
            public const int VkF11 = 0x7A;
            public const int VkF12 = 0x7B;
            public const int VkF13 = 0x7C;
            public const int VkF14 = 0x7D;
            public const int VkF15 = 0x7E;
            public const int VkF16 = 0x7F;
            public const int VkF17 = 0x80;
            public const int VkF18 = 0x81;
            public const int VkF19 = 0x82;
            public const int VkF2 = 0x71;
            public const int VkF20 = 0x83;
            public const int VkF21 = 0x84;
            public const int VkF22 = 0x85;
            public const int VkF23 = 0x86;
            public const int VkF24 = 0x87;
            public const int VkF3 = 0x72;
            public const int VkF4 = 0x73;
            public const int VkF5 = 0x74;
            public const int VkF6 = 0x75;
            public const int VkF7 = 0x76;
            public const int VkF8 = 0x77;
            public const int VkF9 = 0x78;
            public const int VkFinal = 0x18;
            public const int VkHelp = 0x2F;
            public const int VkHome = 0x24;
            public const int VkIco00 = 0xE4;
            public const int VkInsert = 0x2D;
            public const int VkJunja = 0x17;
            public const int VkKana = 0x15;
            public const int VkKanji = 0x19;
            public const int VkLaunchApp1 = 0xB6;
            public const int VkLaunchApp2 = 0xB7;
            public const int VkLaunchMail = 0xB4;
            public const int VkLaunchMediaSelect = 0xB5;
            public const int VkLbutton = 0x01;
            public const int VkLcontrol = 0xA2;
            public const int VkLeft = 0x25;
            public const int VkLmenu = 0xA4;
            public const int VkLshift = 0xA0;
            public const int VkLwin = 0x5B;
            public const int VkMbutton = 0x04;
            public const int VkMediaNextTrack = 0xB0;
            public const int VkMediaPlayPause = 0xB3;
            public const int VkMediaPrevTrack = 0xB1;
            public const int VkMediaStop = 0xB2;
            public const int VkModechange = 0x1F;
            public const int VkNext = 0x22;
            public const int VkNonconvert = 0x1D;
            public const int VkNumlock = 0x90;
            public const int VkOemFjJisho = 0x92;
            public const int VkPause = 0x13;
            public const int VkPrint = 0x2A;
            public const int VkPrior = 0x21;
            public const int VkRbutton = 0x02;
            public const int VkRcontrol = 0xA3;
            public const int VkRight = 0x27;
            public const int VkRmenu = 0xA5;
            public const int VkRshift = 0xA1;
            public const int VkRwin = 0x5C;
            public const int VkScroll = 0x91;
            public const int VkSleep = 0x5F;
            public const int VkSnapshot = 0x2C;
            public const int VkUp = 0x26;
            public const int VkVolumeDown = 0xAE;
            public const int VkVolumeMute = 0xAD;
            public const int VkVolumeUp = 0xAF;
            public const int VkXbutton1 = 0x05;
            public const int VkXbutton2 = 0x06;
        }

        /// <summary>
        ///     Windows message Id constants
        /// </summary>
        private class WindowMessage
        {
            public const int WmNull = 0;
            public const int WmKeyfirst = 0x0100;
            public const int WmKeydown = 0x0100;
            public const int WmKeylast = 0x0109;
            public const int WmTimer = 0x0113;
            public const int WmChangeuistate = 0x0127;
            public const int WmUpdateuistate = 0x0128;
            public const int PmRemove = 0x0001;
        }

        /// <summary>
        ///     Windows imports
        /// </summary>
        private static class WindowsInterop
        {
            [DllImport("user32.dll")]
            public static extern IntPtr GetForegroundWindow();

            [DllImport("user32.dll")]
            public static extern short GetKeyState(int keyCode);

            [DllImport("user32.dll")]
            public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

            /// <summary>
            ///     See: http://support.microsoft.com/kb/71450
            /// </summary>
            /// <param name="hDlg">
            /// </param>
            /// <param name="lpMsg">
            /// </param>
            /// <returns>
            /// </returns>
            [DllImport("user32.dll")]
            public static extern int IsDialogMessage(IntPtr hDlg, IntPtr lpMsg);

            [DllImport("user32.dll")]
            public static extern int IsWindowEnabled(IntPtr hWnd);

            [DllImport("user32.dll")]
            public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);

            [DllImport("user32.dll")]
            public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

            /// <summary>
            ///     Windows MSG structure definition
            /// </summary>
            [StructLayout(LayoutKind.Sequential)]
            public struct MSG
            {
                private IntPtr hwnd;

                private uint Msg;

                private IntPtr wParam;

                private IntPtr lParam;

                private int time;

                private int x; // decomposed POINT pt

                private int y;
            }
        }

        #region Private members

        private LocalWindowsHook _mLocalWindowsHook = new LocalWindowsHook(HookType.WhGetmessage);

        private Form _mControl;

        private bool _mRunningModal;

        #endregion Private members

        #region Control event callbacks

        /// <summary>
        ///     Install the hook when the control window handle is created
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void ControlHandleCreated(object sender, EventArgs e)
        {
            this._mLocalWindowsHook.Install();
        }

        /// <summary>
        ///     Uninstall the hook when the control window is destroyed
        /// </summary>
        /// <param name="sender">
        /// </param>
        /// <param name="e">
        /// </param>
        private void ControlHandleDestroyed(object sender, EventArgs e)
        {
            this._mLocalWindowsHook.Uninstall();
        }

        #endregion Control event callbacks
    }
}

 

this is windowwrapper

 

namespace FoilAddin
{
    using System;
    using System.Windows.Forms;

    public class WindowWrapper : IWin32Window
    {
        public WindowWrapper(IntPtr handle)
        {
            this.Handle = handle;
        }

        public IntPtr Handle { get; private set; }
    }
}

 

This is localwindowshook

 

namespace FoilAddin
{
    using System;
    using System.Runtime.InteropServices;

    public class LocalWindowsHook
    {
        public delegate void HookEventHandler(object sender, HookEventArgs e);

        public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        private HookProc _mFilterFunc = null;
        private IntPtr _mHhook = IntPtr.Zero;
        private HookType _mHookType;

        public LocalWindowsHook(HookType hook)
        {
            this._mHookType = hook;

            this._mFilterFunc = new HookProc(this.CoreHookProc);
        }

        public LocalWindowsHook(HookType hook, HookProc func)
        {
            this._mHookType = hook;

            this._mFilterFunc = func;
        }

        public event HookEventHandler HookInvoked;

        public void Install()
        {
            this._mHhook = SetWindowsHookEx(
                this._mHookType,
                this._mFilterFunc,
                IntPtr.Zero,
                (int) AppDomain.GetCurrentThreadId());
        }

        public void Uninstall()
        {
            UnhookWindowsHookEx(this._mHhook);
        }

        [DllImport("user32.dll")]
        private static extern int CallNextHookEx(IntPtr hhook, int code, IntPtr wParam, IntPtr lParam);

        private int CoreHookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            if (code < 0)
            {
                return CallNextHookEx(this._mHhook, code, wParam, lParam);
            }

            var e = new HookEventArgs {HookCode = code, WParam = wParam, LParam = lParam};

            this.OnHookInvoked(e);

            return CallNextHookEx(this._mHhook, code, wParam, lParam);
        }

        private void OnHookInvoked(HookEventArgs e)
        {
            if (this.HookInvoked != null)
            {
                this.HookInvoked(this, e);
            }
        }

        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(HookType code, HookProc func, IntPtr hInstance, int threadId);

        [DllImport("user32.dll")]
        private static extern int UnhookWindowsHookEx(IntPtr hhook);
    }
}

 

this is hooktype

 

namespace FoilAddin
{
    public enum HookType : int
    {
        WhJournalrecord = 0,

        WhJournalplayback = 1,

        WhKeyboard = 2,

        WhGetmessage = 3,

        WhCallwndproc = 4,

        WhCbt = 5,

        WhSysmsgfilter = 6,

        WhMouse = 7,

        WhHardware = 8,

        WhDebug = 9,

        WhShell = 10,

        WhForegroundidle = 11,

        WhCallwndprocret = 12,

        WhKeyboardLl = 13,

        WhMouseLl = 14
    }
}

 

and this is hookeventparms

 

namespace FoilAddin
{
    using System;

    public class HookEventArgs : EventArgs
    {
        public int HookCode;
        public IntPtr LParam;
        public IntPtr WParam;
    }
}

 

The rest is typical code for an addin...

 

Hope it helps.

 

Thanks!

0 Likes