DocX c# library merging table fields in a row - c#

I am working with DocX, a library for creating Microsoft .docx files from C#. https://docx.codeplex.com/
I need to merge some tables inside same row. I already figured out how to merge them inside a column.
var doc = DocX.Load(fileName);
Table t3 = doc.AddTable(39, 11);
t3.Alignment = Alignment.center;
t3.Design = TableDesign.TableGrid;
t3.MergeCellsInColumn(0, 0, 9);
doc.InsertTable(t3);

Row row = table.InsertRow();
if use: row.MergeCells(0, 3);
It merge both column and row
So, can use to remove rows empty
row.Cells[0].RemoveParagraphAt(3);
row.Cells[0].RemoveParagraphAt(2);
row.Cells[0].RemoveParagraphAt(1);
and use : table.MergeCellsInColumn(2, 1, 1);
Only merge rows in a column

To merge cells inside a row you need to define the row first. A bitt different than in Colums where you define the colum inside the function.
t2.Rows[0].MergeCells(1, 2);

Set font use Docx.dll:
FontFamily font = new FontFamily("Times New Roman");
Row row = table.InsertRow();
row.Paragraphs[0].Font(font).FontSize(13);

Related

EPPLUS ExcelRange creates table starting in wrong row

In C# I have populated an excel file with data. Now I would like to create a table starting at cell A2.
I am using the code below to create the table but instead of creating the table starting at Cell A2, the table is being created starting at cell A3
using var package = new ExcelPackage(file);
var ws = package.Workbook.Worksheets.Add(Name: "MainReport");
var range = ws.Cells[Address: "A2"].LoadFromCollection(people, PrintHeaders: true);
range.AutoFitColumns();
//Formats Header row
ws.Cells[Address: "A1"].Value = "My Data!";
ws.Cells[Address: "A1:C1"].Merge = true;
ws.Column(col: 1).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
ws.Row(row: 1).Style.Font.Size = 24;
ws.Row(row: 1).Style.Font.Color.SetColor(Color.Pink);
ws.Row(row: 2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
ws.Row(row: 2).Style.Font.Bold = true;
ws.Column(col: 3).Width = 20;
//create a range for the table
ExcelRange range_table = ws.Cells[2, 1, ws.Dimension.End.Row, ws.Dimension.End.Column];
//add a table to the range
ExcelTable tab = ws.Tables.Add(range_table, "Table1");
//format the table
tab.TableStyle = TableStyles.Medium2;
Excel itself works so that if you select A2:C4 and create a table and you say "has no Headers" it will put some generic headers in A2:C2 and shift the data area (and the data if there is any) to A2:C5.
If you say "has Headers" the headers will be defined in A2:C2, too, but the data only is in A3:C4.
The real Excel obviously does some guessing whether headers are present or not and pre-ticks the box for you.
As stated here in the API Documentation, there is no flag to define if headers are present in the range so there might be no guessing and it might be assumed it is "no" and the shifting as explained on top takes place.
If you take that into consideration you should be able to work it out.

Adding datatype to table columns with ClosedXML

I'm using ClosedXML to create an xlsx-file with some data. In the Excel-sheet I have a table with data. In the columns of the table (not the column of the sheet) I want to specify datatypes.
/* This datatable is created and populated somewhere else in my program*/
Datatable dt = new Datatable();
ws.Cell(t.Position.Row, t.Position.Column).InsertTable(dt);
IXLTables allTables = ws.Tables;
var table = allTables.ElementAt(i);
int j = 1;
/* The options object hold the excel datatypes for each column*/
foreach (var c in option.Tables.ElementAt(i).Columns)
{
table.Column(j).DataType = c.Type;
j++;
}
i++;
The datatype is found and added in the foreach-loop from an options object, how this works in my program is probably not important.
The problem is that when I add a datatype to a column in the table it includes the header of the table. Since the header is "Text" and the value I might specified is Number I get an error. Anyone got an idea how to make it ignore the headerline of the table and simply add datatype to the columns below?
Thanks in advance.
Since setting the data type can work on a per column basis in Excel it's not unreasonable to expect the same from ClosedXml. As you've found it doesn't work like that however. The way I do it is to define a range that selects the whole column except the header. I've not worked with tables the way you are but the following snippet should give you a direction.
IXLWorksheet sheet = wb.Worksheets.Add("Sheet1");
sheet.Cell(1, 1).InsertTable(dt);
foreach (var column in sheet.ColumnsUsed())
{
string columnLetter = column.ColumnLetter();
string rng = $"${columnLetter}2:columnLetter}sheet.RangeUsed().RowCount()}";
sheet.Range(rng).DataType = some data type;
}

Remove cells from word table with Novacode DocX

How do I remove a single cell from a word table using Novacode DocX?
I have tried:
table.Rows[0].Cells.RemoveAt(2);
and
Cell c = table.Rows[0].Cells[2];
table.Rows[0].Cells.Remove(c);
and
table.Rows[0].Cells.RemoveRange(2,4);
None of them remove any cells.
I have been working with Novacode DocX very recently. Something I have realized is that a lot of the methods classes have are inherited and are not overridden. An example is cell inherits Remove() from container and is never overridden to do what you would expect it to do.
sorcecode for Novacode.Table
What you can do to try and get around this is to hide the cell/cells, since I am assuming you are not removing an entire row or column.
Novacode.Table table = document.Tables[0]
Novacode.Row row = table.Rows[0]; //int of row that contains cell/cells to remove
int first_cell = 0; //beginning cell of range
int last_cell = 1; //last cell of range
for(int i = first_cell; i < last_cell + 1; i++)
{
foreach (var paragraph in row.Cells[i].Paragraphs)
{
paragraph.Remove(false);
}
}
row.MergeCells(first_cell, last_cell);
Novacode.Cell cell = row.Cells[first_cell];
Novacode.Border border = new Border();
border.Tcbs = Novacode.BorderStyle.Tcbs_none;
cell.SetBorder(Novacode.TableCellBorderType.Right, border);
cell.SetBorder(Novacode.TableCellBorderType.Left, border);
cell.SetBorder(Novacode.TableCellBorderType.Top, border);
cell.SetBorder(Novacode.TableCellBorderType.Bottom, border);
This code would remove text and merge the first two cells of your first table and make there borders invisible. So this would turn the area you want deleted into one invisible blank cell, assuming you are using Paragraphs of text in the Cell.
You may have to save the table back to the document.
Try
appending the table to the end of the document. if that's missing the cells
Remove and replace the table in the document.
You have to clear the child XML from the Cell Element yourself, as it is not correctly handled by the NovaCode DocX Api:
table.Rows.Last().Cells.Last().Xml.RemoveAll();
table.Rows.Last().Cells.Last().InsertParagraph();
Note that the InsertParagraph in the end is necessary as Cells cannot be empty.
In the Table class you can use the methods
MergeCells(int, int)
and
MergeCellsInColumn(int, int, int)
to 'remove' cells.
See: https://docx.codeplex.com/SourceControl/latest#DocX/Table.cs

Add Columns to Existing Excel 2007 workbook using Open Xml

I have a predefined Excel workbook with all sheets in place and I need to write content to it. I succesfully write to cells.
The problem is in a particular worksheet that i need to add three columns to it. In the code bellow, first i'm grabbing the Worksheet and then i proceed to add columns. This code runs fine, i mean, no exception is thrown, but then I get an error when I try to open the Excel file, stating that there are some content that cannot be read and all the content of this particular worksheet is cleared.
I know that the problem is with this operation because if I comment out those lines that add columns, the workbook opens just fine with all the cells values I write from code in place.
This is the relevant code, for testing purpose I'm trying to add 3 columns:
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );
Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;
Columns cs = new Columns();
for ( var y = 1; y <= 3; y++ ) {
Column c = new Column()
{
Min = (UInt32Value)1U,
Max = (UInt32Value)1U,
Width = 44.33203125D,
CustomWidth = true
};
cs.Append( c );
}
workSheet2.Append( cs );
}
EDIT : As per Chris's explanation about columns's concept
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)){
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single( s => s.Name == "Miscellaneous Credit" );
Worksheet workSheet2 = ( (WorksheetPart)document.WorkbookPart.GetPartById( sheet2.Id ) ).Worksheet;
// Check if the column collection exists
Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
if ( ( cs == null ) ) {
// If Columns appended to worksheet after sheetdata Excel will throw an error.
SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
if ( ( sd != null ) ) {
cs = workSheet2.InsertBefore( new Columns(), sd );
}
else {
cs = new Columns();
workSheet2.Append( cs );
}
}
//create a column object to define the width of columns 1 to 3
Column c = new Column
{
Min = (UInt32Value)1U,
Max = (UInt32Value)3U,
Width = 44.33203125,
CustomWidth = true
};
cs.Append( c );
}
This first part of answer deals about how to set columns width (based on the initial sample code, I was thinking that you wanted only define the width of the columns).
First, it seems you misunderstood what are Min and Max properties of the Column object. They represent respectively First and Last column affected by this 'column info' record. So if you have a set of contiguous columns with the same width, you can set that width using one Column class. In your snippet you define 3 times the width of the same column (Index 1).
Then, you presume Columns collection doesn't exist yet...
And finally, the main point is that if the Columns collection is appended after SheetData, Excel will throw error.
Final code that work for me (Open XML SDK 2.0)
using (SpreadsheetDocument document = SpreadsheetDocument.Open(outputPath, true)) {
Sheet sheet2 = document.WorkbookPart.Workbook.Descendants<Sheet>().Single(s => s.Name == "Your sheet name");
Worksheet workSheet2 = ((WorksheetPart)document.WorkbookPart.GetPartById(sheet2.Id)).Worksheet;
// Check if the column collection exists
Columns cs = workSheet2.Elements<Columns>().FirstOrDefault();
if ((cs == null)) {
// If Columns appended to worksheet after sheetdata Excel will throw an error.
SheetData sd = workSheet2.Elements<SheetData>().FirstOrDefault();
if ((sd != null)) {
cs = workSheet2.InsertBefore(new Columns(), sd);
} else {
cs = new Columns();
workSheet2.Append(cs);
}
}
//create a column object to define the width of columns 1 to 3
Column c = new Column {
Min = (UInt32Value)1U,
Max = (UInt32Value)3U,
Width = 44.33203125,
CustomWidth = true
};
cs.Append(c);
}
I'm still confused on how to perform column insert. Says I have
columns A, B and C, I want to insert three columns between B and C,
ending up with columns A,B,C,D,E,F. How can i achieve it?
The Columns object in OpenXml SDK is here to store styles and width informations for the columns. Inserting a Column in the collection won't "insert" a column in the sheet.
"Inserting" a column like you mean is a very large and complex task with OpenXmlSDK.
From my understanding of the problem, it means you will have to find all cells and shift them by changing their reference (ex. a cell with ref "B1" would become "F1" after inserting 3 columns, etc ...). And it means you will have to change a lot of other things (reference of cell in formulas for example).
This kind of task could be easily done with Office.Interop or probably with libraries like EEPlus or ClosedXml.

