November 2014

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            










« 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:

blog comments powered by Disqus

Feed/Share

10 Random Posts