A blog for developers programming with Autodesk platforms, particularly AutoCAD and Forge. With a special focus on AR/VR and IoT.

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

{

publicclassCommands

{

// Keys

constint MK_SHIFT = 4;

constint MK_CONTROL = 8;

// Keyboard messages

constint WM_KEYDOWN = 256;

constint WM_KEYUP = 257;

constint WM_CHAR = 258;

constint WM_SYSKEYDOWN = 260;

constint WM_SYSKEYUP = 261;

// Mouse messages

constint WM_MOUSEMOVE = 512;

constint WM_LBUTTONDOWN = 513;

constint WM_LBUTTONUP = 514;

staticlong MakeLong(int LoWord, int HiWord)

{

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

}

staticIntPtr MakeLParam(int LoWord, int HiWord)

{

return (IntPtr)MakeLong(LoWord,HiWord);

}

staticint HiWord(int Number)

{

return (Number >> 16) & 0xffff;

}

staticint 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")]

publicvoid Caps()

{

Application.PreTranslateMessage +=

newPreTranslateMessageEventHandler(CapsHandler);

}

[CommandMethod("uncaps")]

publicvoid UnCaps()

{

Application.PreTranslateMessage -=

newPreTranslateMessageEventHandler(CapsHandler);

}

[CommandMethod("vhmouse")]

publicvoid Vhmouse()

{

Application.PreTranslateMessage +=

newPreTranslateMessageEventHandler(VhmouseHandler);

}

[CommandMethod("unvhmouse")]

publicvoid UnVhmouse()

{

Application.PreTranslateMessage -=

newPreTranslateMessageEventHandler(VhmouseHandler);

}

[CommandMethod("watchCC")]

publicvoid WatchCC()

{

Application.PreTranslateMessage +=

newPreTranslateMessageEventHandler(WatchCCHandler);

}

[CommandMethod("unwatchCC")]

publicvoid UnWatchCC()

{

Application.PreTranslateMessage -=

newPreTranslateMessageEventHandler(WatchCCHandler);

}

[CommandMethod("noX")]

publicvoid NoX()

{

Application.PreTranslateMessage +=

newPreTranslateMessageEventHandler(NoXHandler);

}

[CommandMethod("yes")]

publicvoid YesX()

{

Application.PreTranslateMessage -=

newPreTranslateMessageEventHandler(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

elseif (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:

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.