Select range in aspose

Do you know an equivalent to VBA code:
Range(Selection, Selection.End(xlToRight)).Select
In Aspose.Cells. It seems that its only possible to select the last cell in the entire row:
public Aspose.Cells.Cell EndCellInRow ( Int32 rowIndex )
Or the last cell on the right within a range:
public Aspose.Cells.Cell EndCellInRow ( Int32 startRow, Int32 endRow, Int32 startColumn, Int32 endColumn )
but then you must know more or less how big your table is going to be.
I found this from 2009: http://www.aspose.com/community/forums/permalink/196519/196405/showthread.aspx but that will not resolve my problem as I may have many tables in a sheet both horizontally and vertiacally. And I can't predict where they are going to be.
Edit1:
Sorry if this is dumb question, but ctrl+shift+arrow is such a common operation that I can't believe it would be not implemented so I'm making sure I really have to re-invent the wheel.
Aspose.Cells provides the list of tables in a worksheet using property named 'Worksheet.ListObjects'. 'ListObjects' is a colloection of 'ListObject' type which represents a Table in an excel sheet. That means if one has more than one Tables in a worksheet, the ListObjects collection will give access to every table in the worksheet very conveniently. Each 'ListObject' in turn contains a property named 'DataRange' which specifies all the cells inside a Table. For the sake of convenience DataRange can be used for following operations on a Table:
To apply styles/formatting on the cells in Table
To get the data values
Merge or move the cells in Range
Export contents
To get enumerator to traverse through Table cells
To make selection of cells from DataRange, you can traverse using DataRange to get all the cells in a Row (This could also be done for a column)
Applying any operation on Table cells like after selecting cells using Ctrl+Shift+Arrow, could be performed using a workbook object as follows:
Workbook workbook = new Workbook(new FileStream("book1.xls", FileMode.Open));
if (workbook.Worksheets[0].ListObjects.Count > 0)
{
foreach (ListObject table in workbook.Worksheets[0].ListObjects)
{
Style st = new Style();
st.BackgroundColor = System.Drawing.Color.Aqua;
st.ForegroundColor = System.Drawing.Color.Black;
st.Font.Name = "Agency FB";
st.Font.Size = 16;
st.Font.Color = System.Drawing.Color.DarkRed;
StyleFlag stFlag = new StyleFlag();
stFlag.All = true;
table.DataRange.ApplyStyle(st, stFlag);
}
}
workbook.Save("output.xls");
There is also some worthy information available in Aspose docs about Table styles and applying formatting on a ListObject. For getting last Table cell in a certain row or column, I am sure this will help:
int iFirstRowIndex = table.DataRange.FirstRow;
int iFirstColumnIndex = table.DataRange.FirstColumn;
int iLastRowIndex = table.DataRange.RowCount + iFirstRowIndex;
int iLastColumnIndex = table.DataRange.ColumnCount + iFirstColumnIndex;
for (int rowIndex = 0; rowIndex < table.DataRange.RowCount; rowIndex++)
{
//Get last cell in every row of table
Cell cell = worksheet.Cells.EndCellInColumn(rowIndex + iFirstRowIndex, rowIndex + iFirstRowIndex, (short)iFirstColumnIndex, (short)(iLastColumnIndex - 1));
//display cell value
System.Console.WriteLine(cell.Value);
}

Categories

Resources