Wednesday, May 31, 2017

Swap Layout Order

A question was posted in the popular CAD programming discussion forum here, regarding re-ordering layout tabs (setting Layout.TabOrder). In this discussion thread, one of my posts published here quite a few years ago was mentioned.

While the situation is a bit different from the situation described in my old post, I thought the way to re-order layout should be the same: set TabOrder property to desired number.

However, the TabOrder cannot be duplicated, and cannot be 0 (which is always the TabOrder of "MODEL" layout). So, when re-ordering a Layout tab, one must realise that a layout tab can either not be re-ordered (the first one cannot be moved down, the last one cannot be moved up), or the move will affect the layout before or after it (that is, the affected layout needs to swap its TabOrder with the one you want to re-order).

Another thing to know is, once you have Layout object open in a Transaction, you can set its TabOrder to a number that is currently assigned to other Layout. For example, you have 3 layouts. You open the Layout object in the middle, which has TabOrder=2. Then you can have code

Layout2.TabOrder=1

At this time, the first layout also has TabOrder=1. When the code executes the line Layout2.TabOrder=1, there is no exception raised. Error only raise when the Transaction is committing, because of the duplicated TabOrder.

OK, enough explanation. Here is my sample project showing how easy to re-order layout tabs by swapping TabOrder values of 2 Layout objects.

Firstly, I wrote a class doing the CAD processing, including retrieving Layout information for UI, and the TabOrder swaping code:
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
namespace SwapLayoutTabs
{
    public class CadUtil
    {
        public static IEnumerable<LayoutInfo> GetLayoutInformation()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
 
            var lst = new List<LayoutInfo>();
 
            using (var tran = dwg.TransactionManager.StartTransaction())
            {
                var dic = (DBDictionary)tran.GetObject(
                    dwg.Database.LayoutDictionaryId, OpenMode.ForRead);
                foreach (DBDictionaryEntry entry in dic)
                {
                    var layout = (Layout)tran.GetObject(entry.Value, OpenMode.ForRead);
                    if (layout.LayoutName.ToUpper() != "MODEL")
                    {
                        lst.Add(new LayoutInfo()
                        {
                            LayoutId = entry.Value,
                            TabOrder = layout.TabOrder,
                            LayoutName = layout.LayoutName
                        });
                    }
                }
                tran.Commit();
            }
 
            return from l in lst orderby l.TabOrder ascending select l;
        }
 
        public static void SwapLayoutTabOrder(ObjectId lay1Id, ObjectId lay2Id)
        {
            using (var tran = lay1Id.Database.TransactionManager.StartTransaction())
            {
                var layout1 = (Layout)tran.GetObject(lay1Id, OpenMode.ForWrite);
                var layout2 = (Layout)tran.GetObject(lay2Id, OpenMode.ForWrite);
 
                var order1 = layout1.TabOrder;
                var order2 = layout2.TabOrder;
 
                layout1.TabOrder = order2;
                layout2.TabOrder = order1;
 
                tran.Commit();
            }
        }
 
        public static void UpdateEditorScreen()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            dwg.Editor.Regen();
        }
    }
}


I created a dialog box to allow user to re-order the layout:


To fill up the ListView and then let user to manipulate the layout orders I create a class LayoutInfo to hold layout information:
using Autodesk.AutoCAD.DatabaseServices;
 
namespace SwapLayoutTabs
{
    public class LayoutInfo
    {
        public string LayoutName { setget; }
        public int TabOrder { setget; }
        public ObjectId LayoutId { setget; }
    }
}

Here is the dialog form's code-behind:
using System;
using System.Windows.Forms;
 
using Autodesk.AutoCAD.DatabaseServices;
 
namespace SwapLayoutTabs
{
    public partial class dlgLayoutOrder : Form
    {
        public dlgLayoutOrder()
        {
            InitializeComponent();
        }
 
        private void SetListView()
        {
            var layouts = CadUtil.GetLayoutInformation();
 
            lvLayouts.Items.Clear();
 
            foreach (var layout in layouts)
            {
                var item = new ListViewItem(layout.TabOrder.ToString());
                item.SubItems.Add(layout.LayoutName);
                item.Tag = layout.LayoutId;
                item.Selected = false;
 
                lvLayouts.Items.Add(item);
            }
 
            btnUp.Enabled = false;
            btnDown.Enabled = false;
        }
 
        private void OrderLayoutTab(bool increment)
        {
            var selectedIndex = lvLayouts.SelectedItems[0].Index;
            var otherIndex = increment ? selectedIndex + 1 : selectedIndex - 1;
 
            ObjectId id1 = (ObjectId)lvLayouts.Items[selectedIndex].Tag;
            ObjectId id2 = (ObjectId)lvLayouts.Items[otherIndex].Tag;
 
            try
            {
                Cursor = Cursors.WaitCursor;
 
                CadUtil.SwapLayoutTabOrder(id1, id2);
                CadUtil.UpdateEditorScreen(); //Regen is require to actually see the tab change in AutoCAD editor
                SetListView();
            }
            catch(System.Exception ex)
            {
                MessageBox.Show("Swapping layout tab error:\n\n" + ex.Message);
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }
 
        private void lvLayouts_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvLayouts.SelectedItems.Count == 0)
            {
                btnUp.Enabled = false;
                btnDown.Enabled = false;
            }
            else
            {
                var selectedIndex = lvLayouts.SelectedItems[0].Index;
                btnUp.Enabled = selectedIndex > 0;
                btnDown.Enabled = selectedIndex < lvLayouts.Items.Count - 1;
            }
        }
 
        private void btnUp_Click(object sender, EventArgs e)
        {
            OrderLayoutTab(false);
        }
 
        private void btnDown_Click(object sender, EventArgs e)
        {
            OrderLayoutTab(true);
        }
 
        private void btnClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }
 
        private void dlgLayoutOrder_Load(object sender, EventArgs e)
        {
            SetListView();
        }
    }
}

And finally, this the command class:
using Autodesk.AutoCAD.Runtime;
using CadApp = Autodesk.AutoCAD.ApplicationServices.Application;
 
[assemblyCommandClass(typeof(SwapLayoutTabs.MyCommands))]
 
namespace SwapLayoutTabs
{
    public class MyCommands 
    {
        [CommandMethod("OrderLayouts")]
        public static void SetLayoutOrders()
        {
            var dwg = CadApp.DocumentManager.MdiActiveDocument;
            var ed = dwg.Editor;
 
            try
            {
                using (var dlg = new dlgLayoutOrder())
                {
                    CadApp.ShowModalDialog(dlg);
                }
            }
            catch (System.Exception ex)
            {
                ed.WriteMessage("\nError: {0}", ex.Message);
                ed.WriteMessage("\n*Cancel*");
            }
            finally
            {
                Autodesk.AutoCAD.Internal.Utils.PostCommandPrompt();
            }
        }
    }
}

Go to this video clip to see how the code works.














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.