Showing posts with label Drawings. Show all posts
Showing posts with label Drawings. Show all posts

Thursday, October 1, 2009

Pro/WebLink: Replace Drawing Formats

Jürgen from Germany was asking me recently about replacing formats with Pro/WebLink. In the process of providing a solution, I found an interesting combination of disappointments, curiosities, and finally a very nice solution.

SetSheetFormat()

Looking through the API documentation, it's not too hard to find that the method call to replace drawing formats is SetSheetFormat() in the pfcSheetOwner class. One of the primary arguments is the drawing format object in the form of a pfcDrawingFormat object. Unfortunately, this object's class is not obviously documented. The docs say only that its parent class is a pfcModel. So how to you get a drawing format object when it seems to have no methods?

Since it's a subclass of pfcModel, you use pfcBaseSession methods such as OpenFile(), GetModel, and RetrieveModel(). Here's where the J-Link docs are bit better, because they list the methods inherited from parent classes and interfaces, just making it that much more obvious. To use RetrieveModel(), a pfcModelDescriptor object needs to be created which specifies what is to be retrieved and how.

Here is the core sequence of steps, assuming the current model is a drawing:

var session = pfcGetProESession();
var drawing = session.CurrentModel;
var drwSheetNum = drawing.CurrentSheetNumber; // use current sheet

var frmName = "c_size.frm";
var frmSheetNum = 1;
var frmDescr = pfcCreate("pfcModelDescriptor").CreateFromFileName(frmName);
var format = session.RetrieveModel(frmDescr);

var drwModel = null;
drawing.SetSheetFormat(drwSheetNum, format, frmSheetNum, drwModel);
 

Format Folder

You'll notice that the folder where the format is stored was not specified. Why? According to the docs, you can't specify the path to a folder containing a pfcModel when using RetrieveModel(). Although the pfcModelDescriptor object contains a Path property, it is ignored.

How does Pro/Engineer know where to find the format? It just does. Well, if the PRO_FORMAT_DIR config.pro option is set properly, then it just does. Fortunately, this config.pro option can handle folder paths, Intralink 3.x folders, and PDMLink folders. That should cover most scenarios.


Format Sheets

SetSheetFormat() also allows you to specify the sheet number of the format to use (should your format have more than one sheet). Unfortunately, since the pfcDrawing Format object does not implement the pfcSheetOwner interface, the developer has no way to determine how many sheets are in the format. You'll just have to know how many sheet your formats have ahead of time, not a major tragedy.

To be fair this is a Pro/Toolkit limitation first and foremost. The PFC APIs merely inherit Pro/Toolkit's limitations.

Drawings, layouts, and Pro/Report files (who uses these anyway?!) all are defined as pfcModel2D objects, which gives lots of info about sheets, tables, and detail items. Formats, although pretty much the same thing, are not pfcModel2D objects. As a result, we can get none of these things.


Drawing Model

The drawing model is the part or assembly documented on the current sheet. Although this can be specified as the last argument to SetSheetFormat(), we don't have the ability to know what the current model is (at least until WF4). null is used to here to indicate the current drawing model.


Format Tables

Just as with interactive use of Pro/Engineer, when drawing formats are replaced, the drawing tables from the previous format have to be dealt with. Typically, this involves deleting the tables. Since SetSheetFormat() does not provide a mechanism or option for removing the old format tables, this will have to be done with more code.

This is where it gets a little tricky. While there is a CheckIfIsFromFormat() method call that will tell us if a table came from a format, we'll need the table object first. To get the table object, first we'll have to use ListTables() from the pfcTableOwner class to get all of the tables for the entire drawing. ListTables() takes no argument, meaning that we have no way to pre-filter the collection of tables. It really should take a sheet number argument to return only the tables on that sheet. As an alternatives, had the pfcTable class been a pfcDetailItem (in addition to a pfcModelItem), we could have gathered the tables by sheet number using ListDetailItems(), from pfcDetailItemOwner.

var tables = drawing.ListTables();
 
The workflow for CheckIfIsFromFormat() is a bit odd as well. To check if a table is from a format, we need the pfcTableObject, and then we need to specify the sheet number. Now wait a minute, the function returns a boolean so logically the table is from the format or it is not. What does the sheet number have to do with that? Even if the table had been segmented, it's still going to be from the format or not.

if (table.CheckIfIsFromFormat(drwSheetNum) == true) {
// table is from format, delete it
drawing.DeleteTable(table, false);
}
 
