Wednesday, March 18, 2020

Showing Helpful Information As Tool Tip During Jig Dragging - 2

In the first part of this topic, I have built a quite simple moving jig by handling Editor.PointMonitor, where Transient Graphics is used to show a ghost image as the dragging effect. Because of PointMonitorEventArgs, it is really easy to show custom tool tip to provide useful information that would help user to decide where/how to drag an entity.

At this point, I have a good working moving jig that could prompt some information during entity dragging. However a new issue comes: how to make the jig to show different information as tool tip, based on business workflow, without having to modify the jig's code? When mouse cursor is at an entity during dragging, we now can get the entity's ObjectId, then we could obtain different information according to business requirements and show the information as tool tip, if necessary. Obviously, we do not want to modify the code in the PointMonitor event handler whenever there is different business requirement.

The approach to solve this is to inject a predefined tool tip generating interface functions, and the interface functions are implemented/coded outside the jig class. Following is the updated jig class code (red lines are the changes).

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
 
namespace JigWithTooltip
{
    public class TooltipMovingJig2
    {
        private Document _dwg = null;
        private Editor _ed = null;
 
        private Entity _ghost = null;
        private Entity _entity = null;
        private Point3d _basePoint = Point3d.Origin;
        private Point3d _mousePoint = Point3d.Origin;
 
        private TransientManager _tsManager = 
            TransientManager.CurrentTransientManager;
 
        private Func<ObjectId, Point3d, string> _tipExtractFunction = null;
        private Func<ObjectId, bool> _isTooltipTargetFunc = null;
 
        public TooltipMovingJig2(Document dwg)
        {
            _dwg = dwg;
            _ed = dwg.Editor;
        }
 
        public void MoveEntity(
            Func<ObjectId, Point3d, string> tipExtractFunc = null,
            Func<ObjectId, bool> isTooltipTargetFunc = null)
        {
            if (!SelectEntity(out ObjectId entIdout _basePoint)) return;
 
            _tipExtractFunction = tipExtractFunc;
            _isTooltipTargetFunc = isTooltipTargetFunc;
 
            _mousePoint = _basePoint;
 
            using (var tran = _dwg.TransactionManager.StartTransaction())
            {
                _entity = (Entity)tran.GetObject(entIdOpenMode.ForRead);
                _entity.Highlight();
                try
                {
                    if (GetDestinationPoint(out Point3d destPoint))
                    {
                        var mt = Matrix3d.Displacement(
                            _basePoint.GetVectorTo(destPoint));
                        _entity.UpgradeOpen();
                        _entity.TransformBy(mt);
                    }
                }
                finally
                {
                    _entity.Unhighlight();
                }
 
                tran.Commit();
            }
        }
 
        #region private methods
 
        private bool SelectEntity(out ObjectId entIdout Point3d basePoint)
        {
            entId = ObjectId.Null;
            basePoint = Point3d.Origin;
 
            var res = _ed.GetEntity("\nSelect entity to move:");
            if (res.Status == PromptStatus.OK)
            {
                entId = res.ObjectId;
                basePoint = res.PickedPoint;
 
                var opt = new PromptPointOptions(
                    "\nSelect base point:");
 
                var pRes = _ed.GetPoint(opt);
                if (pRes.Status == PromptStatus.OK)
                {
                    basePoint = pRes.Value;
                }
 
                return true;
            }
            else
            {
                return false;
            }
        }
 
        private void CreateMovingGhost()
        {
            ClearMovingGhost();
 
            _ghost = _entity.Clone() as Entity;
            _ghost.ColorIndex = 2;
            var mt = Matrix3d.Displacement(_basePoint.GetVectorTo(_mousePoint));
            _ghost.TransformBy(mt);
 
            _tsManager.AddTransient(
                _ghost, 
                TransientDrawingMode.DirectTopmost, 
                128, 
                new IntegerCollection());
        }
 
        private void ClearMovingGhost()
        {
            if (_ghost != null)
            {
                _tsManager.EraseTransient(_ghost, new IntegerCollection());
                _ghost.Dispose();
                _ghost = null;
            }
        }
 
        private bool GetDestinationPoint(out Point3d destPoint)
        {
            destPoint = Point3d.Origin;
            var picked = false;
 
            var opt = new PromptPointOptions(
                "Move to:");
            opt.UseBasePoint = true;
            opt.BasePoint = _basePoint;
            opt.UseDashedLine = true;
 
            // Set system variable "PICKBOX" to at least 10 (range 0 to 20)
            // so that mouse cursor would pick up entities easily 
            // when moveving close
            var pickBox = Convert.ToInt32(
                Application.GetSystemVariable("PICKBOX"));
            bool pickBoxChanged = false;
            if (pickBox < 10)
            {
                Application.SetSystemVariable("PICKBOX", 10);
                pickBoxChanged = true;
            }
 
            var forcedCount = _ed.TurnForcedPickOn();
            _ed.PointMonitor += Editor_PointMonitor;
 
            try
            {
                var res = _ed.GetPoint(opt);
                if (res.Status == PromptStatus.OK)
                {
                    destPoint = res.Value;
                    picked = true;
                }
            }
            finally
            {
                ClearMovingGhost();
                _ed.PointMonitor -= Editor_PointMonitor;
 
                var count = _ed.TurnForcedPickOff();
                while (count > forcedCount - 1)
                {
                    count = _ed.TurnForcedPickOff();
                }
 
                // restore "PICKBOX" original value
                if (pickBoxChanged)
                    Application.SetSystemVariable("PICKBOX"pickBox);
            }
 
            return picked;
        }
 
