One of the responses to my last post on the “Plugin of the Month” asked about showing information on an AutoCAD drawing object via a tooltip. Other than using the standard rollover tooltip properties mechanism, as shown in this previous post, the best way to achieve this is via a PointMonitor.
In the below C# code we check which object ore objects are being hovered over, get information about those objects and add them to the tooltip.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
public class PointMonitorTooltips
{
[CommandMethod("SM")]
public static void StartMonitor()
{
Editor ed =
Application.DocumentManager.MdiActiveDocument.Editor;
ed.PointMonitor +=
new PointMonitorEventHandler(ed_PointMonitor);
}
[CommandMethod("XM")]
public static void StopMonitor()
{
Editor ed =
Application.DocumentManager.MdiActiveDocument.Editor;
ed.PointMonitor -=
new PointMonitorEventHandler(ed_PointMonitor);
}
static void ed_PointMonitor(object sender, PointMonitorEventArgs e)
{
Editor ed = (Editor)sender;
Document doc = ed.Document;
if (!e.Context.PointComputed)
return;
try
{
// Determine the size of the selection aperture
short pickbox =
(short)Application.GetSystemVariable("PICKBOX");
Point2d extents =
e.Context.DrawContext.Viewport.GetNumPixelsInUnitSquare(
e.Context.ComputedPoint
);
double boxWidth = pickbox / extents.X;
Vector3d vec =
new Vector3d(boxWidth / 2, boxWidth / 2, 0.0);
// Do a crossing selection using a centred box the
// size of the aperture
PromptSelectionResult pse =
ed.SelectCrossingWindow(
e.Context.ComputedPoint - vec,
e.Context.ComputedPoint + vec
);
if (pse.Status != PromptStatus.OK ||
pse.Value.Count <= 0)
return;
// Go through the results of the selection
// and detect the curves
string curveInfo = "";
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
// Open each object, one by one
ObjectId[] ids = pse.Value.GetObjectIds();
foreach (ObjectId id in ids)
{
DBObject obj =
tr.GetObject(id, OpenMode.ForRead);
if (obj != null)
{
// If it's a curve, get its length
Curve cv = obj as Curve;
if (cv != null)
{
double length =
cv.GetDistanceAtParameter(cv.EndParam);
// Then add a message including the object
// type and its length
curveInfo +=
obj.GetType().Name + "'s length: " +
string.Format("{0:F}", length) + "\n";
}
}
}
// Cheaper than aborting
tr.Commit();
}
// Add the tooltip of the lengths of the curves detected
if (curveInfo != "")
e.AppendToolTipText(curveInfo);
}
catch
{
// Not sure what we might get here, but not real action
// needed (worth adding an Exception parameter and a
// breakpoint, in case things need investigating).
}
}
}
During the command we perform a selection, using Editor.SelectCrossingWindow() specifying a window based on the selected point, using a window that’s about the size of the selection aperture (at least that’s what I’m trying to do: let me know if you’ve got a better solution for this). Having some amount of “fuzz” in the selection allows us to not have to hover very precisely over the object, which may or may not be what you’re looking for.
We then iterate through the objects, gathering information about the objects we care about. In this case we’re looking for curves: when we find one, we add its length (along with its object type) to the string we eventually add to the tooltip via AppendToolTipText().
There are two commands: SM adds the monitor, and XM removes it. Bear in mind that the event fires for all sorts of input events - including keystrokes – so you can actually cause problems unless you’re careful. As an example, I wasn’t previously checking the PointComputed status of the PointManager’s context, which is false if we’re looking at keyboard input. The code would go ahead and try to select geometry, which – for some reason unknown to me – cause the character in the keystroke buffer to be lost. Which meant that entering any command would only result in the last command being executed (via the return being processed – that one got through, OK :-). All this to point out that care should be taken when relying on events that are so integrated so deeply into AutoCAD’s user input mechanism.
So let’s see what we get after NETLOADing the module, running the SM command and hovering over a curve, in this case a line. We see a tooltip appear containing the line’s length.
And if we do the same with a lot more intersecting objects, we see that we also get the opportunity to provide information on those. It’s ultimately our choice how we manage the selection process and what we do with the results (although please don’t assume you can attempt everything from this kind of very frequently fired event: there are a number of areas where you may find things failing, especially if there’s any interactive component).
Update:
Thanks to Tony Tanzillo for keeping me honest (see his comments, below). The below code addresses a few issues with the version shown above, and should be used instead.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
public class PointMonitorTooltips
{
[CommandMethod("SM")]
public static void StartMonitor()
{
Editor ed =
Application.DocumentManager.MdiActiveDocument.Editor;
ed.PointMonitor +=
new PointMonitorEventHandler(ed_PointMonitor);
}
[CommandMethod("XM")]
public static void StopMonitor()
{
Editor ed =
Application.DocumentManager.MdiActiveDocument.Editor;
ed.TurnForcedPickOn();
ed.PointMonitor -=
new PointMonitorEventHandler(ed_PointMonitor);
}
static void ed_PointMonitor(object sender, PointMonitorEventArgs e)
{
Editor ed = (Editor)sender;
Document doc = ed.Document;
try
{
FullSubentityPath[] paths =
e.Context.GetPickedEntities();
// Go through the results of the selection
// and detect the curves
string curveInfo = "";
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
// Open each object, one by one
foreach (FullSubentityPath path in paths)
{
ObjectId[] ids = path.GetObjectIds();
if (ids.Length > 0)
{
ObjectId id = ids[ids.GetUpperBound(0)];
DBObject obj =
tr.GetObject(id, OpenMode.ForRead);
if (obj != null)
{
// If it's a curve, get its length
Curve cv = obj as Curve;
if (cv != null)
{
double length =
cv.GetDistanceAtParameter(cv.EndParam) -
cv.GetDistanceAtParameter(cv.StartParam);
// Then add a message including the object
// type and its length
curveInfo +=
obj.GetType().Name + "'s length: " +
string.Format("{0:F}", length) + "\n";
}
}
}
}
// Cheaper than aborting
tr.Commit();
}
// Add the tooltip of the lengths of the curves detected
if (curveInfo != "")
e.AppendToolTipText(curveInfo);
}
catch
{
// Not sure what we might get here, but not real action
// needed (worth adding an Exception parameter and a
// breakpoint, in case things need investigating).
}
}
}