This quirkiness turns out to be very useful because it's rather difficult to tell on what sheet the table exists. The pfcTableInfo class does not contain this info. While it's true that the GetSegmentSheet() method can be used for this, we would have to assume that segment zero is the proper segment to use.


Full Example: Replacing Formats

This example shows a complete code set for obtaining the drawing object, the format object, and their related info. It then procedes to call the ReplaceFormat() function, which removes the old format tables prior to replacing the format. The code replaces the format and tables very quickly.

// get current drawing and sheet number
var session = pfcGetProESession();
var drawing = session.CurrentModel;
var drwSheetNum = drawing.CurrentSheetNumber;

// get format name from html text field
var frmName = document.getElementById("frmName").value

// use format sheet 2 if drawing sheet is not sheet 1
var frmSheetNum = 1;
if (drwSheetNum > 1) { frmSheetNum = 2; }

// get the format
var frmDescr = pfcCreate("pfcModelDescriptor").CreateFromFileName(frmName);
var format = session.RetrieveModel(frmDescr);

// replace the format
ReplaceFormat(drawing, drwSheetNum, format, frmSheetNum);

// update the display (optional)
// session.CurrentWindow.Repaint();
// drawing.Regenerate();


function ReplaceFormat ( drawing, drwSheetNum, format, frmSheetNum ) {

var tables = null;

try {
// get sequence of tables in the drawing
tables = drawing.ListTables();
for (var i=0; i<tables.Count; i++) {
var table = tables.Item(i);
if (table.CheckIfIsFromFormat(drwSheetNum) == true) {
// table is from format, delete it
drawing.DeleteTable(table, false);
}
}
}
catch (e) {
// ignore, no tables in drawing
}

drawing.SetSheetFormat(drwSheetNum, format, frmSheetNum, null);

}
 
Another step to perform (marked as optional in the example) is to update the display using either Repaint() or Regenerate(). They both should accomplish the same goal, but one may work better in certain drawings than the other. You can be sure that if a large drawing is slow to repaint when performed in Pro/Engineer interactively, it will be just as slow via Pro/Web.Link.

Tuesday, June 17, 2008

Pro/WebLink: Send Excel Data To Pro/ENGINEER Drawing Tables, and Back Again - Part 2

In part 1, the transfering of data from Excel to Pro/ENGINEER drawing tables was discussed. In this second part, the discussion will focus on the other direction, sending Pro/ENGINEER drawing table data to an Excel workbook.

TableToExcel Function

The TableToExcel() function is also pretty simple. After resetting the status fields, it uses ArrayFromProETable() to obtain an array of data from a user selected drawing table, then uses ArrayToExcel() to populate a new Excel workbook with the data from the array.

function TableToExcel ( ) {

var mesg = document.getElementById("mesg");
var errmesg = document.getElementById("errmesg");
mesg.innerHTML = "";
errmesg.innerHTML = "";

var array = ArrayFromProETable();
if (array == null) {
mesg.innerHTML = "null array";
return;
}

if (array != null) { ArrayToExcel(array); }
}
 

ArrayFromProETable Function

The ArrayFromProETable() function gathers drawing table data into an array. It's main steps are: Get the Pro/Engineer session object, obtaining the current model object and ensure a drawing is active, request a drawing table selection from the user, and iterate through the table cells to populate the array elements. After the table is selected by the user, UnHighlight() is used to repaint the table with its normal colors.

The table cell iteration code cycles first through the table rows, then the columns of each row, then through the lines of text in each cell. When there is more than one line of text in a table cell, a linefeed character is added as a line separator. This is correctly interpreted by Excel as multi-line data.

It's important to note that while in Javascript (as in many programming languages) arrays are indexed starting with zero, both drawing tables and Excel cells are indexed starting with one.

Once the iteration of the table cells has completed and the array has been populated, the array is returned.

function ArrayFromProETable ( ) {

var mesg = document.getElementById("mesg");
var errmesg = document.getElementById("errmesg");
var session = null;
var drawing = null;


// Get ProE session object
//
try {
session = pfcGetProESession();
}
catch (e) {
errmesg.innerHTML = "Unable to connect to Pro/Engineer";
return null;
}


// Get model object and ensure it's a drawing
//
try {
drawing = session.CurrentModel;

if (drawing == null || drawing.Type != pfcCreate("pfcModelType").MDL_DRAWING) {
errmesg.innerHTML = "A drawing must be active!";
return null;
}
}
catch (e) {
errmesg.innerHTML = "A drawing must be active.";
return null;
}


// Prompt user to select an existing table
//
var selections = null;
var table = null;

try {
selections = selectItems("dwg_table", 1);
var tabnum = selections.Item(0).SelItem;
table = drawing.GetTable(tabnum.Id);
}
catch (e) { // nothing selected
errmesg.innerHTML = "A drawing table was not selected.";
return null;
}

selections.Item(0).UnHighlight();


mesg.innerHTML = "Selected Table: "
+ table.GetRowCount() + " rows, "
+ table.GetColumnCount() + " columns";


// Gather data from table and populate into array
//

var array = new Array();

for (var i=0; i<table.GetRowCount(); i++) {

array[i] = new Array();

for (var j=0; j<table.GetColumnCount(); j++) {

// Table cell indexes start with one, arrays with zero
var cell = pfcCreate("pfcTableCell").Create(i+1,j+1);
var mode = pfcCreate("pfcParamMode").DWGTABLE_NORMAL;
array[i][j] = "";

try {
var textseq = table.GetText(cell, mode);

for (var k=0; k<textseq.Count; k++) {
var textitem = textseq.Item(k);
if (k > 0) { array[i][j] += "\n"; }
array[i][j] += textitem;
}
}
catch (e) { // cell has no value
// ignore
}

}

}

return array;
}
 

ArrayToExcel Function

The ArrayToExcel() function takes the two dimensional array and populates cells in a new Excel workbook. There are only three steps in this function: Get the Excel session object, Create a new workbook and get the active sheet object, Populate cells of the active sheet from data in the array.

As an alternative, a new Excel session could be started if an existing one can not be found.

function ArrayToExcel ( array ) {

var oXL;
var errmesg = document.getElementById("errmesg");


// Try to access Excel and get Application object.
//
try {
oXL = GetObject("","Excel.Application");
if (oXL == null) {
errmesg.innerHTML = "Failed to get Excel session object.";
return null;
}
}
catch (e) {
// oXL = new ActiveXObject("Excel.Application");
errmesg.innerHTML = "Excel must be running!";
return;
}


// Make session visible, get Sheet object
//
try {
oXL.Visible = true;
var oWB = oXL.Workbooks.Add();
var oSheet = oWB.ActiveSheet;
}
catch (e) {
errmesg.innerHTML = "Problem creating new workbook.";
return;
}


// Put the array data into the cells of the active sheet
//
for (var i=0; i < array.length; i++ ) {
for (var j=0; j < array[i].length; j++ ) {
// Excel cell indexes start with one, arrays with zero
oSheet.Cells(i+1, j+1).Value = array[i][j];
}
}

}
 

selectItems Function

The selectItems() function is largely based on selectItems() from an example in the Pro/WebLink documentation. It is changed here to be slightly more generic. The function builds a pfcSelectionOptions object that defines the type and number of allowable items that can be selected. It also minimizes the browser window during the selection process.

A sequence of selections (or null if nothing was selected) is returned.

function selectItems ( options, max ) {

// Setup options object
selOptions = pfcCreate("pfcSelectionOptions").Create(options);
selOptions.MaxNumSels = parseInt(max);


var session = pfcGetProESession();
var browserSize = session.CurrentWindow.GetBrowserSize();
session.CurrentWindow.SetBrowserSize(0.0);


var selections = null;

try {
selections = session.Select(selOptions, null);
session.CurrentWindow.SetBrowserSize(browserSize);
}
catch (err) {
session.CurrentWindow.SetBrowserSize(browserSize);
// In case user didn't select expected item
var errstr = pfcGetExceptionType(err);
if (errstr == "pfcXToolkitUserAbort" || errstr == "pfcXToolkitPickAbove") {
return null;
}
}

if (selections == null || selections.Count == 0)
return null;


return selections;
}
 


As I've mentioned, interaction with Excel is easy and straightforward. Although the Pro/Engineer interaction code is much more complex, the PFC API's contain a rich set a classes and methods making automation, such as I have shown here, possible. It's a quantum leap beyond relying upon mapkeys.

Monday, June 9, 2008

Pro/WebLink: Send Excel Data To Pro/ENGINEER Drawing Tables and Back Again - Part 1

In previous articles (BOM Data to Excel, Point Data to Excel), I've discussed the transfer of data from Pro/ENGINEER to Excel. Of course, transfering the data in the other direction, from Excel to Pro/Engineer, is not only possible, but it's just as easy.

In this two part article series, I will be discussing how to take data from a selected range in Excel and transfer that into a new table in a Pro/ENGINEER drawing, and vice versa. Part 1 concerns the Excel to Pro/Engineer transfer, and part 2 concerns the Pro/Engineer to Excel transfer.

HTML Page

The HTML page for this application is very simple: two javascript files, two buttons, and two status fields. pfcUtils.js is provided by PTC (see my pfcCreate optimization article).

All remaining code from this article is in the excel2table.js file. One of the buttons allows transfer from Excel to ProEngineer using the ExcelToTable() function, while the other allows transfer in the other direction using the TableToExcel() function. The two div fields allow for the display of error and general status information.

<HTML>
<SCRIPT LANGUAGE="JavaScript" type=text/javascript src="pfcUtils.js"></SCRIPT>
<SCRIPT LANGUAGE="JavaScript" type=text/javascript src="excel2table.js"></SCRIPT>
<BODY>

<form name="f">
<table border=1>
<tr>
<td>Excel</td>
<td>
<INPUT name="button1" type=button value=">>" onclick="ExcelToTable()">
<br>
<INPUT name="button2" type=button value="<<" onclick="TableToExcel()">
</td>
<td>Drawing Table</td>
</tr>
</table>

<br><font color='red'><a id="errmesg"></a></font>
<br><a id="mesg"></a><br>
</form>

</BODY>
</HTML>
 

ExcelToTable Function

The core of ExcelToTable() is pretty basic. Other than resetting the status fields, it uses ArrayFromExcel() to obtain an array of Excel data and ArrayToProETable() to create a drawing table from the array.

function ExcelToTable ( ) {
var mesg = document.getElementById("mesg");
var errmesg = document.getElementById("errmesg");
mesg.innerHTML = "";
errmesg.innerHTML = "";

var array = ArrayFromExcel();

if (array == null) {
mesg.innerHTML = "null array";
return;
}

if (array != null) { ArrayToProETable(array); }
}
 

ArrayFromExcel Function

The ArrayFromExcel() function has three main tasks: get the Excel session object using GetObject(), get the current range selection object, then loop through the rows and columns of the selection and extract the cell values.

Looping through the selection is done using Enumerator objects, one for rows and one for columns. The rows Enumerator is created using the 'rows' property of the selection object, then a for loop is used to iterate over the rows. For each row, a columns Enumerator is created using the 'columns' property, which references the selected columns of the row. The second for loop iterates over those columns, which in this case are cells.

The Enumerator has an item() method which is used to obtain the row object from the row Enumerator and the cell object from the column Enumerator. The 'value' property returns the cell value, which would usually be of type 'Number' or 'String'. Regardless of the type, the value is coerced into a String for ease of processing later. Pro/ENGINEER treats all table cell values as strings anyway, so it doesn't really matter too much what the type is.

The array used is two dimensional, populated in the iteration of the selection. A two dimensional array is created by first constructing an Array object, then populating it with array objects. The elements of the internal arrays contain the cell values. In the iteration, as a new row is processed, a new Array object is created and then assigned to the top level array. In Javascript, elements can be added to an Array object by a simple assignment statement, almost endlessly.

Once the iteration of the selection via Enumerator's has completed and the array has been populated, the array is returned.

function ArrayFromExcel ( ) {

var oXL = null;
var mesg = document.getElementById("mesg");
var errmesg = document.getElementById("errmesg");


// Try to access Excel and get Application object.
//
try {
oXL = GetObject("","Excel.Application");
if (oXL == null) {
errmesg.innerHTML = "Failed to get Excel session object.";
return null;
}
}
catch (e) {
errmesg.innerHTML = "Excel must be running!";
return null;
}


// Get Selection object
var sel = null;

try {
sel = oXL.ActiveWindow.Selection;
}
catch (e) {
errmesg.innerHTML = "Could not get Selection object.";
return null;
}

if (sel.columns.count <= 1 && sel.rows.count <= 1) {
errmesg.innerHTML = "A range of cells must be selected in Excel.";
return null;
}

mesg.innerHTML = "Selection: " + sel.rows.count + " rows, " + sel.columns.count + " columns";


var i_row = 0;
var e_rows = new Enumerator(sel.rows);
var array = new Array();


// Loop through each row of the selection
//
for ( ; !e_rows.atEnd(); e_rows.moveNext() ) {

var i_col = 0;
var row = e_rows.item();
var e_cols = new Enumerator(row.columns);

array[i_row] = new Array();

// Within the row, loop through each column
//
for ( ; !e_cols.atEnd(); e_cols.moveNext() ) {

var cell = e_cols.item();
var val = cell.value;

if (val == null) { val = "-"; }
array[i_row][i_col] = "" + val;
i_col++;
}

i_row++;
}

return array;
}
 

ArrayToProETable Function

The ArrayToProETable() function takes an array and creates a drawing table. Its main steps are: get the ProE session object, prompt the user for the table location, create the table creation instructions object, create the row and column specification objects, create the table, add content to the table cells, and display the table. As you can see, the ProE portion is quite a bit more complicated than the Excel part.

Included in the function, part of the row and column specs, are calculations to auto-size the rows and columns to fit the data in Excel. This is primarily for readability of the resulting table making it optional, but very helpful.

Also included is a workaround for a table creation bug that limits the size of a table to no more than 50 rows and/or no more than 50 columns. This is a bug in all Pro/Engineer API's, even Pro/Toolkit and J-Link. It doesn't mean that a huge table can't be created, just that it can't be created in one shot.

The workaround in the function is to create the table with a single row or column then add more rows or columns as necessary to get the table to the requested size. With the adjustment step (adding rows and/or columns) coming after the table creation step, but before the table display step, the time required by the workaround is hardly noticable, except with huge tables.

function ArrayToProETable ( array ) {

var errmesg = document.getElementById("errmesg");
var session = null;
var drawing = null;


// Get ProE session object
//
try {
session = pfcGetProESession();
}
catch (e) {
errmesg.innerHTML = "Unable to connect to Pro/Engineer";
return null;
}


// Get model object and ensure it's a drawing
//
try {
drawing = session.CurrentModel;

if (drawing == null || drawing.Type != pfcCreate("pfcModelType").MDL_DRAWING) {
errmesg.innerHTML = "A drawing must be active!";
return null;
}
}
catch (e) {
errmesg.innerHTML = "A drawing must be active.";
return null;
}


// Get location for new table by mouse click
//
var location = getMousePickPosition("Select location for table", true);
if (location == null) { return null; }


// Setup the table creation instructions object
//
var instrs = new pfcCreate("pfcTableCreateInstructions").Create(location);
instrs.SizeType = pfcCreate("pfcTableSizeType").TABLESIZE_BY_NUM_CHARS;


// Figure out max sizes to auto-expand the table
var ArrayMaxData = getArrayMaxData(array);


// Generate column data (for table creation instructions object)
// API Bug: Can't create table with more than 50 columns
// Workaround: Create with one column, add more later
//

var column_just = pfcCreate("pfcColumnJustification").COL_JUSTIFY_LEFT;
var columnData = pfcCreate("pfcColumnCreateOptions");
var columnOption = pfcCreate("pfcColumnCreateOption");
var maxOneShotCols = 50; // Most columns that can be created without workaround

if (ArrayMaxData.cols < maxOneShotCols) {
// Number of columns is ok, setup instructions to create table as-is.
// Use max column widths to auto-size columns
for (var i=0; i < ArrayMaxData.max_col_width.length; i++ ) {
var column = columnOption.Create(column_just, ArrayMaxData.max_col_width[i]);
columnData.Append(column);
}
}
else {
// Too many columns to create table as-is, setup instructions
// to create table with one column, expand after creation
var column = columnOption.Create(column_just, ArrayMaxData.max_col_width[0]);
columnData.Append(column);
}

// Add column data object to table creation instructions
instrs.ColumnData = columnData;


// Generate row data (for table creation instructions object)
// API Bug: Can't create table with more than 50 rows
// Workaround: Create with one row, add more later
//

var rowData = pfcCreate("realseq");
var maxOneShotRows = 50; // Most rows that can be created without workaround

if (ArrayMaxData.rows < maxOneShotRows) {
// Number of rows is ok, setup instructions to create table as-is.
// Use max row heights to auto-size rows
for (var i = 0; i < array.length; i++) {
rowData.Append(ArrayMaxData.max_row_height[i]);
}
}
else {
// Too many rows to create table as-is, setup instructions
// to create table with one row, expand after creation
rowData.Append(ArrayMaxData.max_row_height[0]);
}

// Add row data object to table creation instructions
instrs.RowHeights = rowData;


// Create the table (no contents yet, and won't be visible yet)
//
var dwgTable = null;

try {
dwgTable = drawing.CreateTable(instrs);
}
catch (e) {
errmesg.innerHTML += "Cannot create table: " + e.message;
return;
}


// Add rows if data had more than allowed to create in one shot
//
try {
if (ArrayMaxData.rows >= maxOneShotRows) {
for (var i=1; i < array.length; i++ ) {
// Add remaining rows needed. Index starts
// at 1 because one row already exists
dwgTable.InsertRow(ArrayMaxData.max_row_height[i], null, false)
}
}
}
catch (e) {
errmesg.innerHTML += "Cannot add rows: " + e.message;
return;
}


// Add columns if data had more than allowed to create in one shot
//
try {
if (ArrayMaxData.cols >= maxOneShotCols) {
for (var i=1; i < ArrayMaxData.max_col_width.length; i++ ) {
// Add remaining columns needed. Index starts
// at 1 because one column already exists
dwgTable.InsertColumn(ArrayMaxData.max_col_width[i], null, false);
}
}
}
catch (e) {
errmesg.innerHTML += "Cannot add columns: " + e.message;
return;
}


// Fill in each cell with data
//
for (var i=0; i < array.length; i++ ) {
for (var j=0; j < array[i].length; j++ ) {
// Table cell indexes start with one, arrays with zero
writeTextInCell(dwgTable, i+1, j+1, array[i][j]);
}
}


// Display the table
dwgTable.Display ();
}
 

getMousePickPosition Function

The getMousePickPosition() function is used to prompt the user for a position by using the left mouse button. It uses the UIGetNextMousePick() method of the pfcSession object to accomplish this. The function also hides the browser window, displays an alert dialog to prompt the user, and then restores the browser window to its previous size.

function getMousePickPosition ( prompt, hideBrowser ) {

var browserSize = null;
var session = pfcGetProESession();

if (hideBrowser) {
// Minimize browser, remember browser size for later restoration
browserSize = session.CurrentWindow.GetBrowserSize();
session.CurrentWindow.SetBrowserSize(0.0);
}

// Prompt user and get user specified location
alert(prompt);
var mouseButton = pfcCreate("pfcMouseButton");
var mouseClick = session.UIGetNextMousePick(mouseButton.MOUSE_BTN_LEFT);

if (hideBrowser) {
// Restore browser window
session.CurrentWindow.SetBrowserSize(browserSize);
}

return mouseClick.Position;
}
 

getArrayMaxData Function

The getArrayMaxData function analyzes an array and returns an object containing four properties. Two properties are the number of rows and columns in the array, which is expected to be two dimensional. The other two properties are maximum column widths for each column and maximum row heights for each row.

If a row in excel contains a cell with a single linefeed character, that row will have a maximum row height of at least two characters. Similarly, if a column contains a cell with 50 characters, the maximum column width for that column will be at least 50 characters.

Once these values are populated, the object is returned to the calling function.

function getArrayMaxData ( array ) {

var obj = new Object();

// Figure out max size for each row and column to auto-expand
//
obj.max_col_width = new Array();
obj.max_row_height = new Array();

for (var i=0; i < array.length; i++ ) {
obj.max_row_height[i] = 0;
}

for (var i=0; i < array[0].length; i++ ) {
obj.max_col_width[i] = 0;
}

obj.rows = array.length;
obj.cols = 0;

// populate arrays of max column widths and max row heights
//
for (var i=0; i < array.length; i++) {

for (var j=0; j < array[i].length; j++) {

if (obj.cols < array[i].length) { obj.cols = array[i].length; }

var lines = array[i][j].split("\n");

if (lines.length > obj.max_row_height[i]) {
obj.max_row_height[i] = lines.length;
}

for (var k=0; k < lines.length; k++ ) {
if (lines[k].length > obj.max_col_width[j]) {
obj.max_col_width[j] = lines[k].length;
}
}

}

}

return obj;
}
 

writeTextInCell Function

The writeTextInCell() function writes text to a specific table cell. If the cell data in Excel contains linefeeds, the result will be a table cell that matches with the same number of lines of text.

function writeTextInCell ( table, row, col, text ) {

var cell = pfcCreate("pfcTableCell").Create(row, col);
var lines = pfcCreate("stringseq");

var cell_lines = text.split("\n");

for (var i=0; i < cell_lines.length; i++ ) {
lines.Append(cell_lines[i]);
}

table.SetText(cell, lines);
}
 


There may be strange issues if "zombie" or multiple Excel sessions are running. If Excel is clearly running, but unexpected error messages are received, it may be best to kill all Excel sessions, then start it over again.

Part 2: Sending drawing table data to Excel