Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « Generating fractals inside AutoCAD using F# | Main | Sectioning an AutoCAD solid using F# »

    May 30, 2008

    Filtering Windows messages inside AutoCAD using .NET

    Back when I joined Autodesk in 1995, I worked in European Developer Support with one of the most talented programmers I've met, Markus Kraus. One of Markus' contributions to the R13 ARX SDK (or maybe it was R14?) was a sample called pretranslate, which remained on the SDK up until ObjectARX 2008, under samples/editor/mfcsamps/pretranslate (it was removed from the 2009 SDK when we archived a number of aging samples).

    Anyway, with AutoCAD 2009 the API that makes this sample possible has been added to the .NET API, so in homage to Markus' original sample (which I have fond memories of demoing during a number of events around Europe), I decided to translate the original C++ sample to C#.

    Here's the C# code:

    using Autodesk.AutoCAD.ApplicationServices;

    using Autodesk.AutoCAD.Runtime;

    using System.Windows.Interop;

    using System;


    namespace PreTranslate

    {

      public class Commands

      {

        // Keys


        const int MK_SHIFT = 4;

        const int MK_CONTROL = 8;


        // Keyboard messages


        const int WM_KEYDOWN = 256;

        const int WM_KEYUP = 257;

        const int WM_CHAR = 258;

        const int WM_SYSKEYDOWN = 260;

        const int WM_SYSKEYUP = 261;


        // Mouse messages


        const int WM_MOUSEMOVE = 512;

        const int WM_LBUTTONDOWN = 513;

        const int WM_LBUTTONUP = 514;


        static long MakeLong(int LoWord, int HiWord)

        {

          return (HiWord << 16) | (LoWord & 0xffff);

        }

        static IntPtr MakeLParam(int LoWord, int HiWord)

        {

          return (IntPtr)MakeLong(LoWord,HiWord);

        }

        static int HiWord(int Number)

        {

          return (Number >> 16) & 0xffff;

        }

        static int LoWord(int Number)

        {

          return Number & 0xffff;

        }


        // State used by the VhmouseHandler to filter on

        // the vertical or the horizontal


        bool vMode;

        bool hMode;

        int ptx;

        int pty;


        // Commands to add/remove message handlers


        [CommandMethod("caps")]

        public void Caps()

        {

          Application.PreTranslateMessage +=

            new PreTranslateMessageEventHandler(CapsHandler);

        }

        [CommandMethod("uncaps")]

        public void UnCaps()

        {

          Application.PreTranslateMessage -=

            new PreTranslateMessageEventHandler(CapsHandler);

        }

        [CommandMethod("vhmouse")]

        public void Vhmouse()

        {

          Application.PreTranslateMessage +=

            new PreTranslateMessageEventHandler(VhmouseHandler);

        }

        [CommandMethod("unvhmouse")]

        public void UnVhmouse()

        {

          Application.PreTranslateMessage -=

            new PreTranslateMessageEventHandler(VhmouseHandler);

        }

        [CommandMethod("watchCC")]

        public void WatchCC()

        {

          Application.PreTranslateMessage +=

            new PreTranslateMessageEventHandler(WatchCCHandler);

        }

        [CommandMethod("unwatchCC")]

        public void UnWatchCC()

        {

          Application.PreTranslateMessage -=

            new PreTranslateMessageEventHandler(WatchCCHandler);

        }

        [CommandMethod("noX")]

        public void NoX()

        {

          Application.PreTranslateMessage +=

            new PreTranslateMessageEventHandler(NoXHandler);

        }

        [CommandMethod("yes")]

        public void YesX()

        {

          Application.PreTranslateMessage -=

            new PreTranslateMessageEventHandler(NoXHandler);

        }


        // The event handlers themselves...


        // Force alphabetic character entry to uppercase


        void CapsHandler(

          object sender,

          PreTranslateMessageEventArgs e

        )

        {

          // For every lowercase character message,

          // reduce it my 32 (which forces it to

          // uppercase in ASCII)


          if (e.Message.message == WM_CHAR &&

              (e.Message.wParam.ToInt32() >= 97 &&

              e.Message.wParam.ToInt32() <= 122))

          {

            MSG msg = e.Message;

            msg.wParam =

              (IntPtr)(e.Message.wParam.ToInt32() - 32);

            e.Message = msg;

          }

        }


        // Force mouse movement to either horizontal or

        // vertical


        void VhmouseHandler(

          object sender,

          PreTranslateMessageEventArgs e

        )

        {

          // Only look at mouse messages


          if (e.Message.message == WM_MOUSEMOVE ||

              e.Message.message == WM_LBUTTONDOWN ||

              e.Message.message == WM_LBUTTONUP)

          {

            // If the left mousebutton is pressed and we are

            // filtering horizontal or vertical movement,

            // make the position the one we're storing


            if ((e.Message.message == WM_LBUTTONDOWN ||

                e.Message.message == WM_LBUTTONUP)

                && (vMode ||  hMode))

            {

              MSG msg = e.Message;

              msg.lParam = MakeLParam(ptx, pty);

              e.Message = msg;

              return;

            }


            // If the Control key is pressed


            if (e.Message.wParam.ToInt32() == MK_CONTROL)

            {

              // If we're already in "vertical" mode,

              // set the horizontal component of our location

              // to the one we've stored


              // Otherwise we set the internal "x" value

              // (as this is the first time through)


              if (vMode)

              {

                MSG msg = e.Message;

                msg.lParam =

                  MakeLParam(

                    ptx,

                    HiWord(e.Message.lParam.ToInt32())

                  );

                e.Message = msg;

                pty = HiWord(e.Message.lParam.ToInt32());

              }

              else

                ptx = LoWord(e.Message.lParam.ToInt32());


              vMode = true;

              hMode = false;

            }


            // If the Shift key is pressed


            else if (e.Message.wParam.ToInt32() == MK_SHIFT)

            {

              // If we're already in "horizontal" mode,

              // set the vertical component of our location

              // to the one we've stored


              // Otherwise we set the internal "y" value

              // (as this is the first time through)


              if (hMode)

              {

                MSG msg = e.Message;

                msg.lParam =

                  MakeLParam(

                    LoWord(e.Message.lParam.ToInt32()),

                    pty

                  );

                e.Message = msg;

                ptx = LoWord(e.Message.lParam.ToInt32());

              }

              else

                pty = HiWord(e.Message.lParam.ToInt32());

              hMode = true;

              vMode = false;

            }

            else

              // Something else was pressed,

              // so cancel our filtering


              vMode = hMode = false;

          }

        }


        // Watch for Ctrl-C, and display a message


        void WatchCCHandler(

          object sender,

          PreTranslateMessageEventArgs e

        )

        {

          // Check for the Ctrl-C Windows message


          if (e.Message.message == WM_CHAR &&

              e.Message.wParam.ToInt32() == 3)

          {

            Document doc =

              Application.DocumentManager.MdiActiveDocument;

            doc.Editor.WriteMessage(

              "\nCtrl-C is pressed"

            );

          }

        }


        // Filter out use of the letter x/X


        void NoXHandler(

          object sender,

          PreTranslateMessageEventArgs e

        )

        {

          // If lowercase or uppercase x is pressed,

          // filter the message by setting the

          // Handled property to true


          if (e.Message.message == WM_CHAR &&

              (e.Message.wParam.ToInt32() == 120 ||

              e.Message.wParam.ToInt32() == 88))

            e.Handled = true;

        }

      }

    }

    To be able to use the System.Windows.Interop namespace, you'll need to add a project reference to WindowsBase.dll. Strangely this can take some finding - at least on my system it wasn't included in the base assembly list. To find it, I browsed to:

    C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll

    The application defines a number of commands, which are described in this text from the original sample's ReadMe:

    This sample shows how to pretranslate AutoCAD messages

    before they're processed by AutoCAD.

    In order to pre-processe AutoCAD messages, a hook function

    needs to be installed. The following commands install

    different hook functions.


    - vhmouse/unvhmouse

      Installs/uninstalls a hook that makes the mouse move only in a

      vertical direction if <CTRL> key is pressed, and only in a

      horizontal direction if the <SHIFT> key is pressed.


    - caps/uncaps

      Installs/uninstalls a hook that capitalizes all

      letters typed in the command window.


    - noX/yes

      Installs/uninstalls a hook that filters out the

      letters 'x' or 'X'.


    - watchCC/unwatchCC

      Installs/uninstalls a hook that watchs

      for <CTRL>+C key combination to be pressed.

    Here's what happens when we run the various commands:

    Command: caps

    Command: UNCAPS

    Command: vhmouse

    Command: unvhmouse

    Command: watchcc

    Command:

    Ctrl-C is pressed

    Command:

    Command: _copyclip

    Select objects: *Cancel*

    Command: nox

    Command: yes

    While you can't see all the effects of the various commands from this dump of the command-line, here are some comments and pointers, should you try this sample:

    • The Shift or Caps Lock keys was not used at all during entry of the command-names
    • During the vhmouse command, move the mouse around and use the Shift and Ctrl keys to force the movement to horizontal or vertical
      • The Ctrl key now shows an entity selection cursor in AutoCAD, so I should probably have changed to another key for this, but anyway
    • Ctrl-C now launches COPYCLIP, but we see the message first
    • The yes command should obviously be called yesx, but then we can't enter the "x" character after running the nox command. :-)

    As a final note: I don't recommend filtering commonly-used keystrokes in your application - your users really won't thank you - but this fun little sample at least shows you the capabilities of the PreTranslate mechanism.

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e200e552a918a78834

    Listed below are links to weblogs that reference Filtering Windows messages inside AutoCAD using .NET:

    Comments

    Quite an informative post Kean.
    Although most who read this blog don't comment, rest assured it is being read.

    Great stuff - keep it coming.

    Cheers.

    This is useful stuff--and even outside of the scope it was originally intended for. Thanks for the posting.

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search