In a occassion when picking a series of entities, the user may not only wants to have the picked entity highlighted, but also wants a rubber band line drawn from the previously picked point (or point derived from the previously picked entity) to the moving mouse cursor before picking next entity.
As we know, using PromptPointOption in conjunction with Editor.GetPoint(), AutoCAD draws a rubber band line, if PromptPointOptions.UseBasePoint is set true and a base point is supplied. However, to pick an entity, PromptEntityOptions and Editor.GetEntity() do not provide such option as "UseBasePoint".
Here is my way to solve this issue. After the first entity is picked, I handle Editor.PointMonitor event, so that when user moves mouse to pick next entity, a Transient Graphics (Line) is drawn in the event handler. The code is fairly simple, as following.
First, as I usually do, I created a class to encapsulate the wanted functionality: PickEntities.
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
namespace PickMultiplePoints
{
public class PickEntities
{
private Document _dwg;
private Editor _editor;
private List_points = null;
private List_entities = null;
private Line _rubberLine = null;
private Point3d _basePoint;
private Point3d _currentPoint;
public PickEntities(Document dwg)
{
_dwg = dwg;
_editor = _dwg.Editor;
}
#region public properties
public ListPickedPoints
{
get { return _points; }
}
public ListPickedEntities
{
get { return _entities; }
}
#endregion
public void DoPick(bool unhighlightAtEnd)
{
//Pick first entity
ObjectId id;
Point3d pt;
if (!PickFirstEntity(out id, out pt)) return;
_rubberLine = null;
_points = new List();
_entities = new List();
_points.Add(pt);
_entities.Add(id);
SetHighlight(id, true);
//Start drag rubber band line
while (true)
{
if (!PickNextEntity()) break;
}
//Unhighlight all picked entities
if (unhighlightAtEnd)
{
foreach (ObjectId entId in _entities)
SetHighlight(entId, false);
}
}
#region private methods
private bool PickFirstEntity(
out ObjectId id, out Point3d pt)
{
pt = new Point3d();
id = ObjectId.Null;
PromptEntityOptions opt =
new PromptEntityOptions("\nPick an entity: ");
PromptEntityResult res = _editor.GetEntity(opt);
if (res.Status == PromptStatus.OK)
{
id = res.ObjectId;
//if the entity is a BlockRefernece, get Insertion Point
//otherwise get the picked point
Point3d p;
if (GetBlockInsPoint(res.ObjectId, out p))
pt = p;
else
pt = res.PickedPoint;
return true;
}
else
{
return false;
}
}
private bool GetBlockInsPoint(ObjectId id, out Point3d insPt)
{
insPt=new Point3d();
bool isBlk = false;
using (Transaction tran = _dwg.Database.
TransactionManager.StartTransaction())
{
BlockReference blk = tran.GetObject(
id, OpenMode.ForRead) as BlockReference;
if (blk != null)
{
insPt = blk.Position;
isBlk = true;
}
tran.Commit();
}
return isBlk;
}
private bool PickNextEntity()
{
_basePoint = _points[_points.Count - 1];
string msg =
"\n" + _points.Count + " picked. Pick next entity: ";
PromptEntityOptions opt = new PromptEntityOptions(msg);
try
{
//Create rubber band line
_rubberLine = new Line(_basePoint, _basePoint);
//Set line properties, for example
_rubberLine.SetDatabaseDefaults(_dwg.Database);
//Create Transient graphics
IntegerCollection intCol = new IntegerCollection();
TransientManager.CurrentTransientManager.
AddTransient(_rubberLine,
TransientDrawingMode.DirectShortTerm, 128, intCol);
_editor.PointMonitor +=
new PointMonitorEventHandler(_editor_PointMonitor);
PromptEntityResult res =_editor.GetEntity(opt);
if (res.Status == PromptStatus.OK)
{
bool exists = false;
foreach (ObjectId ent in _entities)
{
if (ent == res.ObjectId)
{
exists = true;
break;
}
}
if (!exists)
{
//if the entity is a BlockRefernece,
//get Insertion Point.
//Otherwise get the picked point
Point3d p;
if (!GetBlockInsPoint(res.ObjectId, out p))
{
p = res.PickedPoint;
}
_points.Add(p);
_entities.Add(res.ObjectId);
SetHighlight(res.ObjectId, true);
}
else
{
_editor.WriteMessage(
"\nThe entity has already been picked!");
}
return true;
}
else
{
return false;
}
}
finally
{
if (_rubberLine != null)
{
//Clear transient graphics
IntegerCollection intCol = new IntegerCollection();
TransientManager.CurrentTransientManager.
EraseTransient(_rubberLine, intCol);
_rubberLine.Dispose();
_rubberLine = null;
}
_editor.PointMonitor -=
new PointMonitorEventHandler(_editor_PointMonitor);
}
}
private void SetHighlight(ObjectId id, bool highlight)
{
using (Transaction tran = _dwg.Database.
TransactionManager.StartTransaction())
{
Entity ent = (Entity)tran.GetObject(id, OpenMode.ForWrite);
if (highlight)
ent.Highlight();
else
ent.Unhighlight();
}
}
private void _editor_PointMonitor(
object sender, PointMonitorEventArgs e)
{
//Get mouse cursor point
_currentPoint = e.Context.RawPoint;
//Update line
_rubberLine.EndPoint = _currentPoint;
//Update Transient graphics
IntegerCollection intCol = new IntegerCollection();
TransientManager.CurrentTransientManager.
UpdateTransient(_rubberLine, intCol);
}
#endregion
}
}
Then, I use "PickEntities" class in a command method of a command class:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
[assembly: CommandClass(typeof(PickMultiplePoints.MyCommands))]
namespace PickMultiplePoints
{
public class MyCommands
{
[CommandMethod("MyPick")]
public static void DoCommand()
{
Document dwg = Autodesk.AutoCAD.ApplicationServices.
Application.DocumentManager.MdiActiveDocument;
Editor ed = dwg.Editor;
//Do picking
PickEntities picker = new PickEntities(dwg);
picker.DoPick(true);
ObjectId[] ids = picker.PickedEntities.ToArray();
ed.WriteMessage(
"\nMy command executed: {0} entities picked.",
ids.Length);
}
}
}
Here is the video clip of the code in action.
Use the technique presented in this article, one can also makes AutoCAD draw a polyline started from the first picked entity/point along the picked entities dynamically.