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.