Anda di halaman 1dari 19

Through the Interface

Excel
February 05, 2014
Inserting a cell range from an Excel sheet as an AutoCAD table using .NET
I wasnt planning on covering this particular topic today, but then this comment came in overnight and I
ended up taking a look into it. Paul has been trying to append a specific cell range to his connection string
when creating a data-link for an Excel spreadsheet, adding to the code from this previous post.
I gave it a try myself with a hardcoded cell range and it seemed to work fine, and so went ahead and modified
the implementation of the TFS command to ask the user to enter their own cell range. Whats probably most
interesting about the code in this post is its use of a regular expression something I dont use often enough,
in truth which is a great way of validating that strings conform to a particular pattern.
Its also possible to apply group labels within a regex to tag groups of characters for later extraction and
validation. We use this technique to check that the row numbers are both greater than 0, for instance
(A0:B3 and A10:B0 are both invalid cell ranges that otherwise meet the pattern we define).
There may well be a better way to do this within a regex as Ive said, I dont use them as often as I should
but hopefully the overall technique of using them will be a helpful reminder for people.
On a more general note, this kind of input validation is extremely important if youre asking for user input
and then using it to build database queries: if you dont protect against arbitrary strings being entered then
your application will be wide open to SQL Injection attacks. This isnt something thats as important with
desktop applications accessing local databases, perhaps, but as applications move more to the web this
becomes absolutely critical (as the unfortunate autocomplete snafu on healthcare.gov highlighted a couple
of months ago).
Heres the updated C# code... the TFS command is the only section thats had significant changes from the
previous version, in case:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
using Excel = Microsoft.Office.Interop.Excel;

