OfficeOpenXML excel Resize rows with cells merged across them - c#

Text in a merged cell across rows is cut off because Excel ignores merged cells when automatically resizing a row.
How can I resize cell height to its contents for rows that resize doesn't work for?
I am posting this out there to help others that had this problem. I posted a solution as an answer.

For this solution, I am only showing code related to resizing rows not displaying data
Solution:
///
/// Merged Row Model Psuedo Code
///
MergedRow {
// Fields merged across rows
...
// list of rows with non merged data
list<indvidulerow> data
}
///
/// Code to find rows with merged cells.
///
... // worksheet prep
var mergedRows = new Dictionary<int, MergedRow>();
... // begin generating rows
...
// code generating the first merged cell of a row
using (var r = sheet.Cells[rowIndex, 1, rowIndex+ mergedRow.Data.Count - 1, 1]){
if(mergedRow.Data.count>1){
r.Merge=true;
mergedRows.Add(rowIndex, mergedRow);
}
... // code for remainder of of cell
///
/// Code to Resize Rows. Making last row expand enough to see
/// all content in merged cells
///
foreach (var firstRowIndex in mergedRows.Keys){
var numRowsMerged = mergedRows[firstRowIndex].Data.Count;
var columnHeights = new List<ColumnHeightCalcModel>();
for (var colIndex = 0; colIndex < totalColumns; colIndex++){
var calcModel = new ColumnHeightCalcModel();
var combinedHeight = 0.0;
var lastRowHeight = 0.0;
for (rowIndex = 0; rowIndex < numRowsMerged; rowIndex++){
var cell = sheet.Cells[firstRowIndex + rowIndex, colIndex + 1];
var cellText = cell.Text;
var cellmerged = cell.Merge;
var cellWidth = sheet.Column(colIndex+1).Width;
var font = cell.Style.Font;
if (string.IsNullOrEmpty(cellText)) combinedHeight += 0.0;
var bitmap =new Bitmap(1,1);
var graphics = Graphics.FromImage(bitmap);
var pixelWidth = Convert.ToInt32(cellWidth * 6.0); //6.0 pixels per excel column width
var drawingFont = new Font(font.Name, font.Size);
var size = graphics.MeasureString(cellText, drawingFont, pixelWidth);
//72 DPI and 96 points per inch. Excel height in points with max of 409 per Excel requirements.
combinedHeight += Math.Min(Convert.ToDouble(size.Height) * 72 / 96, 409);
if (cellmerged) break;
lastRowHeight = Math.Min(Convert.ToDouble(size.Height) * 72 / 96, 409);
}
calcModel.TotalCombinedHeight = combinedHeight;
calcModel.LastRowHeight = lastRowHeight;
columnHeights.Add(calcModel);
}
var row = sheet.Row(firstRowIndex + numRowsMerged - 1);
row.CustomHeight = true;
var maxCombo = columnHeights.Max(col => col.TotalCombinedHeight);
var maxLast = columnHeights.Max(col => col.LastRowHeight);
row.Height = maxCombo - maxLast;
}
End Solution
Links that helped me find this solution
The following link was for Merged columns but it helped with merged rows.
Autofit Row Height of Merged Cell in EPPlus

Related

Why the last row never gets read?

Hello well i have one question why the last row never gets read? It dosenĀ“t matter if its only one row in the excel file or 100 rows. The last row never shows up in the List. And i have no clue why....
Here is my Excel File:
and this is my method:
public List<string> getListData(bool skipFirstRow, int numberOfColumns, string filepath)
{
int startpoint = 1;
int cell = 1;
int row = 1;
List<string> stringList = new List<string>();
//Open Excel (Application)
var excelApplication = openExcelApplication();
//Open Excel File
Excel.Workbook excelWorkbook = excelApplication.Workbooks.Open(filepath);
//Get the Worksheets from the file
Excel.Sheets excelSheets = excelWorkbook.Worksheets;
//Select the first Worksheet
Excel.Worksheet worksheet = (Excel.Worksheet)excelSheets.get_Item(1);
if (skipFirstRow == true)
{
startpoint = 2;
}
Excel.Range range = worksheet.get_Range("A" + Convert.ToString(startpoint), Missing.Value);
while ((range.Cells[startpoint, cell] as Excel.Range).Value2 != null)
{
for (int i = 1; i <= numberOfColumns + 1; i++)
{
string sValue = (range.Cells[row, cell] as Excel.Range).Value2.ToString();
stringList.Add(sValue);
cell++;
}
startpoint++;
cell = 1;
row++;
}
closeExcelApplication(excelApplication);
var result =
stringList
.Select((item, index) => new { Item = item, Index = index })
.GroupBy(x => x.Index / numberOfColumns)
.Select(g => string.Join(";", g.Select(x => x.Item)))
.ToList();
return result;
}
I tried it with the debugger and even google. Then i tried it with the last used row stuff but didnt worked.
Excel.Range last = worksheet.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
Excel.Range range = worksheet.get_Range("A1", last);
int lastUsedRow = last.Row;
int lastUsedColumn = last.Column;
Any help or advise would be great so thanks for your time and help.
Your algorithm is buggy.
Let's see what happens when skipFirstRow is true and your Excel sheet has three rows 1, 2 and 3. At the start of the while loop, we have the following situation:
startpoint = 2
row = 1
During the first iteration, your while loop reads the contents of row 1. After the iteration, we have the following situation:
startpoint = 3
row = 2
During the second iteration, your while loop reads the contents of row 2. After the iteration, we have the following situation:
startpoint = 4
row = 3
Since range.Cells[startpoint, cell] is empty, your code stops here. Rows 1 and 2 have been read, row 3 has been ignored.
As you can see, the reason for your problem is that you check the row in startpoint and read the row in row, and when those two differ, you have a problem. Suggested solution: Drop the startpoint variable and use row instead.
The excel sheet display first row number as 1
but internally it starts from 0, may be it is reading all the data,put different test data on last column

EPPLUS AutoFit cells

how can i set autosize on my cells, based on the max length of one input.
using (rng = workSheet.Cells["A1:G1"])
{
rng.Style.Font.Bold = true;
rng.Style.Fill.PatternType = ExcelFillStyle.Solid;
rng.Style.Fill.BackgroundColor.SetColor(Color.DarkBlue);
rng.Style.Font.Color.SetColor(Color.White);
}
using (ExcelRange col = workSheet.Cells[2, 6, 7, 7])
{
col.Style.Numberformat.Format = "yyyy-mm-dd HH:mm";
col.Style.HorizontalAlignment = ExcelHorizontalAlignment.Right;
}
for (int i = 1; i <= a; i++)
{
workSheet.Cells["A1"].Value = "RU_ID";
workSheet.Cells["B1"].Value = "COR_REQ_ID";
workSheet.Cells["C1"].Value = "RU_NAME";
workSheet.Cells["D1"].Value = "PARENT_RU_NAME";
workSheet.Cells["E1"].Value = "ADJUSTMENT_STATE";
workSheet.Cells["F1"].Value = "COR_START";
workSheet.Cells["G1"].Value = "COR_END";
}
...
rng.AutoFitColumns();
string path = #"D:\excel\test.xlsx";
Stream stream = File.Create(path);
excel.SaveAs(stream);
stream.Close();
byte[] data = File.ReadAllBytes(path);
}
The only thing that AutoFitColumn is doing is to bring the cell to the size of the header, as if i have the header as "STH" and the inputs as "Something good", "something to increase cell size" than AutoFitColumn will set the size based on "STH" not "something to increase cell size".
Thanks in advance for the help.
Look at your lines:
using (rng = workSheet.Cells["A1:G1"])
...
rng.AutoFitColumns();
Notice you are call AutoFitColumns on the range of of your headers A1:G1 so EPPlus is using only those cells to determine the width of the columns.
Just do this instead:
workSheet.Cells.AutoFitColumns();
since Cells in Epplus only contain cells with actual values so there is no real concern over efficiency.

