Friday, August 12, 2016

Showing Current Selection in a Modeless Form

There is a question post in the AutoCAD .NET discussion forum here. I happened to have a similar requirement in one of my recent development projects. So, instead of post a reply in the discussion forum there, I though it would be easier to post a relatively simple and run-able project for the benefit of all readers.

The project is fairly easy and is comprised of 3 classes.

Class CurrentSelectionWatcher:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace ShowSelectionChange
{
    public class CurrentSelectionWatcher
    {
        private frmSelection _view = null;
        private bool _show = false;
        private Document _curDoc = null;
        public CurrentSelectionWatcher()
        {
            _curDoc = CadApp.DocumentManager.MdiActiveDocument;
            if (_curDoc != null)
            {
                SetView();
                _curDoc.ImpliedSelectionChanged += 
                    Document_ImpliedSelectionChanged;
            }
 
            CadApp.DocumentManager.DocumentBecameCurrent += 
                DocumentManager_DocumentBecameCurrent;
        }
 
        public bool IsShown
        {
            get { return _show; }
        }
 
        private void Document_ImpliedSelectionChanged(
            object sender, EventArgs e)
        {
            SetView();
        }
        
        private void DocumentManager_DocumentBecameCurrent(
            object sender, DocumentCollectionEventArgs e)
        {
            if (_curDoc!=null)
            {
                _curDoc.ImpliedSelectionChanged -= 
                    Document_ImpliedSelectionChanged;
            }
 
            if (e.Document!=null)
            {
                if (e.Document != _curDoc)
                {
                    ClearView();
                    SetView();
                    _curDoc = e.Document;
                    _curDoc.ImpliedSelectionChanged += 
                        Document_ImpliedSelectionChanged;
                }
            }
        }
 
        public void Show()
        {
            _view = new frmSelection();
            _view.VisibleChanged += _view_VisibleChanged;
            CadApp.ShowModelessDialog(_view);
            _show = true;
        }
 
        private void _view_VisibleChanged(object sender, EventArgs e)
        {
            if (!_view.Visible)
            {
                _view.Dispose();
                _view = null;
                _show = false;
            }
        }
 
        #region private methods
 
        private void ClearView()
        {
            _view.ClearView();
        }
 
        private void SetView()
        {
            if (!_show) return;
            if (!_curDoc.IsActive) return;
            
            var res = _curDoc.Editor.SelectImplied();
            if (res.Status==PromptStatus.OK)
            {
                _view.SetSelection(res.Value.GetObjectIds());
            }
            else
            {
                _view.ClearView();
            }
        }
 
        #endregion
    }
}

Then a Windows.Form class frmSelection:



This form contains a ListView, a Button and a Label.

Here is the code-behind:
using System;
using System.Windows.Forms;
 
using Autodesk.AutoCAD.DatabaseServices;
 
namespace ShowSelectionChange
{
    public partial class frmSelection : Form
    {
        public frmSelection()
        {
            InitializeComponent();
 
            ClearView();
        }
 
        public void ClearView()
        {
            lvSelection.Items.Clear();
            lblCount.Text = "Selected: 0";
        }
 
        public void SetSelection(ObjectId[] entIds)
        {
            ClearView();
 
            foreach (var id in entIds)
            {
                var item = new ListViewItem(id.ToString());
                item.SubItems.Add(id.ObjectClass.DxfName);
                item.Selected = false;
 
                lvSelection.Items.Add(item);           
            }
 
            lblCount.Text = "Selected: " + 
                lvSelection.Items.Count.ToString();
        }
 
        private void btnClose_Click(object sender, EventArgs e)
        {
            Visible = false;
        }
    }
}

And finally, here the the CommandClass that runs the code:
using Autodesk.AutoCAD.Runtime;
 
[assemblyCommandClass(typeof(ShowSelectionChange.Commands))]
 
namespace ShowSelectionChange
{
    public class Commands
    {
        private static CurrentSelectionWatcher _selectionCounter = null;
 
        [CommandMethod("WatchSelection"CommandFlags.Session)]
        public static void DoSessionCommand()
        {
            if (_selectionCounter==null)
            {
                _selectionCounter = new CurrentSelectionWatcher();
            }
 
            if (!_selectionCounter.IsShown) _selectionCounter.Show();
        }
    }
}

Here is a video clip shows how the code works.

Obviously, I can make the floating form as static and only show/hide it instead of creating new instance/disposing. Of course the modeless form can be substituted by a PaletteSet easily.

P.S. When I copied the code a few hours ago, I missed a couple of lines, which actually makes the execution appear having a bug. The missing lines has been added (in red). I also missed the link to the video clip.

Extra Code

In the following discussion on the topic in AutoCAD .NET API forum, the OP asked if I could show sample code with a PaletteSet. So, here are some extra codee, working in this manner:

  • When only one entity is selected/highlighted, and the entity is a lightweight polyline, a floating PaletteSet with single palette shows the polyline's information;
  • When more than one entities are selected, or no entity is selected, the PaletteSet hides automatically.
The codes are as following.

First, I created another selection watching class CurrentSelectionWatcherA.cs:

using System;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
using Autodesk.AutoCAD.DatabaseServices;
 
namespace ShowSelectionChange
{
    public class CurrentSelectionWatcherA
    {
        private MyPolylinePaletteSet _ps = null;
        private Document _curDoc = null;
 
        public CurrentSelectionWatcherA()
        {
            _ps = new MyPolylinePaletteSet();
 
            _curDoc = CadApp.DocumentManager.MdiActiveDocument;
            if (_curDoc != null)
            {
                SetView();
                _curDoc.ImpliedSelectionChanged += 
                    Document_ImpliedSelectionChanged;
            }
 
            CadApp.DocumentManager.DocumentBecameCurrent += 
                DocumentManager_DocumentBecameCurrent;
        }
 
        private void Document_ImpliedSelectionChanged(
            object sender, EventArgs e)
        {
            SetView();
        }
        
        private void DocumentManager_DocumentBecameCurrent(
            object sender, DocumentCollectionEventArgs e)
        {
            if (_curDoc!=null)
            {
                _curDoc.ImpliedSelectionChanged -= 
                    Document_ImpliedSelectionChanged;
            }
 
            if (e.Document!=null)
            {
                if (e.Document != _curDoc)
                {
                    SetView();
                    _curDoc = e.Document;
                    _curDoc.ImpliedSelectionChanged += 
                        Document_ImpliedSelectionChanged;
                }
            }
        }
 
        #region private methods
 
        private void SetView()
        {
            if (!_curDoc.IsActive)
            {
                _ps.Visible = false;
                return;
            }
 
            ObjectId polyId = ObjectId.Null;
            var res = _curDoc.Editor.SelectImplied();
            if (res.Status==PromptStatus.OK)
            {
                if (res.Value.Count==1)
                {
                    var id = res.Value[0].ObjectId;
                    if (id.ObjectClass.DxfName.ToUpper()=="LWPOLYLINE")
                    {
                        polyId = id;
                    }
                }            
            }
            
            if (polyId.IsNull)
            {
                _ps.Visible = false;
            }
            else
            {
                _ps.ShowPolyline(polyId);
                _ps.Visible = true;
            }
        }
 
        #endregion
    }
}

Secondly, here is the PaletteSet class MyPolylinePaletteSet.cs:

using System;
using System.Drawing;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Windows;
 
namespace ShowSelectionChange
{
    public class MyPolylinePaletteSet : PaletteSet
    {
        private PolylinePalette _palette = null;
 
        public MyPolylinePaletteSet():
            base("My Polyline","",new Guid("D07DBD90-4D4C-44DB-A6C4-3CC030BD0520"))
        {
            Style = PaletteSetStyles.UsePaletteNameAsTitleForSingle;
            DockEnabled = DockSides.None;
            Dock = DockSides.None;
            Size = new Size(300, 200);
            MinimumSize = new Size(300, 200);
 
            _palette = new PolylinePalette();
            Add("My Polyline", _palette);
        }
 
        public void ShowPolyline(ObjectId polyId)
        {
            string id = "";
            string len = "";
 
            using (var tran = polyId.Database.TransactionManager.StartTransaction())
            {
                var poly = tran.GetObject(polyId, OpenMode.ForRead) as Polyline;
                if (poly!=null)
                {
                    id = polyId.ToString();
                    len = poly.Length.ToString("########0.000");
                }
 
                _palette.ShowData(id, len);
            }
        }
    }
}

This is the UserControl used as single Palette: PolylinePalette.cs:




using System.Windows.Forms;
 
namespace ShowSelectionChange
{
    public partial class PolylinePalette : UserControl
    {
        public PolylinePalette()
        {
            InitializeComponent();
        }
 
        public void ShowData(string id, string length)
        {
            txtId.Text = id;
            txtLength.Text = length;
        }
    }
}

Finally, this is the CommandClass that run the code (the red code is for this addition):


using Autodesk.AutoCAD.Runtime;
 
[assemblyCommandClass(typeof(ShowSelectionChange.Commands))]
 
namespace ShowSelectionChange
{
    public class Commands
    {
        private static CurrentSelectionWatcher _selectionCounter = null;
        private static CurrentSelectionWatcherA _polyWatcher = null;
 
        [CommandMethod("WatchSelection"CommandFlags.Session)]
        public static void WatchImpliedSelection()
        {
            if (_selectionCounter==null)
            {
                _selectionCounter = new CurrentSelectionWatcher();
            }
 
            _selectionCounter.Show();
        }
 
        [CommandMethod("ShowPoly"CommandFlags.Session)]
        public static void WatchSelectedPolyline()
        {
            if (_polyWatcher==null)
            {
                _polyWatcher = new CurrentSelectionWatcherA();
            }
        }
    }
}

Now see this video clip for the action.

If one wants to let the PaletteSet stay or docked whether there is single polyline selected or not, the code can be easily modified to do that.

The technique shown here can be used as dynamic entity information tip, similar to QuickProperties Window (well, not as pretty).





Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.