namespace LinkToExcel
{
public class Commands
{
[CommandMethod("S2T")]
static public void UpdateTableFromSpreadsheet()
{
var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;

var opt = new PromptEntityOptions("\nSelect table to update");
opt.SetRejectMessage("\nEntity is not a table.");
opt.AddAllowedClass(typeof(Table), false);

var per = ed.GetEntity(opt);
if (per.Status != PromptStatus.OK)
return;

using (var tr = db.TransactionManager.StartTransaction())
{
try
{
var obj = tr.GetObject(per.ObjectId, OpenMode.ForRead);
var tb = obj as Table;

// It should always be a table
// but we'll check, just in case

if (tb != null)
{
// The table must be open for write

tb.UpgradeOpen();

// Update data link from the spreadsheet

var dlIds = tb.Cells.GetDataLink();

foreach (ObjectId dlId in dlIds)
{
var dl =
(DataLink)tr.GetObject(dlId, OpenMode.ForWrite);
dl.Update(
UpdateDirection.SourceToData,
UpdateOption.None
);

// And the table from the data link

tb.UpdateDataLink(
UpdateDirection.SourceToData,
UpdateOption.None
);
}
}
tr.Commit();
ed.WriteMessage(
"\nUpdated the table from the spreadsheet."
);
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage("\nException: {0}", ex.Message);
}
}
}

[CommandMethod("T2S")]
static public void UpdateSpreadsheetFromTable()
{
var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;

var opt =
new PromptEntityOptions(
"\nSelect table with spreadsheet to update"
);
opt.SetRejectMessage(
"\nEntity is not a table."
);
opt.AddAllowedClass(typeof(Table), false);

var per = ed.GetEntity(opt);
if (per.Status != PromptStatus.OK)
return;

using (var tr = db.TransactionManager.StartTransaction())
{
try
{
var obj = tr.GetObject(per.ObjectId, OpenMode.ForRead);
var tb = obj as Table;

// It should always be a table
// but we'll check, just in case

if (tb != null)
{
// The table must be open for write

tb.UpgradeOpen();

// Update the data link from the table

tb.UpdateDataLink(
UpdateDirection.DataToSource,
UpdateOption.ForceFullSourceUpdate
);

// And the spreadsheet from the data link

var dlIds = tb.Cells.GetDataLink();
foreach (ObjectId dlId in dlIds)
{
var dl =
(DataLink)tr.GetObject(dlId, OpenMode.ForWrite);
dl.Update(
UpdateDirection.DataToSource,
UpdateOption.ForceFullSourceUpdate
);
}
}
tr.Commit();

ed.WriteMessage(
"\nUpdated the spreadsheet from the table."
);
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage("\nException: {0}", ex.Message);
}
}
}

static public List<string> GetSheetNames(string excelFileName)
{
var listSheets = new List<string>();

var excel = new Excel.Application();
var wbs = excel.Workbooks.Open(excelFileName);
foreach (Excel.Worksheet sheet in wbs.Worksheets)
{
listSheets.Add(sheet.Name);
}
excel.Quit();

return listSheets;
}

[CommandMethod("TFS")]
static public void TableFromSpreadsheet()
{
const string dlName = "Import table from Excel demo";

var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;

// Ask the user to select an XLS(X) file

var ofd =
new OpenFileDialog(
"Select Excel spreadsheet to link",
null,
"xls; xlsx",
"ExcelFileToLink",
OpenFileDialog.OpenFileDialogFlags.
DoNotTransferRemoteFiles
);

var dr = ofd.ShowDialog();

if (dr != System.Windows.Forms.DialogResult.OK)
return;

// Display the name of the file and the contained sheets

ed.WriteMessage(
"\nFile selected was \"{0}\". Contains these sheets:",
ofd.Filename
);

// First we get the sheet names

var sheetNames = GetSheetNames(ofd.Filename);

if (sheetNames.Count == 0)
{
ed.WriteMessage(
"\nWorkbook doesn't contain any sheets."
);
return;
}

// And loop through, printing their names

for (int i = 0; i < sheetNames.Count; i++)
{
var name = sheetNames[i];

ed.WriteMessage("\n{0} - {1}", i + 1, name);
}

// Ask the user to select one

var pio = new PromptIntegerOptions("\nSelect a sheet");
pio.AllowNegative = false;
pio.AllowZero = false;
pio.DefaultValue = 1;
pio.UseDefaultValue = true;
pio.LowerLimit = 1;
pio.UpperLimit = sheetNames.Count;

var pir = ed.GetInteger(pio);
if (pir.Status != PromptStatus.OK)
return;

// Ask the user to select a range of cells in the spreadsheet

// We'll use a Regular Expression that matches a column (with
// one or more letters) followed by a numeric row (which we're
// naming "row1" so we can validate it's > 0 later),
// followed by a colon and then the same (but with "row2")

const string rangeExp =
"^[A-Z]+(?<row1>[0-9]+):[A-Z]+(?<row2>[0-9]+)$";
bool done = false;
string range = "";

do
{
var psr = ed.GetString("\nEnter cell range <entire sheet>");
if (psr.Status != PromptStatus.OK)
return;

if (String.IsNullOrEmpty(psr.StringResult))
{
// Default is to select entire sheet

done = true;
}
else
{
// If a string was entered, make sure it's a
// valid cell range, which means it matches the
// Regular Expression and has positive (non-zero)
// row numbers

var m =
Regex.Match(
psr.StringResult, rangeExp, RegexOptions.IgnoreCase
);
if (
m.Success &&
Int32.Parse(m.Groups["row1"].Value) > 0 &&
Int32.Parse(m.Groups["row2"].Value) > 0
)
{
done = true;
range = psr.StringResult.ToUpper();
}
else
{
ed.WriteMessage("\nInvalid range, please try again.");
}
}
} while (!done);

// Ask for the insertion point of the table

var ppr = ed.GetPoint("\nEnter table insertion point");
if (ppr.Status != PromptStatus.OK)
return;

try
{
// Remove any Data Link, if one exists already

var dlm = db.DataLinkManager;
var dlId = dlm.GetDataLink(dlName);
if (dlId != ObjectId.Null)
{
dlm.RemoveDataLink(dlId);
}

// Create and add the new Data Link, this time with
// a direction connection to the selected sheet

var dl = new DataLink();
dl.DataAdapterId = "AcExcel";
dl.Name = dlName;
dl.Description = "Excel fun with Through the Interface";
dl.ConnectionString =
ofd.Filename +
"!" + sheetNames[pir.Value - 1] +
(String.IsNullOrEmpty(range) ? "" : "!" + range);
dl.DataLinkOption = DataLinkOption.PersistCache;
dl.UpdateOption |= (int)UpdateOption.AllowSourceUpdate;

dlId = dlm.AddDataLink(dl);

using (var tr = doc.TransactionManager.StartTransaction())
{
tr.AddNewlyCreatedDBObject(dl, true);

var bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);

// Create our table

var tb = new Table();
tb.TableStyle = db.Tablestyle;
tb.Position = ppr.Value;
tb.Cells.SetDataLink(dlId, true);
tb.GenerateLayout();

// Add it to the drawing

var btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);

btr.AppendEntity(tb);
tr.AddNewlyCreatedDBObject(tb, true);
tr.Commit();
}
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage("\nException: {0}", ex.Message);
}
}
}
}
Posted at 05:33 PM in AutoCAD, AutoCAD .NET, Excel, Selection, Tables | Permalink | 6 Comments
June 18, 2013
Inserting a specific Excel sheet as an AutoCAD table using .NET
Last week I received the following question from Adam Schilling:
I have enjoyed your posts on .net programming for datalinks. I have searched high and low and
havent been able to find answers or any support to help me with a small issue.
My code (much of which was based off of your posts from 2007) works fine, except I cannot get it to
use a different sheet from the workbook that is select. Since the option to select a specific sheet is
available when adding a link manually, I would think that it would be possible to programmatically do
it also.
If it would work for a post great, if not, I apologize for taking your time.
Seeing as Adam asked so nicely (and I found the question to be of broad interest, which is also important), I
went ahead and dusted off the 6-year old posts that hed referred to. [Has it really been that long since I wrote
them? Sigh.]
Heres the series that Ive refreshed for the purposes of this post:
1. Creating an AutoCAD table linked to an Excel spreadsheet using .NET
2. Updating an AutoCAD table linked to an Excel spreadsheet using .NET
3. Updating an Excel spreadsheet from a linked AutoCAD table using .NET
Its really the code from the first that has been modified to allow selection of a specific sheet, but the code
was all in the same file (and the compiler warned of some obsolete functions), so I decided to update the
other commands, too, rather than stripping them out.
The quick answer to Adams question is that you can specify a particular sheet to link to by passing its name
(using the ! character as a separator) into the DataLinks ConnectionString property. e.g.:
dl.ConnectionString = "spreadsheet.xlsx!Sheet2";
Thats really all there is to it. But that would make for a pretty short post, so I went ahead and added some
code that, for a selected spreadsheet, presents the list of contained sheets to the user and asks for one to be
selected.
While not really being what the post is about the main point is to show the AutoCAD side of things there
are a few different ways to access the contents of an Excel spreadsheet from .NET.
The one Id personally have preferred to have used is the OleDb provider for Office. The problem with that
approach relates to getting the right version of the component working for your app, even though 32- and
64-bit versions are both available (at least for Office 2010, for Office 2007 theres just the 32-bit version).
Heres my own situation, as an example: Im running 32-bit Office on a 64-bit system (the main reason being
thats the way our IT department supports it, as far as I can tell), which means the installer doesnt let me
install the 64-bit component (probably because it needs to match the underlying Office version, which I
suppose is fair enough). But while I can install the 32-bit version, my .NET application inside AutoCAD needs
to be either x64 or Any CPU, and so leads to the The 'Microsoft.ACE.OLEDB.12.0' provider is not
registered on the local machine exception being thrown when the connection attempt is made.
Which led me back to using the COM library for Excel, instead (for which I added a COM project reference to
the Microsoft Excel 14.0 Object Library). Using this component actually fires up an instance of Excel in the
background which certainly seems like overkill just to access the list of sheets contained in a spreadsheet
but its reliable and easy to get working. And the code is certainly more readable.
Speaking of the code, heres the updated C# code letting a user select a specific Excel sheet for insertion (in
the TFS command):
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;