Why the 2nd row of table won't be written? [duplicate]

This question already has an answer here:
Odd Numbered Cell Not Added To Pdf
(1 answer)
Closed 7 years ago.
I'm trying to create table without borders with 4 columns and 2 rows on top of my PDF document. The problem is that the 2nd row won't be written. This is my code:
float[] columnWidths = { 2, 1, 1, 1};
PdfPTable table = new PdfPTable(columnWidths);
table.WidthPercentage = 100;
if (...) //true
{
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("AAA:_______________",infoFont));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 1st col,1st row
}
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("BBB:_____", infoFont));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 2nd col,1st row
}
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("CCC:_____", infoFont));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 3rd col,1st row
}
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("DDD:_____", infoFont));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 4th col,1st row
}
}
if (...) //true
{
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("EEE: " + eee));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 1st col,2nd row
}
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("FFF: " + fff));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 2nd col,2nd row
}
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("GGG: " + ggg));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 3rd col,2nd row
}
if (...) //true
{
PdfPCell p = new PdfPCell(new Phrase("HHH:___________________"));
p.BorderWidth = 0;
table.AddCell(p); // fixed pos. 4th col,2nd row
}
}
document.Add(table);
How can I deal with this? And the 2nd question: can I have fixed position for every if condition (check the comments in the code) so when one if-condition in first row is not true then that cell should be empty?
I assume that you've simplified your code to the extent that the snippet you share is no longer consistent with your own code. You are creating a table with 4 columns. If you add 4 cells, one row will be rendered. If you add 8 cells, two rows will be rendered. However: if you only add 7 cells, then a single row will be added. The 3 cells in the incomplete row will be omitted because iText only renders complete rows.
See also How to generate pdf if our column less than the declared table column and ItextSharp, number of Cells not dividable by the length of the row and Odd Numbered Cell Not Added To Pdf and PdfTable: last cell is not visible and ...
This explains why the second row isn't shown. Add the following line to see if this fixes the problem:
table.CompleteRow();
As for your other question: you can always add an empty cell like this:
PdfPCell cell = new PdfPCell();
if (someCondition) {
cell.addElement(new Paragraph("AAA"));
}
table.addCell(cell);
Finally, there's another error in your code. This doesn't make any sense:
p.BorderWidth = 0;
A border width of 0 doesn't mean that no border will be shown. As explained many times before ISO-32000-1 defines a line with 0 width as a line of which the width is equal to the minimal width that can be displayed by the device. If you don't want any border use:
p.Border = PdfPCell.NO_BORDER;
Finally, I need to ask you a favor: we've redesigned the iText web site and we released it on Thanksgiving. We now notice that we don't get as many visits as we used to before the change. Given the fact that all the information you needed can be found on the online documentation and given the fact that you still needed to ask the question, we'd like to know what is wrong with the web site. Is there something we can do to improve the content? What could be the reason that drives people away from our web site? Why are you asking so many questions that are already answered in the official documentation? Do we have too much content now?

background worker and progress bar implementation for additional column in datagridview?

still relatively new to C#, im trying to get my head around the background worker and progress bar suited to my app, the following code shows i am adding a new column to a datagridview and then filling all the cells of the new column with a zero (0). what would be the best way to implement the background worker and progress bar for this.
The whole process takes around 15 seconds hence the need for a progress bar to show the user something is happening.
The code for adding and filling the new column is as follows:
//Creates new column in the datagridview
DataGridViewColumn newCol = new DataGridViewColumn();
newCol.CellTemplate = new DataGridViewTextBoxCell();
newCol.HeaderText = tbAddSupp.Text.ToUpper();
newCol.Name = tbAddSupp.Text.ToUpper();
newCol.Visible = true;
dgvStock.Columns.Add(newCol);
cbSuppList.Items.Clear();
cbSuppList2.Items.Clear();
//Adds the default 0 value to all the cells in the new column
//ITS TOO SLOW THOUGH....!!!!!
int cellVal = 0;
foreach (DataGridViewRow row in dgvStock.Rows)
{
for (int r = 0; r < dgvStock.Rows.Count - 1; r++)
{
dgvStock.Rows[r].Cells[newCol.Name.ToString()].Value = cellVal;
}
}
It is slow because you are doing the same loop twice. I think this is enough:
int cellVal = 0;
foreach (DataGridViewRow row in dgvStock.Rows)
{
row.Cells[newCol.Name.ToString()].Value = cellVal;
}
In fact if you have just 100 rows your loop was running from 100 * 100 = 10000 times!

How do I autofit a column that has merged cells?

In C#, for an excel spreadsheet, how do I autofit a column that has merged cells? I tried doing this with the code below, but it does not autofit the column.
Here is my code so far:
Worksheet xlSheet
xlSheet.Cells[2, 1] = "Autobiographies and Titles, Autobiographies and Titles, Autobiographies
and Titles, Autobiographies and Titles, Autobiographies and Titles, Autobiographies and Titles,
Autobiographies and Titles, Autobiographies and Titles";
Range hRangeSubsystemName = xlSheet.get_Range("A2", "G2");
hRangeSubsystemName.MergeCells = true;
hRangeSubsystemName.EntireColumn.AutoFit();
You can't use autofit on columns with merged cells in Excel.
See MS support article:
http://support.microsoft.com/kb/212010
I wrote a function to do this with Gembox which you might find helpful
private int AutoSizeMergedCells(CellRange myMergedCells, string text)
{
var file = new ExcelFile();
file.Worksheets.Add("AutoSize");
var ws = file.Worksheets[0];
ws.Cells[0, 0].Column.Width = myMergedCells.Sum(x => x.Column.Width);
ws.Cells[0, 0].Value = text;
ws.Cells[0, 0].Style.WrapText = true;
ws.Cells[0, 0].Row.AutoFit();
var result = ws.Cells[0, 0].Row.Height;
file = null;
return result;
}
Perhaps you can convert this into C#, but I found a VB macro here that will simulate the autofit of any merged cells on the active sheet. Source credits parry from MrExcel.com
Sub AutoFitMergedCellRowHeight()
Dim CurrentRowHeight As Single, MergedCellRgWidth As Single
Dim CurrCell As Range
Dim ActiveCellWidth As Single, PossNewRowHeight As Single
Dim StartCell As Range, c As Range, MergeRng As Range, Cell As Range
Dim a() As String, isect As Range, i
'Take a note of current active cell
Set StartCell = ActiveCell
'Create an array of merged cell addresses that have wrapped text
For Each c In ActiveSheet.UsedRange
If c.MergeCells Then
With c.MergeArea
If .Rows.Count = 1 And .WrapText = True Then
If MergeRng Is Nothing Then
Set MergeRng = c.MergeArea
ReDim a(0)
a(0) = c.MergeArea.Address
Else
Set isect = Intersect(c, MergeRng)
If isect Is Nothing Then
Set MergeRng = Union(MergeRng, c.MergeArea)
ReDim Preserve a(UBound(a) + 1)
a(UBound(a)) = c.MergeArea.Address
End If
End If
End If
End With
End If
Next c
Application.ScreenUpdating = False
'Loop thru merged cells
For i = 0 To UBound(a)
Range(a(i)).Select
With ActiveCell.MergeArea
If .Rows.Count = 1 And .WrapText = True Then
'Application.ScreenUpdating = False
CurrentRowHeight = .RowHeight
ActiveCellWidth = ActiveCell.ColumnWidth
For Each CurrCell In Selection
MergedCellRgWidth = CurrCell.ColumnWidth + MergedCellRgWidth
Next
.MergeCells = False
.Cells(1).ColumnWidth = MergedCellRgWidth
.EntireRow.AutoFit
PossNewRowHeight = .RowHeight
.Cells(1).ColumnWidth = ActiveCellWidth
.MergeCells = True
.RowHeight = IIf(CurrentRowHeight > PossNewRowHeight, _
CurrentRowHeight, PossNewRowHeight)
End If
End With
MergedCellRgWidth = 0
Next i
StartCell.Select
Application.ScreenUpdating = True
'Clean up
Set CurrCell = Nothing
Set StartCell = Nothing
Set c = Nothing
Set MergeRng = Nothing
Set Cell = Nothing
End Sub
please try this
private double AutoSizeMergedCells( string text)
{
Excel.Worksheet ws = xlWorkBook.Sheets[1];
`enter code here`ws.Cells[14, 10].ColumnWidth = 9.29+7.43+10.71+11.29;(size width range)
ws.Cells[14, 10].Value = text;
ws.Cells[14, 10].Style.WrapText = true;
ws.Cells[14, 10].Rows.AutoFit();
var result = ws.Cells[14, 10].RowHeight;
ws.Cells[14, 10].Value = "";
return result;
}
I had the same issue. I've made workaround. After filling Worksheet with data, I use my ExtensionMethod mySheet.AutoFitRowsWithMergedCells();
I'm iterating all data in worksheet. When I find merged cell I copy it's sizes and value into not merged temporary cell before last used column in same row, executes AutoFit(), witch now works correctly and removes temp cell.
public static void AutoFitRowsWithMergedCells(this Worksheet worksheet)
{
var range = worksheet.UsedRange;
int rowsCount = range.Rows.Count;
int columnsCount = range.Columns.Count;
for (int rowIndex = 1; rowIndex <= rowsCount; rowIndex++)
{
for (int columnIndex = 1; columnIndex <= columnsCount; columnIndex++)
{
var subRange = range.GetSubRange(rowIndex, columnIndex);
if ((bool)subRange.MergeCells && subRange.Value != null)
{
var mergedArea = subRange.MergeArea;
double mergedColumnsWidth = 0;
foreach (dynamic column in mergedArea.Columns)
{
mergedColumnsWidth += column.ColumnWidth;
}
var tempRange = worksheet.Cells[rowIndex, columnsCount + 1];
tempRange.Value = subRange.Value;
tempRange.Style.WrapText = true;
double originalTempColumnWidth = tempRange.Columns[1].ColumnWidth;
tempRange.Columns[1].ColumnWidth = mergedColumnsWidth;
subRange.EntireRow.AutoFit();
double correctHeight = subRange.EntireRow.RowHeight;
tempRange.Value = string.Empty;
tempRange.Columns[1].ColumnWidth = originalTempColumnWidth;
subRange.EntireRow.RowHeight = correctHeight;
}
}
}
}

Categories