        private void Editor_PointMonitor(object senderPointMonitorEventArgs e)
        {
            _mousePoint = e.Context.RawPoint;
            CreateMovingGhost();
 
            var paths = e.Context.GetPickedEntities();
            if (paths == null || paths.Length == 0) return;
            var ids = paths[0].GetObjectIds();
            if (ids == null || ids.Length == 0) return;
 
            var id = ids[0];
 
            if (_isTooltipTargetFunc != null)
            {
                if (!_isTooltipTargetFunc(id))
                {
                    id = ObjectId.Null;
                }
            }
            
            if (!id.IsNull)
            {
                var tip = GetDefaultTooltip(id);
                if (_tipExtractFunction != null)
                {
                    tip = _tipExtractFunction(id, _mousePoint);
                }
 
                e.AppendToolTipText(tip);
            }
        }
 
        private string GetDefaultTooltip(ObjectId entId)
        {
            return $"\nMove to/close to:{entId.ObjectClass.DxfName.ToUpper()}";
        }
 
        #endregion
    }
}

As the code shows, the jig class now has 2 Functions as its member, which can be injected from the jig's calling procedure. One function is to determine if an entity is the target entity that I want to show custom tool tip; the other is to generate actual tool tip content. Both function take ObjectId as input parameter; and the tool til generating function also takes a Point3d input as parameter, which is where the mouse cursor is and may be needed for specific tool tip content.

The jig class only defines the 2 function's signature (interface). The actual implementations of the 2 functions are done outside the jig class. They are injected into the jig class when the jig's public method MoveEntity() is called. Thus I am free to write different functions to determine whether an entity is the tool tip showing target and what tool tip content to be generated against the entity.

Following are 2 pairs of these functions: one pair is to test if the entity is closed polyline, if yes, get its area information as tool tip; the other pair - to test if the entity is curve, and show the distance of mouse cursor point to the curve's start point as tool tip information.

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
 
namespace JigWithTooltip
{
    public class EntityToolTipHelper
    {
        public static bool IsAreaToolTipTarget(ObjectId entId)
        {
            return entId.ObjectClass == RXClass.GetClass(typeof(Polyline));
        }
 
        public static string ExtractAreaToolTip(ObjectId entIdPoint3d mouseLocation)
        {
            if (entId.ObjectClass != RXClass.GetClass(typeof(Polyline))) return "";
 
            var tip = "";
 
            using (var tran = new OpenCloseTransaction())
            {
                var poly = (Polyline)tran.GetObject(entIdOpenMode.ForRead);
                if (poly.Closed)
                {
                    tip =$"AREA: {poly.Area.ToString("##########0.00")}";
                }
            }
 
            return tip;
        }
 
        public static bool IsDistanceToolTipTarget(ObjectId entId)
        {
            return entId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Curve)));
        }
 
        public static string ExtractDistanceToolTip(ObjectId entIdPoint3d mousePoint)
        {
            if (!entId.ObjectClass.IsDerivedFrom(RXClass.GetClass(typeof(Curve)))) return "";
 
            var tip = "";
 
            using (var tran = new OpenCloseTransaction())
            {
                var curve = (Curve)tran.GetObject(entIdOpenMode.ForRead);
                var pt = curve.GetClosestPointTo(mousePointfalse);
                var dist = curve.GetDistAtPoint(pt);
 
                tip = $"DISTANCE FROM START POINT: {dist.ToString("########0.00")}";
            }
 
            return tip;
        }
    }
}

Now I can have a command to run the moving jig with area being prompted and another command to run the moving jig with distance being prompted:

using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(JigWithTooltip.MyCommands))]
 
namespace JigWithTooltip
{
    public class MyCommands
    {
        [CommandMethod("DoMove")]
        public static void RunMyCommand()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                var mover = new TooltipMovingJig2(dwg);
                mover.MoveEntity();
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nInitializing error:\n{ex.Message}\n");
            }
        }
 
        [CommandMethod("MoveToArea")]
        public static void MoveWithAreaPrompt()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                var mover = new TooltipMovingJig2(dwg);
                mover.MoveEntity(
                    EntityToolTipHelper.ExtractAreaToolTip,
                    EntityToolTipHelper.IsAreaToolTipTarget);
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nInitializing error:\n{ex.Message}\n");
            }
        }
 
        [CommandMethod("MoveToDistance")]
        public static void MoveWithDistancePrompt()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                var mover = new TooltipMovingJig2(dwg);
                mover.MoveEntity(
                    EntityToolTipHelper.ExtractDistanceToolTip,
                    EntityToolTipHelper.IsDistanceToolTipTarget);
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage($"\nInitializing error:\n{ex.Message}\n");
            }
        }
    }
}

As the code shows, it is very easy to implement a pair of functions outside the jig class code to make the jig smart enough to decide whether custom tool tip is wanted, and what tool tip content is to appear, Here the a video clip showing the visual effect of running the commands:


No comments:

Post a Comment