namespace LinkToExcel
{
public class Commands
{
[CommandMethod("S2T")]
static public void UpdateTableFromSpreadsheet()
{
var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;

var opt = new PromptEntityOptions("\nSelect table to update");
opt.SetRejectMessage("\nEntity is not a table.");
opt.AddAllowedClass(typeof(Table), false);

var per = ed.GetEntity(opt);
if (per.Status != PromptStatus.OK)
return;

using (var tr = db.TransactionManager.StartTransaction())
{
try
{
var obj = tr.GetObject(per.ObjectId, OpenMode.ForRead);
var tb = obj as Table;

// It should always be a table
// but we'll check, just in case

if (tb != null)
{
// The table must be open for write

tb.UpgradeOpen();

// Update data link from the spreadsheet

var dlIds = tb.Cells.GetDataLink();

foreach (ObjectId dlId in dlIds)
{
var dl =
(DataLink)tr.GetObject(dlId, OpenMode.ForWrite);
dl.Update(
UpdateDirection.SourceToData,
UpdateOption.None
);

// And the table from the data link

tb.UpdateDataLink(
UpdateDirection.SourceToData,
UpdateOption.None
);
}
}
tr.Commit();
ed.WriteMessage(
"\nUpdated the table from the spreadsheet."
);
}
catch (Exception ex)
{
ed.WriteMessage(
"\nException: {0}",
ex.Message
);
}
}
}

[CommandMethod("T2S")]
static public void UpdateSpreadsheetFromTable()
{
var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;

var opt =
new PromptEntityOptions(
"\nSelect table with spreadsheet to update"
);
opt.SetRejectMessage(
"\nEntity is not a table."
);
opt.AddAllowedClass(typeof(Table), false);

var per = ed.GetEntity(opt);
if (per.Status != PromptStatus.OK)
return;

Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
try
{
DBObject obj =
tr.GetObject(per.ObjectId, OpenMode.ForRead);
Table tb = obj as Table;

// It should always be a table
// but we'll check, just in case

if (tb != null)
{
// The table must be open for write

tb.UpgradeOpen();

// Update the data link from the table

tb.UpdateDataLink(
UpdateDirection.DataToSource,
UpdateOption.ForceFullSourceUpdate
);

// And the spreadsheet from the data link

var dlIds = tb.Cells.GetDataLink();
foreach (ObjectId dlId in dlIds)
{
var dl =
(DataLink)tr.GetObject(dlId, OpenMode.ForWrite);
dl.Update(
UpdateDirection.DataToSource,
UpdateOption.ForceFullSourceUpdate
);
}
}
tr.Commit();

ed.WriteMessage(
"\nUpdated the spreadsheet from the table."
);
}
catch (Exception ex)
{
ed.WriteMessage("\nException: {0}", ex.Message);
}
}
}

static public List<string> GetSheetNames(string excelFileName)
{
var listSheets = new List<string>();

var excel = new Excel.Application();
var wbs = excel.Workbooks.Open(excelFileName);
foreach (Excel.Worksheet sheet in wbs.Worksheets)
{
listSheets.Add(sheet.Name);
}
excel.Quit();

return listSheets;
}

[CommandMethod("TFS")]
static public void TableFromSpreadsheet()
{
const string dlName = "Import table from Excel demo";

var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;

// Ask the user to select an XLS(X) file

var ofd =
new OpenFileDialog(
"Select Excel spreadsheet to link",
null,
"xls; xlsx",
"ExcelFileToLink",
OpenFileDialog.OpenFileDialogFlags.
DoNotTransferRemoteFiles
);

var dr = ofd.ShowDialog();

if (dr != System.Windows.Forms.DialogResult.OK)
return;

// Display the name of the file and the contained sheets

ed.WriteMessage(
"\nFile selected was \"{0}\". Contains these sheets:",
ofd.Filename
);

// First we get the sheet names

var sheetNames = GetSheetNames(ofd.Filename);

if (sheetNames.Count == 0)
{
ed.WriteMessage(
"\nWorkbook doesn't contain any sheets."
);
return;
}

// And loop through, printing their names

for (int i=0; i < sheetNames.Count; i++)
{
var name = sheetNames[i];

ed.WriteMessage("\n{0} - {1}", i + 1, name);
}

// Ask the user to select one

var pio = new PromptIntegerOptions("\nSelect a sheet");
pio.AllowNegative = false;
pio.AllowZero = false;
pio.DefaultValue = 1;
pio.UseDefaultValue = true;
pio.LowerLimit = 1;
pio.UpperLimit = sheetNames.Count;

var pir = ed.GetInteger(pio);
if (pir.Status != PromptStatus.OK)
return;

// Ask for the insertion point of the table

var ppr = ed.GetPoint("\nEnter table insertion point");
if (ppr.Status != PromptStatus.OK)
return;

// Remove any Data Link, if one exists already

var dlm = db.DataLinkManager;
var dlId = dlm.GetDataLink(dlName);
if (dlId != ObjectId.Null)
{
dlm.RemoveDataLink(dlId);
}

// Create and add the new Data Link, this time with
// a direction connection to the selected sheet

var dl = new DataLink();
dl.DataAdapterId = "AcExcel";
dl.Name = dlName;
dl.Description = "Excel fun with Through the Interface";
dl.ConnectionString =
ofd.Filename + "!" + sheetNames[pir.Value - 1];
dl.DataLinkOption =
DataLinkOption.PersistCache;
dl.UpdateOption |=
(int)UpdateOption.AllowSourceUpdate;

dlId = dlm.AddDataLink(dl);

using (var tr = doc.TransactionManager.StartTransaction())
{
tr.AddNewlyCreatedDBObject(dl, true);

var bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);

// Create our table

var tb = new Table();
tb.TableStyle = db.Tablestyle;
tb.Position = ppr.Value;
tb.Cells.SetDataLink(dlId, true);
tb.GenerateLayout();

// Add it to the drawing

var btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);

btr.AppendEntity(tb);
tr.AddNewlyCreatedDBObject(tb, true);
tr.Commit();
}
}
}
}
When we run the TFS command, we see the user gets presented with the usual file selection dialog, but then a
command-line interface for choosing a specific sheet from the selected spreadsheet:
Command: TFS
File selected was "C:\Data\Spreadsheet.xlsx". Contains these sheets:
1 - Sheet1
2 - Sheet2
3 - Sheet3
Select a sheet <1>: 2
Enter table insertion point:
Which results in the specified sheet getting inserted as a table into the current AutoCAD drawing.
Posted at 09:58 AM in AutoCAD, AutoCAD .NET, Excel, Selection, Tables | Permalink | 6 Comments
August 27 , 2007
Updating an Excel spreadsheet from a linked AutoCAD table using .NET
In the last post we saw some code to update an AutoCAD table linked to an Excel spreadsheet. In this post we
go the other way, updating an Excel spreadsheet from a linked AutoCAD table.
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
namespace LinkToExcel
{
public class Commands
{
[CommandMethod("T2S")]
static public void UpdateSpreadsheetFromTable()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptEntityOptions opt =
new PromptEntityOptions(
"\nSelect table with spreadsheet to update: "
);
opt.SetRejectMessage(
"\nEntity is not a table."
);
opt.AddAllowedClass(typeof(Table), false);
PromptEntityResult per =
ed.GetEntity(opt);
if (per.Status != PromptStatus.OK)
return;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
try
{
DBObject obj =
tr.GetObject(
per.ObjectId,
OpenMode.ForRead
);
Table tb = (Table)obj;
// It should always be a table
// but we'll check, just in case
if (tb != null)
{
// The table must be open for write
tb.UpgradeOpen();
// Update the data link from the table
tb.UpdateDataLink(
UpdateDirection.DataToSource,
UpdateOption.ForceFullSourceUpdate
);
// And the spreadsheet from the data link
ObjectId dlId = tb.GetDataLink(0, 0);
DataLink dl =
(DataLink)tr.GetObject(
dlId,
OpenMode.ForWrite
);
dl.Update(
UpdateDirection.DataToSource,
UpdateOption.ForceFullSourceUpdate
);
}
tr.Commit();
ed.WriteMessage(
"\nUpdated the spreadsheet from the table."
);
}
catch (Exception ex)
{
ed.WriteMessage(
"\nException: {0}",
ex.Message
);
}
}
}
}
}
Tables with linked spreadsheets are locked by default and display this glyph when you hover over them: .
Before you run the code you will need to unlock the table by right-clicking the cell(s) you wish to edit:
One point to note is that the code will work even if the spreadsheet is open in Excel, but the contents will not
be updated automatically - you have to close and reopen the file to see the results. And you will probably see
this dialog come up twice:
For the best (most logical) results, the T2S command should really be run when the spreadsheet is not open
in Excel. I expect it's possible to determine whether a spreadsheet is open in Excel from using standard file
access functions in .NET (requesting exclusive access, to see whether it's possible to get it), but that's being
left as an exercise for the reader (or for another day, at least :-).
For your convenience, here's a source file containing the code from the last three posts (command
implementations for TFS, S2T and T2S).
Posted at 02:15 PM in AutoCAD, AutoCAD .NET, Excel, Tables | Permalink | 1 Comment | TrackBack (0)
August 24, 2007
Updating an AutoCAD table linked to an Excel spreadsheet using .NET
Thanks to Viru Aithal, from DevTech India, for providing the code for this post (I converted the C# code
below from some C++ he had sent to a developer).
In the last post we showed how to create a table linked to an Excel spreadsheet using .NET in AutoCAD 2008.
AutoCAD does a great job of looking for changes in the Excel spreadsheet, and asking whether you want to
update the linked table:
There may be times, however, when you want to force the update programmatically, whether from the
spreadsheet to the table ot vice-versa. In this post we'll show the code to update the table from the
spreadsheet, and in the next post we'll see some code to update the spreadsheet from the table (should it
have been unlocked and edited).
Here's the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
namespace LinkToExcel
{
public class Commands
{
[CommandMethod("S2T")]
static public void UpdateTableFromSpreadsheet()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
PromptEntityOptions opt =
new PromptEntityOptions(
"\nSelect table to update: "
);
opt.SetRejectMessage(
"\nEntity is not a table."
);
opt.AddAllowedClass(typeof(Table), false);
PromptEntityResult per =
ed.GetEntity(opt);
if (per.Status != PromptStatus.OK)
return;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
try
{
DBObject obj =
tr.GetObject(
per.ObjectId,
OpenMode.ForRead
);
Table tb = (Table)obj;
// It should always be a table
// but we'll check, just in case
if (tb != null)
{
// The table must be open for write
tb.UpgradeOpen();
// Update the data link from the spreadsheet
ObjectId dlId = tb.GetDataLink(0, 0);
DataLink dl =
(DataLink)tr.GetObject(
dlId,
OpenMode.ForWrite
);
dl.Update(
UpdateDirection.SourceToData,
UpdateOption.None
);
// And the table from the data link
tb.UpdateDataLink(
UpdateDirection.SourceToData,
UpdateOption.None
);
}
tr.Commit();
ed.WriteMessage(
"\nUpdated the table from the spreadsheet."
);
}
catch (Exception ex)
{
ed.WriteMessage(
"\nException: {0}",
ex.Message
);
}
}
}
}
}
When you run the S2T (for Spreadsheet-to-Table) command, you will be prompted to select a table. The code
retrieves the link information from the table and then requests the data link to pull down new data from the
spreadsheet before updating the table. Next time we'll look at the code for T2S...
Posted at 04:23 PM in AutoCAD, AutoCAD .NET, Excel, Tables | Permalink | 7 Comments | TrackBack (0)
August 22, 2007
Creating an AutoCAD table linked to an Excel spreadsheet using .NET
In the last post I promised to tackle this issue, and so here we are again. :-)
Note: the code in this post relies on enhanced table functionality introduced in AutoCAD 2008, so please
don't get frustrated trying to make this work in previous versions.
The following C# code follows on from yesterday's, taking the spreadsheet selected by the user and linking it
to a newly-created table in the active AutoCAD drawing. I haven't bothered with line numbering in the below
code, as it follows on almost exactly from the code shown last time (aside from renaming the namespace, the
command and the function, as well as adding a string constant at the top of the function implementation).
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Windows;
namespace LinkToExcel
{
public class Commands
{
[CommandMethod("TFS")]
static public void TableFromSpreadsheet()
{
// Hardcoding the string
// Could also select for it
const string dlName =
"Import table from Excel demo";
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
OpenFileDialog ofd =
new OpenFileDialog(
"Select Excel spreadsheet to link",
null,
"xls; xlsx",
"ExcelFileToLink",
OpenFileDialog.OpenFileDialogFlags.
DoNotTransferRemoteFiles
);
System.Windows.Forms.DialogResult dr =
ofd.ShowDialog();
if (dr != System.Windows.Forms.DialogResult.OK)
return;
ed.WriteMessage(
"\nFile selected was \"{0}\".",
ofd.Filename
);
PromptPointResult ppr =
ed.GetPoint(
"\nEnter table insertion point: "
);
if (ppr.Status != PromptStatus.OK)
return;
// Remove the Data Link, if it exists already
DataLinkManager dlm = db.DataLinkManager;
ObjectId dlId = dlm.GetDataLink(dlName);
if (dlId != ObjectId.Null)
{
dlm.RemoveDataLink(dlId);
}
// Create and add the Data Link
DataLink dl = new DataLink();
dl.DataAdapterId = "AcExcel";
dl.Name = dlName;
dl.Description =
"Excel fun with Through the Interface";
dl.ConnectionString = ofd.Filename;
dl.DataLinkOption =
DataLinkOption.PersistCache;
dl.UpdateOption |=
(int)UpdateOption.AllowSourceUpdate;
dlId = dlm.AddDataLink(dl);
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
tr.AddNewlyCreatedDBObject(dl, true);
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
);
Table tb = new Table();
tb.TableStyle = db.Tablestyle;
tb.Position = ppr.Value;
tb.SetDataLink(0, 0, dlId, true);
tb.GenerateLayout();
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId,
OpenMode.ForWrite
);
btr.AppendEntity(tb);
tr.AddNewlyCreatedDBObject(tb, true);
tr.Commit();
}
// Force a regen to display the table
ed.Regen();
}
}
}
Here's what happens when you run the TFS command and select your favourite XLS for linking (I used mass-
balance.xls from AutoCAD 2008's Sample\Mechanical Sample folder):
At this stage I haven't focused at all on formating - this is just coming in "as is", without any adjustment of cell
alignments, column widths or row heights.
I chose to hardcode the name of the Data Link we use for the spreadsheet. You can run the DATALINK
command to check on it, after the command has executed:

It doesn't seem to be an issue if you repeat the command and bring in a different spreadsheet using the same
link - the link appears to continue (although I haven't performed exhaustive testing). If it does prove to be a
problem it should be simple enough to create a unique Data Link per spreadsheet imported (or even per time
the command is run).
Posted at 05:40 PM in AutoCAD, AutoCAD .NET, Excel, Tables | Permalink | 5 Comments | TrackBack (0)

Anda mungkin juga menyukai