November 04, 2011

Handling protocol changes to AutoCAD’s table in .NET

I have been re-building some old code in VS2010 using the 2012 ObjectARX SDK and came across some 'blablabla is obsolete: use thingy instead' warnings.

The code still compiles and runs fine but I want to start thinking about the future.

a few examples will follow, please add any others you come across.

[…]

The next was to do with the Table Class i.e. Autodesk.AutoCAD.DatabaseServices.Table

In fact, there are a myriad of obsolete warnings in the Table class ... some of them appear to have no documented resolution, some appear to not work using the suggested resolution.

As Kerry said, further down the thread, he felt it was time to “phone a friend”, which I assume (or hope) means me (although he did email rather than phone me :-). In looking into this, I had some help from Stephen Preston (who pointed me at some information previously provided by Philippe Leefsma and Balaji Ramamoorthy, which helped a great deal).

Let’s focus on the changes to the Table protocol that now (and I’m not sure when exactly it changed – perhaps in AutoCAD 2009?) results in warnings in posts such as this one.

To illustrate some of the main changes in the Table API, I’ve created some C# code that creates two identical tables, one using the old approach and one using the newer, non-deprecated protocol:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System;

namespace TableCreation

{

publicclassCommands

{

[CommandMethod("TWOTABLES")]

publicvoid CreateTwoTables()

{

Document doc =

Application.DocumentManager.MdiActiveDocument;

Database db = doc.Database;

Transaction tr =

doc.TransactionManager.StartTransaction();

using (tr)

{

// Set some constants that we'll use to create our tables

constint numRows = 7;

constint numCols = 5;

constdouble rowHeight = 3;

constdouble colWidth = 3;

constdouble horMarg = 0.5;

constdouble verMarg = 0.8;

constdouble txtHeight = 1;

// We'll add our tables to the current space

BlockTableRecord btr =

(BlockTableRecord)tr.GetObject(

db.CurrentSpaceId,

OpenMode.ForWrite

);

// We'll use scoping to let us use the same variable

// name(s) for both tables we're creating

{

// Create a table with the old protocol

Table tb = newTable();

tb.TableStyle = db.Tablestyle;

// Resize the table, adding rows and columns

tb.NumRows = numRows;

tb.NumColumns = numCols;

// Set the column width and row height

tb.SetColumnWidth(colWidth);

tb.SetRowHeight(rowHeight);

// Set the horizontal and vertical margins

tb.HorizontalCellMargin = horMarg;

tb.VerticalCellMargin = verMarg;

// Make sure the header and title rows are visible

tb.IsHeaderSuppressed = false;

tb.IsTitleSuppressed = false;

// Set the title string

tb.SetTextString(0, 0, "Old Table");

// Set the text height for the full table: value of 7

// is bit-encoded Data (1), Title (2) and Header (4)

tb.SetTextHeight(txtHeight, 7);

// Populate the contents of the header and data sections

int n = 0;

for (int i = 1; i < numRows; i++)

{

for (int j = 0; j < numCols; j++)

{

// Contents is A-E for the header an 0-n for the data

string contents =

(i == 1 ?

Convert.ToChar(

Convert.ToInt16('A') + j

).ToString() :

n++.ToString()

);

tb.SetTextString(i, j, contents);

}

}

tb.GenerateLayout();

btr.AppendEntity(tb);

tr.AddNewlyCreatedDBObject(tb, true);

}

{

// Create a table with the new protocol

Table tb = newTable();

tb.TableStyle = db.Tablestyle;

// Set the height and width of the initial row and

// column belonging by default to the blank Table

tb.Rows[0].Height = rowHeight;

tb.Columns[0].Width = colWidth;

// Add the remaining rows and columns

tb.InsertRows(1, rowHeight, numRows - 1);

tb.InsertColumns(1, colWidth, numCols - 1);

// To query the number of rows and columns...

//

// int nRows = tb.Rows.Count;

// int nCols = tb.Columns.Count;

// To "suppress" the title and header, find the row

// with a style of "Title" or "Header" and set it to ""

tb.Cells[0, -1].Style = "Title";

tb.Cells[1, -1].Style = "Header";

// Add the contents of the Title cell

Cell tc = tb.Cells[0, 0];

tc.Contents.Add();

tc.Contents[0].TextHeight = txtHeight;

tc.Contents[0].TextString = "New Table";

// Populate the contents of the header and data sections

int n = 0;

for (int i = 1; i < numRows; i++)

{

for (int j = 0; j < numCols; j++)

{

// Contents is A-E for the header an 0-n for the data

string contents =

(i == 1 ?

Convert.ToChar(

Convert.ToInt16('A') + j

).ToString() :

n++.ToString()

);

Cell c = tb.Cells[i, j];

// Set the text contents of the cell

c.Contents.Add();

c.Contents[0].TextHeight = txtHeight;

c.Contents[0].TextString = contents;

// Set the horizontal margins

c.Borders.Left.Margin = horMarg;

c.Borders.Right.Margin = horMarg;

// Set the vertical margins

c.Borders.Top.Margin = verMarg;

c.Borders.Bottom.Margin = verMarg;

}

}

// We'll separate this table from the first one

tb.Position = newPoint3d(20, 0, 0);

tb.GenerateLayout();

btr.AppendEntity(tb);

tr.AddNewlyCreatedDBObject(tb, true);

}

tr.Commit();

}

}

}

}

Before we take a look at the changes needed to the first code to get the second, let’s see the warnings generated by the first code section when we build the project in Visual Studio (I’ve removed the Autodesk.AutoCAD.DatabaseServices. namespace prefix from each of the following lines):

The first warnings, related to the changing the size of the table, are straightforward enough to fix (when querying, it’s possible to follow the advice to use Table.Rows.Count and Table.Columns.Count: when resizing the table, we need to use the appropriate methods to insert/delete rows/columns at the right place, which actually makes more sense than arbitrarily setting the number of rows and columns to new values.

The second set of warnings, which cover setting the cell margins, are a little trickier to address (the suggestion to use the HorizontalLine and VerticalLine properties is misleading). Yes, you need to set the appropriate property for a specific cell, but the way you access the property is a little different – you need to use the Borders property to get access to the appropriate margin.

The good news is that there’s much finer control now possible: you can set left/right/top/bottom margins rather than having to live with equal margins in horizontal and vertical directions.

Next we have the third set of warnings, which relate to suppression of the title and header rows. These are also at the Cell level, but are also a bit tricky to get access to. You need to get the Cells for the row (passing in the row number and -1, for the column) and get/set the Style property. This string property should contain “Title” for title rows and “Header” for header rows. If you want to suppress the title – the equivalent of Table.IsTitleSuppressed = true), you can search through for the row containing “Title” in its Style property, and set it to the empty string (“”). And the equivalent operation for the header row.

Finally, we have the methods to set the size and contents of the text string for a cell. For this, you need to add a new item to the Contents collection for a cell, and from that you can set the text string and its height, as before.

Just to show that we have similar tables – at least with the properties I’ve assigned: there may be things I’ve missed when passing in different values for text sizes, margins, etc. – here are the results of running the TWOTABLES command: