I have a specific template I have use for every page. It is essentially two columns of tables with 1 column and 1 row. Why the whole thing is not one table I do not know, but I am stuck with it. It looks like the following. Again each cell is actually its own table. So there is 40 tables on the page. These will be used as labels in a binder. Note that a table on the left is duplicated on the right. This is because they fold over and will be used as one.
What I need is to duplicate this to a new page and start over every time a page fills up and the count exceeds 20.
My code is as follows:
Word.Application app = new Word.Application();
var doc = app.Documents.Add(#"C:\.....LabelTemplate.dotx");
object missing = System.Type.Missing;
object pageBreak = Word.WdBreakType.wdPageBreak;
Word.Selection selection = app.Selection;
string path = Path.Combine(destination, "labels.docx");
int page = 1;
foreach (var batch in batches)
{
for (int i = 1; i <= batch.Count(); i++)
{
Word.Table table = doc.Tables[i];
table.Range.Font.Size = 7;
table.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
table.Range.Font.Bold = 1;
int x = i - 1;
string text = batch[x].WellName + "\v" + batch[x].Field + "\v" + batch[x].Reservior;
//int cellNum = i + 1;
table.Cell(1, 1).Range.Text = text;
Word.Table table2 = doc.Tables[i + 20];
table2.Range.Font.Size = 7;
table2.Range.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphCenter;
table2.Range.Font.Bold = 1;
table2.Cell(1, 1).Range.Text = text;
if (i % 20 == 0)
{
selection.EndKey(Word.WdUnits.wdStory, Word.WdMovementType.wdMove);
selection.InsertBreak(ref pageBreak);
}
}
page++;
}
doc.SaveAs(path);
doc.Close();
app.Quit();
Building Blocks provide a convenient way to store and re-use "boiler-plate" in Word documents. The content can be "plain text" or include formatting and all things supported by Word. This is often easier and faster than trying to recreate content using code or importing content from an outside file.
Building Blocks are stored in templates (dotx or dotm). A template can be attached to a document (usually when a document is created from that template) or it can be "global", such as Normal.dotm.
In this case, the Building Block is stored in the attached template and will be available to any document created from that template (but not for other documents).
Building Blocks can be organized into "Types" and "Categories", meaning there can be multiple Building Blocks in a template "container" with the same name. If that's the case, it's necessary to specify the Building Block type, category and name when addressing it. If the name is unique in the template, then only the name is required.
The following code snippet shows both possibilities. The two Building Blocks are inserted one after the other at the end of the Document object.
A Building Block is inserted using the Insert method.
Word.Range rng = doc.Content;
rng.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
Word.Template objTmpl = (Word.Template) doc.get_AttachedTemplate(); // NormalTemplate
Word.BuildingBlock objBB = objTmpl.BuildingBlockEntries.Item("TestCCwithActiveX");
objBB.Insert(rng, true);
rng = doc.Content;
rng.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
Word.BuildingBlock objBB2 = objTmpl.BuildingBlockTypes.Item(Word.WdBuildingBlockTypes.wdTypeAutoText).Categories.Item("Test").BuildingBlocks.Item("test heading");
objBB2.Insert(rng, true);
I need to conditionally colorize ranges in a PivotTable. I tried to do it this way:
private void ColorizeContractItemBlocks(List<string> contractItemDescs)
{
int FIRST_DESCRIPTION_ROW = 7;
int DESCRIPTION_COL = 1;
int ROWS_BETWEEN_DESCRIPTIONS = 4;
int rowsUsed = pivotTableSheet.Cells.Rows.Count;
int currentRowBeingExamined = FIRST_DESCRIPTION_ROW;
// Loop through PivotTable data, colorizing contract items
while (currentRowBeingExamined < rowsUsed)
{
Cell descriptionCell = pivotTableSheet.Cells[currentRowBeingExamined, DESCRIPTION_COL];
String desc = descriptionCell.Value.ToString();
if (contractItemDescs.Contains(desc))
{
// args are firstRow, firstColumn, totalRows, totalColumns
Range rangeToColorize = pivotTableSheet.Cells.CreateRange(
currentRowBeingExamined, 0,
ROWS_BETWEEN_DESCRIPTIONS, _grandTotalsColumnPivotTable + 1);
Style style = workBook.Styles[workBook.Styles.Add()];
style.BackgroundColor = CONTRACT_ITEM_COLOR;
StyleFlag styleFlag = new StyleFlag();
styleFlag.All = true;
rangeToColorize.ApplyStyle(style, styleFlag);
}
currentRowBeingExamined = currentRowBeingExamined + ROWS_BETWEEN_DESCRIPTIONS;
}
}
...but it doesn't work, because rowsUsed does not take into consideration the rows on the PivotTable on the pivotTableSheet, and so my while loop is never entered.
How can I determine how many rows the PivotTable takes up on the sheet, so that I can loop through the PivotTable?
Or, am I approaching this the wrong way? Is there a different standard way of manipulating the styles/formatting of a PivotTable after it has been generated?
#B. Clay Shannon, You may consider using any of the following APIs for your requirement. I have added comments to the code for your reference.
var book = new Workbook(dir + "sample.xlsx");
var sheet = book.Worksheets[0];
var pivot = sheet.PivotTables[0];
// DataBodyRange returns CellArea that represents range between the header row & insert row
var dataBodyRange = pivot.DataBodyRange;
Console.WriteLine(dataBodyRange);
// TableRange1 returns complete Pivot Table area except page fields
var tableRange1 = pivot.TableRange1;
Console.WriteLine(tableRange1);
// TableRange2 returns complete Pivot Table area including page fields
var tableRange2 = pivot.TableRange2;
Console.WriteLine(tableRange2);
// ColumnRange returns range that represents the column area of the Pivot Table
var columnRange = pivot.ColumnRange;
Console.WriteLine(columnRange);
// RowRange returns range that represents the row area of the Pivot Table
var rowRange = pivot.RowRange;
Console.WriteLine(rowRange);
In case you still face any difficulty, please share your sample spreadsheet along with desired results (that you may create manually in Excel application) in a thread at Aspose.Cells support forum for thorough investigation.
Note: I am working as Developer Evangelist at Aspose.
The RowRange property of the pivot table should take you row by row through every element in the table:
Excel.Worksheet ws = wb.Sheets["Sheet1"];
Excel.PivotTable pt = ws.PivotTables("PivotTable1");
Excel.Range cell;
foreach (Excel.Range row in pt.RowRange)
{
cell = ws.Cells[row.Row, 5]; // for example, whatever is in column E
// do your formatting here
}
There are other ranges available -- for example, I typically only care about:
pt.DataBodyRange
Which is every cell within the actual pivot table (whatever is being aggregated).
I am writing a program that accesses an excel template containing columns of data (with an unique ID number in the first column). Based on the first two numbers of the ID number, the row will either be kept or deleted. In the template, this unique ID number column feeds an ActiveX Combobox's (located on the Worksheet) ListFill attribute. When the non-matching rows are removed, the ListFill attribute is reset, but the text is not reset.
Example, if I select rows based on '02' being the first two numbers of the unique ID in Column A, I have no problem removing everything that does not start with '02' but the Combobox text still reads "010001" since that is the first Unique ID in the template, even though it doesn't exist in the new list.
I tell you all this to ask if anyone knows a better way to access the combobox? I can access it as an OLEObject, but that does not allow me to change the index or text properties of the combobox as they are 'read only' as per the following intellisense error in VS 2013:
Property or Indexer 'Microsoft.Office.Interop.Excel_OLEObject.Index' cannot be assinged to -- it is read only.
The error appears on the line:
oleobj.Index = 1;
The code snippet is below. The current Excel application is passed as xlApp and the array comboboxes is passed. Each member of the comboboxes array contains the sheet name the combobox is on, the name of the control and the ListFillRange it has on the template. Example array member would be:
Sheet1!:cbTest:$A$1:$A$10
private void ResetComboBoxes2(string[] comboboxes, Excel.Application xlApp)
{
Excel.Worksheet wksht = new Excel.Worksheet();
Excel.Range rng;
int listEndCellNum;
string listEndCellApha;
string listEndCell;
for (int i = 0; i < comboboxes.Length; i++)
{
string[] comboBoxesSplit = comboboxes[i].Split(':');
string sheetName = comboBoxesSplit[0].ToString();
string oleObjName = comboBoxesSplit[1].ToString();
string[] rangeArray = comboBoxesSplit[2].Split(':');
string rangeStart = rangeArray[0];
listEndCellNum = wksht.Range[rangeStart].End[Excel.XlDirection.xlDown].Offset[1, 0].Row - 1;
string[] cellBreakdown = rangeStart.Split('$');
listEndCellApha = cellBreakdown[1];
listEndCell = "$" + listEndCellApha + "$" + listEndCellNum;
string listFull = rangeStart + ":" + listEndCell;
wksht = xlApp.ActiveWorkbook.Worksheets[sheetName];
foreach (Excel.OLEObject oleobj in wksht.OLEObjects())
{
if (oleobj.Name.ToString() == oleObjName)
{
oleobj.ListFillRange = listFull;
oleobj.Index = 1;
}
}
}
}
I'm not even sure there IS a way to do this properly. I could always make a chunk of VBA code to reset it before saving and access that through C# but I am hoping to do it here.
So I was able to figure out that I was doing too much thinking. I went back to VBA then transposed that back to C#. The result was the following code, which yu will notice is considerably shorter and succinct. I had to test the oleObject's programID which for ALL activeX comboboxes is "Forms.ComboBox.1" then grab that object's name, then call it by name, with an extra "Object" in there for good measure.
private void ResetComboBoxes2(string[] comboboxes, Excel.Application xlApp)
{
Excel.Worksheet wksht = new Excel.Worksheet();
for (int i = 0; i < comboboxes.Length; i++)
{
string[] comboBoxesSplit = comboboxes[i].Split(':');
string sheetName = comboBoxesSplit[0].ToString();
wksht = xlApp.ActiveWorkbook.Worksheets[sheetName];
foreach (Excel.OLEObject oleobj in wksht.OLEObjects())
{
if (oleobj.progID == "Forms.ComboBox.1")//oleobj.Name.ToString() == oleObjName)
{
string cbName = oleobj.Name.ToString();
wksht.OLEObjects(cbName).Object.ListIndex = 0;
}
}
}
}
I created a reporting tool as part of an internal web application. The report displays all results in a GridView, and I used JavaScript to read the contents of the GridView row-by-row into an Excel object. The JavaScript goes on to create a PivotTable on a different worksheet.
Unfortunately I didn't expect that the size of the GridView would cause overloading problems with the browser if more than a few days are returned. The application has a few thousand records per day, let's say 60k per month, and ideally I'd like to be able to return all results for up to a year. The number of rows is causing the browser to hang or crash.
We're using ASP.NET 3.5 on Visual Studio 2010 with SQL Server and the expected browser is IE8. The report consists of a gridview that gets data from one out of a handful of stored procedures depending on which population the user chooses. The gridview is in an UpdatePanel:
<asp:UpdatePanel ID="update_ResultSet" runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn_Submit" />
</Triggers>
<ContentTemplate>
<asp:Panel ID="pnl_ResultSet" runat="server" Visible="False">
<div runat="server" id="div_ResultSummary">
<p>This Summary Section is Automatically Completed from Code-Behind</p>
</div>
<asp:GridView ID="gv_Results" runat="server"
HeaderStyle-BackColor="LightSkyBlue"
AlternatingRowStyle-BackColor="LightCyan"
Width="100%">
</asp:GridView>
</div>
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
I was relatively new to my team, so I followed their typical practice of returning the sproc to a DataTable and using that as the DataSource in the code behind:
List<USP_Report_AreaResult> areaResults = new List<USP_Report_AreaResult>();
areaResults = db.USP_Report_Area(ddl_Line.Text, ddl_Unit.Text, ddl_Status.Text, ddl_Type.Text, ddl_Subject.Text, minDate, maxDate).ToList();
dtResults = Common.LINQToDataTable(areaResults);
if (dtResults.Rows.Count > 0)
{
PopulateSummary(ref dtResults);
gv_Results.DataSource = dtResults;
gv_Results.DataBind();
(I know what you're thinking! But yes, I have learned much more about parameterization since then.)
The LINQToDataTable function isn't anything special, just converts a list to a datatable.
With a few thousand records (up to a few days), this works fine. The GridView displays the results, and there's a button for the user to click which launches the JScript exporter. The external JavaScript function reads each row into an Excel sheet, and then uses that to create a PivotTable. The PivotTable is important!
function exportToExcel(sMyGridViewName, sTitleOfReport, sHiddenCols) {
//sMyGridViewName = the name of the grid view, supplied as a text
//sTitleOfReport = Will be used as the page header if the spreadsheet is printed
//sHiddenCols = The columns you want hidden when sent to Excel, separated by semicolon (i.e. 1;3;5).
// Supply an empty string if all columns are visible.
var oMyGridView = document.getElementById(sMyGridViewName);
//If no data is on the GridView, display alert.
if (oMyGridView == null)
alert('No data for report');
else {
var oHid = sHiddenCols.split(";"); //Contains an array of columns to hide, based on the sHiddenCols function parameter
var oExcel = new ActiveXObject("Excel.Application");
var oBook = oExcel.Workbooks.Add;
var oSheet = oBook.Worksheets(1);
var iRow = 0;
for (var y = 0; y < oMyGridView.rows.length; y++)
//Export all non-hidden rows of the HTML table to excel.
{
if (oMyGridView.rows[y].style.display == '') {
var iCol = 0;
for (var x = 0; x < oMyGridView.rows(y).cells.length; x++) {
var bHid = false;
for (iHidCol = 0; iHidCol < oHid.length; iHidCol++) {
if (oHid[iHidCol].length !=0 && oHid[iHidCol] == x) {
bHid = true;
break;
}
}
if (!bHid) {
oSheet.Cells(iRow + 1, iCol + 1) = oMyGridView.rows(y).cells(x).innerText;
iCol++;
}
}
iRow++;
}
}
What I'm trying to do: Create a solution (probably client-side) that can handle this data and process it into Excel. Someone might suggest using the HtmlTextWriter, but afaik that doesn't allow for automatically generating a PivotTable and creates an obnoxious pop-up warning....
What I've tried:
Populating a JSON object -- I still think this has potential but I haven't found a way of making it work.
Using a SQLDataSource -- I can't seem to use it to get any data back out.
Paginating and looping through the pages -- Mixed progress. Generally ugly though, and I still have the problem that the entire dataset is queried and returned for each page displayed.
Update:
I'm still very open to alternate solutions, but I've been pursuing the JSON theory. I have a working server-side method that generates the JSON object from a DataTable. I can't figure out how to pass that JSON into the (external) exportToExcel JavaScript function....
protected static string ConstructReportJSON(ref DataTable dtResults)
{
StringBuilder sb = new StringBuilder();
sb.Append("var sJSON = [");
for (int r = 0; r < dtResults.Rows.Count; r++)
{
sb.Append("{");
for (int c = 0; c < dtResults.Columns.Count; c++)
{
sb.AppendFormat("\"{0}\":\"{1}\",", dtResults.Columns[c].ColumnName, dtResults.Rows[r][c].ToString());
}
sb.Remove(sb.Length - 1, 1); //Truncate the trailing comma
sb.Append("},");
}
sb.Remove(sb.Length - 1, 1);
sb.Append("];");
return sb.ToString();
}
Can anybody show an example of how to carry this JSON object into an external JS function? Or any other solution for the export to Excel.
It's easy and efficient to write CSV files. However, if you need Excel, it can also be done in a reasonably efficient way, that can handle 60,000+ rows by using the Microsoft Open XML SDK's open XML Writer.
Install Microsoft Open SDK if you don't have it already (google "download microsoft open xml sdk")
Create a Console App
Add Reference to DocumentFormat.OpenXml
Add Reference to WindowsBase
Try running some test code like below (will need a few using's)
Just Check out Vincent Tan's solution at http://polymathprogrammer.com/2012/08/06/how-to-properly-use-openxmlwriter-to-write-large-excel-files/ ( Below, I cleaned up his example slightly to help new users. )
In my own use I found this pretty straight forward with regular data, but I did have to strip out "\0" characters from my real data.
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
...
using (var workbook = SpreadsheetDocument.Create("SomeLargeFile.xlsx", SpreadsheetDocumentType.Workbook))
{
List<OpenXmlAttribute> attributeList;
OpenXmlWriter writer;
workbook.AddWorkbookPart();
WorksheetPart workSheetPart = workbook.WorkbookPart.AddNewPart<WorksheetPart>();
writer = OpenXmlWriter.Create(workSheetPart);
writer.WriteStartElement(new Worksheet());
writer.WriteStartElement(new SheetData());
for (int i = 1; i <= 50000; ++i)
{
attributeList = new List<OpenXmlAttribute>();
// this is the row index
attributeList.Add(new OpenXmlAttribute("r", null, i.ToString()));
writer.WriteStartElement(new Row(), attributeList);
for (int j = 1; j <= 100; ++j)
{
attributeList = new List<OpenXmlAttribute>();
// this is the data type ("t"), with CellValues.String ("str")
attributeList.Add(new OpenXmlAttribute("t", null, "str"));
// it's suggested you also have the cell reference, but
// you'll have to calculate the correct cell reference yourself.
// Here's an example:
//attributeList.Add(new OpenXmlAttribute("r", null, "A1"));
writer.WriteStartElement(new Cell(), attributeList);
writer.WriteElement(new CellValue(string.Format("R{0}C{1}", i, j)));
// this is for Cell
writer.WriteEndElement();
}
// this is for Row
writer.WriteEndElement();
}
// this is for SheetData
writer.WriteEndElement();
// this is for Worksheet
writer.WriteEndElement();
writer.Close();
writer = OpenXmlWriter.Create(workbook.WorkbookPart);
writer.WriteStartElement(new Workbook());
writer.WriteStartElement(new Sheets());
// you can use object initialisers like this only when the properties
// are actual properties. SDK classes sometimes have property-like properties
// but are actually classes. For example, the Cell class has the CellValue
// "property" but is actually a child class internally.
// If the properties correspond to actual XML attributes, then you're fine.
writer.WriteElement(new Sheet()
{
Name = "Sheet1",
SheetId = 1,
Id = workbook.WorkbookPart.GetIdOfPart(workSheetPart)
});
writer.WriteEndElement(); // Write end for WorkSheet Element
writer.WriteEndElement(); // Write end for WorkBook Element
writer.Close();
workbook.Close();
}
If you review that code you'll notice two major writes, first the Sheet, and then later the workbook that contains the sheet. The workbook part is the boring part at the end, the earlier sheet part contains all the rows and columns.
In your own adaptation, you could write real string values into the cells from your own data. Instead, above, we're just using the row and column numbering.
writer.WriteElement(new CellValue("SomeValue"));
Worth noting, the row numbering in Excel starts at 1 and not 0. Starting rows numbered from an index of zero will lead to "Corrupt file" error messages.
Lastly, if you're working with very large sets of data, never call ToList(). Use a data reader style methodology of streaming the data. For example, you could have an IQueryable and utilize it in a for each. You never really want to have to rely on having all the data in memory at the same time, or you'll hit an out of memory limitation and/or high memory utilization.
I would try to use displaytag to display the results. You could set it up display a certain number per page, which should solve your overloading issue. Then, you can set displaytag to allow for an Excel export.
We typically handle this with an "Export" command button which is wired up to a server side method to grab the dataset and convert it to CSV. Then we adjust the response headers and the browser will treat it as a download. I know this is a server side solution, but you may want to consider it since you'll continue having timeout and browser issues until you implement server side record paging.
Almost a week and a half since I began this problem, I've finally managed to get it all working to some extent. I'll wait temporarily from marking an answer to see if anybody else has a more efficient, better 'best practices' method.
By generating a JSON string, I've divorced the JavaScript from the GridView. The JSON is generated in code behind when the data is populated:
protected static string ConstructReportJSON(ref DataTable dtResults)
{
StringBuilder sb = new StringBuilder();
for (int r = 0; r < dtResults.Rows.Count; r++)
{
sb.Append("{");
for (int c = 0; c < dtResults.Columns.Count; c++)
{
sb.AppendFormat("\"{0}\":\"{1}\",", dtResults.Columns[c].ColumnName, dtResults.Rows[r][c].ToString());
}
sb.Remove(sb.Length - 1, 1); //Truncate the trailing comma
sb.Append("},");
}
sb.Remove(sb.Length - 1, 1);
return String.Format("[{0}]", sb.ToString());
}
Returns a string of data such as
[ {"Caller":"John Doe", "Office":"5555","Type":"Incoming", etc},
{"Caller":"Jane Doe", "Office":"7777", "Type":"Outgoing", etc}, {etc} ]
I've hidden this string by assigning the text to a Literal in the UpdatePanel using:
<div id="div_JSON" style="display: none;">
<asp:Literal id="lit_JSON" runat="server" />
</div>
And the JavaScript parses that output by reading the contents of the div:
function exportToExcel_Pivot(sMyJSON, sTitleOfReport, sReportPop) {
//sMyJSON = the name, supplied as a text, of the hidden element that houses the JSON array.
//sTitleOfReport = Will be used as the page header if the spreadsheet is printed.
//sReportPop = Determines which business logic to create a pivot table for.
var sJSON = document.getElementById(sMyJSON).innerHTML;
var oJSON = eval("(" + sJSON + ")");
// DEBUG Example Test Code
// for (x = 0; x < oJSON.length; x++) {
// for (y in oJSON[x])
// alert(oJSON[x][y]); //DEBUG, returns field value
// alert(y); //DEBUG, returns column name
// }
//If no data is in the JSON object array, display alert.
if (oJSON == null)
alert('No data for report');
else {
var oExcel = new ActiveXObject("Excel.Application");
var oBook = oExcel.Workbooks.Add;
var oSheet = oBook.Worksheets(1);
var oSheet2 = oBook.Worksheets(2);
var iRow = 0;
var iCol = 0;
//Take the column names of the JSON object and prepare them in Excel
for (header in oJSON[0])
{
oSheet.Cells(iRow + 1, iCol + 1) = header;
iCol++;
}
iRow++;
//Export all rows of the JSON object to excel
for (var r = 0; r < oJSON.length; r++)
{
iCol = 0;
for (c in oJSON[r])
{
oSheet.Cells(iRow + 1, iCol + 1) = oJSON[r][c];
iCol++;
} //End column loop
iRow++;
} //End row
The string output and the JavaScript 'eval' parsing both work surprisingly fast, but looping through the JSON object is a little slower than I'd like.
I believe that this method would be limited to around 1 billion characters of data -- maybe less depending how memory testing works out. (I've calculated that I'll probably be looking at a maximum of 1 million characters per day, so that should be fine, within one year of reporting.)
I have long tables generated by datagrid control that go beyond the page width. I would like to convert that into separate table for each row or definition list where each field name is followed by field value.
How would I do that.
Uses jquery. If you have more than one table you'll need to change it to accommodate that. Also, just appends to the end of the document. If you want it elsewhere, find the element you want to place it after and insert it into the DOM at that point.
$(document).ready(
function() {
var headers = $('tr:first').children();
$('tr:not(:first)').each(
function(i,row) {
var cols = jQuery(row).children();
var dl = jQuery('<dl></dl>');
for (var i=0, len = headers.length; i < len; ++i) {
var dt = jQuery('<dt>');
dt.text( jQuery(headers[i]).text() );
var dd = jQuery('<dd>');
dd.text( jQuery(cols[i]).text() );
dl.append(dt).append(dd);
}
$('body').append(dl);
}
);
$('table').remove();
}
);
Here's a reference:
http://www.mail-archive.com/flexcoders#yahoogroups.com/msg15534.html
The google terms I think you want are "invert datagrid". You'll get lots of hits.