I am new to iTextSharp, and want to convert my DataGridView to a PdfPtable.
When i write the table to PDF, it shows up nicely, except when the amount of columns is relatively large. Then it accomodates by making the column width smaller.
Instead, I would like to start on a new page, with more of the table shown, with headers.
Below is my code for the table:
PdfPTable table = new PdfPTable( view.Columns.Count );
float[] widths = new float[view.Columns.Count];
for ( int i = 0; i < view.Columns.Count; i++ ) {
widths[i] = view.Columns[i].Width;
}
table.SetWidths( widths );
table.HorizontalAlignment = 1; //Center
PdfPCell cell = null;
//Headers
foreach ( DataGridViewColumn c in view.Columns ) {
cell = new PdfPCell( new Phrase( new Chunk( c.HeaderText, _standardFont ) ) );
cell.HorizontalAlignment = PdfPCell.ALIGN_CENTER;
cell.VerticalAlignment = PdfPCell.ALIGN_CENTER;
table.AddCell( cell );
}
//Rest of table
for ( int i = 1; i < view.Rows.Count; i++ ) {
for ( int j = 0; j < view.Columns.Count; j++ ) {
bool hasValue = view.Rows[i].Cells[j].Value == null ? false : true;
if ( hasValue ) {
cell = new PdfPCell( new Phrase( view.Rows[i].Cells[j].Value.ToString(), _standardFont ) );
} else {
cell = new PdfPCell( new Phrase( "", _standardFont ) );
}
cell.HorizontalAlignment = PdfPCell.ALIGN_CENTER;
cell.VerticalAlignment = PdfPCell.ALIGN_CENTER;
table.AddCell( cell );
}
}
One solution would be to somehow know how many columns could be printed fullsize on a page, and then add a new page, with more of the grid displayed there, but i am unsure how to detect a new page. The problem here is that there seems to be no way of changing the number of columns of a PdfPtable, so I have to be able to calculate the effect in width when adding columns beforehand.
EDIT: I want to split the pages vertically
The problem you're facing, is the fact that PDF isn't HTML. A page in a PDF has a fixed size. You don't have a scrollbar, and the content doesn't change when you resize the viewer window.
I'm going to answer using JAVA examples from Chapter 4 of my book. If you need the C# version of these examples, you'll need this URL: http://tinyurl.com/itextsharpIIA2C04
First this: whether or not a table looks nice is something a machine can't judge. It's something only a human can do.
Seems like you want to control the width of the columns. The example ColumnWidths gives you some examples on how to do that. In your case, you should use a solution that involves using absolute values for the width of the table. You'll recognize these solutions because they all have this line in common:
table.setLockedWidth(true);
Using an absolute width is mandatory if you want to calculate the height of each row or of the complete table. A Frequently Asked Question is "I've created a table and when I ask for its total width, it returns zero." It can be answered by a counter-question: "How can iText calculate the height of a table, if you didn't define the width first?" This is a rhetorical question: it's impossible to calculate the height before the width has been defined. That's shown in the TableHeight example.
Once you've defined the widths of the table (and its columns), you can calculate the height, and you're ready to position the table at absolute positions. The Zhang example shows how it's done. In this example, a table with 4 columns is created. The first two colums are drawn using this commando:
table.writeSelectedRows(0, 2, 0, -1, 236, 806, canvas);
Where the first 0 defines the first column that needs to be drawn, and the 2 defines the first column that isn't drawn. The remaining columns are drawn on the next page, using this commando:
table.writeSelectedRows(2, -1, 0, -1, 36, 806, canvas);
Where 2 defines the first column that needs to be drawn, and -1 indicates that all remaining columns should be drawn.
In this case, 0 and -1 are used for the rows. If your table contains more rows than fit on one page, you'll have to do the math to make sure you don't have any rows that "fall off the page."
Note that the 36 and 806 represent an X and a Y coordinate. As your taking control over the layout, you're responsible to calculate those coordinates.
Related
I am using Microsoft.Office.Interop.Word in a WinForms application written in c# to create a word document programmatically. I need to create a table with the first two columns having a predefined fixed width and the third (last) column stretched to fill the remaining width of the useable page (if I do not specify any widths, by default, the table would stretch itself to fill the useable width of the page and all 3 columns would be resized equally, which is not what I need).
I can preset the size of the first two columns like this:
myTable.Columns[1].Width = 130;
myTable.Columns[2].Width = 70;
That part is straight forward and works ok. But I couldn't work out how to adjust the third column to stretch-fill the rest of the page width. Here's my last attempt which resulted in the third column extending way beyond the edge of the page:
myTable.Columns[3].Width = wordApp.UsableWidth - myTable.Columns[1].Width - myTable.Columns[2].Width;
I also tried not specifying a value for the width of the 3rd column hoping that it would take up the rest of the width of the table by default - but instead, the 3rd column was adjusted to some seemingly arbitrary value (with the content of cells on this column wrapped to fit) and the overall width of the table shrank to fit all 3 columns which was less than the useable width of the page.
I tried playing with different values for the following table attributes without success:
myTable.AllowAutoFit = true;
myTable.PreferredWidth = wordApp.UsableWidth;
If anyone could help, that would be greatly appreciated. Thank you.
I included the relevant block of code if this helps in anyway:
Microsoft.Office.Interop.Word.Table myTable = wordDoc.Tables.Add(paraTitle.Range, 2, 3, ref missing, ref missing);
myTable.Borders.Enable = 1;
myTable.AllowAutoFit = true;
myTable.PreferredWidth = wordApp.UsableWidth;
//myTable.PreferredWidthType = WdPreferredWidthType.wdPreferredWidthAuto;
myTable.Columns[1].Width = 130;
myTable.Columns[2].Width = 70;
myTable.Columns[3].Width = wordApp.UsableWidth - myTable.Columns[1].Width - myTable.Columns[2].Width;
myTable.Rows[1].Cells[1].Range.Text = "Items returned by:";
myTable.Rows[1].Cells[1].Range.Bold = 1;
myTable.Rows[1].Cells[2].Range.Text = "Name";
myTable.Rows[1].Cells[2].Range.Bold = 1;
myTable.Rows[1].Cells[3].Range.Text = "(person's name and initials...)";
myTable.Rows[1].Cells[3].Range.Bold = 0;
myTable.Rows[2].Cells[2].Range.Text = "Date / Time";
myTable.Rows[2].Cells[2].Range.Bold = 1;
myTable.Rows[2].Cells[3].Range.Text = "(timestamp...)";
myTable.Rows[2].Cells[3].Range.Bold = 0;
UsableWidth refers to the Window width of the Word Application, not the document page width.
Instead use:
doc.PageSetup.PageWidth - (doc.PageSetup.LeftMargin + doc.PageSetup.RightMargin);
I am currently using this code to show a comment for a given cell :
public static void AddCellComment(ICell cell, IDrawing patr)
{
var commentString = "Something";
var anchor = new XSSFClientAnchor
{
Col1 = cell.ColumnIndex,
Col2 = cell.ColumnIndex + 2,
Row1 = cell.RowIndex,
Row2 = cell.RowIndex + 1
};
var comment = patr.CreateCellComment(anchor);
comment.String = new XSSFRichTextString(commentString);
cell.CellComment = comment;
}
This successfully creates a 2 column by 1 row wide comment for a given cell to the right of the cell. I tried putting cell.ColumnIndex - 2 in Col1 or Col2 and it either results in a corrupted workbook where none of the comments work or in an invisible comment. Is there a way to have the comment display to the left of the cell?
All right, so here are the results of my experiments:
First off, if you hope you can precisely define the position of the comment when its owning cell is hovered, you'll be disappointed.
This is impossible. It is simply not supported by Excel.
When hovering a cell that has a comment, Excel decides by itself where to place the comment and seems to always show it on the right of the cell. You can try to move the comment around in edition mode, it will still be displayed at the same place when the cell is hovered. (I experienced it myself and had confirmation in this tutorial link.
Then, knowing that, what you have control upon is:
The size of the comment box.
Its location, but only when in edition mode.
Both characteristics are governed by XSSFClientAnchor properties. As per npoi source code, Col1, Row1 and Col2, Row2 define two cells that in turn represent the area (and location in edition mode only) of the comment: cell #1 will be included in the area whereas, cell #2 will not.
The first cell must be the top-left of the comment area (precisely, the top-left of cell #1 will be the top-left of the comment box)
The second one must be the bottom-right of the area (precisely, the top-left of cell #2 will be the bottom-right of the comment box)
I think this rule explains why some of your attempts ended with weird or empty comments (I reproduced some of those too): given the above rule, you must always have: Col2 > Col1 and Row2 > Row1. Although I didn't test it, I also suspect that (absolutely) negative columns or rows wouldn't work, hence when subtracting values from the input cell's column or row, you should make sure the result does not end up < 0...
One last note concerning XSSFClientAnchor: there are 4 other properties that can help you fine tune the size and (edition mode only) placement of the comment: Dx1, Dy1, Dx2 and Dy2: these four properties allow you to add/subtract some size to both cells x and y coordinates. They are expressed in a weird unit: EMU. You can fit 9525 EMU in a pixel.
With all this knowledge, I crafted a simple test (based on a mix of yours and an npoi tutorial). Here it is:
private static void Main()
{
var workbook = new XSSFWorkbook();
var sheet = workbook.CreateSheet("My sheet");
var row = sheet.CreateRow(10);
var cell = row.CreateCell(10);
cell.SetCellValue("Here");
var patr = sheet.CreateDrawingPatriarch();
AddCellComment(cell, patr);
using var stream = new FileStream(#"c:\temp\test.xlsx", FileMode.Create, FileAccess.Write);
workbook.Write(stream);
}
private static int PixelsToEmus(int pixels) => pixels * Units.EMU_PER_PIXEL;
private static void AddCellComment(ICell cell, IDrawing patr)
{
// Let's make a 3x2 cells comment area, then tweak it a bit
var anchor = new XSSFClientAnchor
{
// Top left cell
Col1 = 5, // 6th column
Row1 = 5, // 6th row
// Bottom right cell
Col2 = 8, // 3 cells wide
Row2 = 7, // 2 cells high
// Top left shift
Dx1 = PixelsToEmus(10), // 10 pixels to the left of 6th column's left border
Dy1 = PixelsToEmus(10), // 10 pixels to the bottom of 6th row's top border
// Bottom right shift
Dx2 = PixelsToEmus(30), // 30-10=20 pixels wider than 3 columns
Dy2 = PixelsToEmus(10), // exactly as high as 2 rows
};
var comment = patr.CreateCellComment(anchor);
comment.String = new XSSFRichTextString("Something");
cell.CellComment = comment;
}
When running this, I end up with these results (showing both the hovered placement and the edition mode placement):
To be complete, I double-checked what was written in the resulting xslx (after having unzipped it, i took a look at test\xl\drawings\vmlDrawing1.vml and inparticular the <x:Anchor> tag of the Note object in whichwe find the exact values we set in the program:
<x:Anchor>5, 10, 5, 10, 8, 30, 7, 10</x:Anchor>
The source code of npoi helped me (and hopefully you) understand how averything was working:
XSSFClientAnchor class
XSSFDrawing.CreateCellComment method
PS: For these tests, I used a .NET Core 3.1 app and NPOI v2.5.2 Nuget package
I opened a new question, because none of the answers I found here or elsewhere work for me.
I create a table using Word from C# (Office 16.0 Interop).
My goal: To make the width of a certain column smaller, i.e. fit to its content.
This is how I create a table:
var table = doc.Tables.Add(menuParagraph.Range, rowCount, 2, ref _objMiss, ref _objMiss);
table.Borders.InsideLineStyle = Word.WdLineStyle.wdLineStyleSingle;
table.Borders.OutsideLineStyle = Word.WdLineStyle.wdLineStyleSingle;
// Label
table.Cell(1, 1).Range.Shading.BackgroundPatternColor = Word.WdColor.wdColorGray20;
table.Cell(1, 1).Range.Text = "Label";
table.Cell(1, 2).Range.Shading.BackgroundPatternColor = Word.WdColor.wdColorGray20;
table.Cell(1, 2).Range.Text = $"{recordItem.TextId.EnglishTranslation} ({recordItem.TextId.GermanTranslation})";
// Type and length
table.Cell(2, 1).Range.Text = "DataType";
table.Cell(2, 2).Range.Text = $"{recordItem.DataType} (Bit length: {recordItem.BitLength})";
// Byte offset
table.Cell(3, 1).Range.Text = "Byte offset";
table.Cell(3, 2).Range.Text = $"{recordItem.ByteOffset}";
// Default value
table.Cell(4, 1).Range.Text = "Default value";
table.Cell(4, 2).Range.Text = $"{recordItem.DefaultValue}";
None of the solutions I found so far, solved my problem.
In fact, none of it has any effect on my table.
table.Columns[0].AutoFit(); gets ignored, has no effect whatsoever.
Same goes for table.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitContent);
Even if I set the width directly, it gets ignored:
table.Columns[0].Width = app.PixelsToPoints(100f);
The output is always the same. A table where each column has the same width.
How do I force a table to make one column adjust its width to its contents (and still use the whole width of a page in total).
For auto-fitting the first column, something like this should work. It uses the Column.SetWidth Method.
table.AllowAutoFit = true;
Word.Column firstCol = table.Columns[1];
firstCol.AutoFit(); // force fit sizing
Single firstColAutoWidth = firstCol.Width; // store autofit width
table.AutoFitBehavior(Word.WdAutoFitBehavior.wdAutoFitWindow); // fill page width
firstCol.SetWidth(firstColAutoWidth, Word.WdRulerStyle.wdAdjustFirstColumn); // reset width keeping right table margin
Have you tried with : table.Columns[0].PreferredWidth ?
Maybe that would work.
Here is some documentation about it :
https://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.table.preferredwidth.aspx
I'm using EPPlus and C# and trying to autosize/autofit the height of a row to accommodate the height needed to show all of the contents of a merged cell with text wrapping. However no matter what I try the text always truncates. Since I'm repeating this process with various text sizes on various worksheets, I don't want to hard code the row height (except to enforce a minimum height for the row). If possible I'd like to do this within EPPlus/C#.
With the cells A2:E2 merged and WrapText = true:
Cell with Text Truncated
Here's what it should look like with desired Cell Height
Here's my relevant and short C# code
Int32 intToCol;
intToCol = 5;
eppWorksheet.Cells[2, 1, 2, intToCol].Merge = true;
eppWorksheet.Cells[2, 1].Style.WrapText = true;
//Check if at the minimum height. If not, resize the row
if (eppWorksheet.Row(2).Height < 35.25)
{
eppWorksheet.Row(2).Height = 35.25;
}
I've looked at Autofit rows in EPPlus and it didn't seem to directly answer my question unless I'm reading it wrong.
Here is the solution in a reusable method. Pass in the text value, font used for the cell, summed width of the columns merged, and receive back the row height. Set the row height with the result.
Use of Method
eppWorksheet.Row(2).Height = MeasureTextHeight(cell.Value, cell.Style.Font, [enter the SUM of column widths A-E]);
Reuseable Method
public double MeasureTextHeight(string text, ExcelFont font, double width)
{
if (text.IsNullOrEmpty()) return 0.0;
var bitmap = _bitmap ?? (_bitmap = new Bitmap(1, 1));
var graphics = _graphics ?? (_graphics = Graphics.FromImage(bitmap));
var pixelWidth = Convert.ToInt32(width * 7); //7 pixels per excel column width
var fontSize = font.Size * 1.01f;
var drawingFont = new Font(font.Name, fontSize);
var size = graphics.MeasureString(text, drawingFont, pixelWidth, new StringFormat { FormatFlags = StringFormatFlags.MeasureTrailingSpaces });
//72 DPI and 96 points per inch. Excel height in points with max of 409 per Excel requirements.
return Math.Min(Convert.ToDouble(size.Height) * 72 / 96, 409);
}
I have used a workaround for this and I a had print area A:Q.
I copied merged cells value to column z.
set width of column z to merge cells width.
Then set auto row height true in format.
Hide the z column.
Set print area A:Q
Cons:
There are duplicate data. But we are okay since report is printing and not print z column.
Pros:
Row height works correctly not like calculation method.
Had to tweak the code a little bit by removing the multiplication factor at the return line. May be because i am using this code to get the width of the column.
ws1.Column(colIndx).Width * 7
The multiplication factor is the number of columns been merged.
I need to draw a histogram, and columns with minimum and maximum values should have other colors that are different from other columns and have different widths!
Now I have done this in two sets, but it's the wrong outcome.
Below is my code:
chart1.Series[0].Color = Color.Blue;
int y;
for (y = 0; y < lbls.Length; y++)
{
chart1.Series[0].Points.AddY(costs[y]);
chart1.ChartAreas[0].AxisX.CustomLabels.Add(new CustomLabel(y, y + 2, lbls[y], 0, LabelMarkStyle.LineSideMark));
}
chart1.Series[1].Color = Color.Red;
chart1.Series[1].Points.AddY(1500);
chart1.ChartAreas[1].AxisX.CustomLabels.Add(new CustomLabel(y+4, y + 6, "lol", 0, LabelMarkStyle.LineSideMark));
its give me:
Not too sure how to change specific column width but and to change a specific i points color do the below.
'Loop through series to find min and max and then color label
Chart1.Series[0].Points[i].Color = Color.Red
P.S.
I'm not sure if you can change just one columns width. I sometimes use Chart1.Series[0]("PixelPointWidth") = some integer to change the width of column series but looking at the msdn page it seems it can only be applied to the series and not individual datapoints.
PixelPointWidth # MSDN
You could just create a new series and add it to the chartarea with just the max and min values and then do Chart1.Series[1]("PixelPointWidth") = some integer to make them wider than the first series