[EDIT due to misunderstanding of the answer]
I'm doing a simple program in C# with PDF file creation with iText7.
In this PDF i'm adding a table whose first cell starts at a certain position in the file.
I don't know if I set the position correctly, but everytime I add another cell with tab.StartNewRow() the resulting new table is repositioned taking THAT last cell as position reference, putting the previously added cells from that point up, while I want to add the cells from that point down.
Which method should I use? That's my code:
Previously I set the position of the first table cell using tab1.SetFixedPosition(20, heigh, width);
and then, in order to add the other cells:
if (mylistbox.Items.Count > 0)
{
tab1.AddCell("FIRST CELL");
tab1.StartNewRow();
for (int i = 0; i < mylistbox.Items.Count; i++)
{
tab1.AddCell(mylistbox.Items[i].ToString());
tab1.StartNewRow();
}
doc.Add(tab1);
}
[EDIT #2] in order to explain my issue better
I have to put 5 tables, which have to grow from a certain point DOWN, positioned at equal distances, same height and width in the doc. This image explains how it should result:
In a WPF application, I have a ListBox with 5 items, numbered 1 through 5. This should be very similar to WinForms.
The CreatePercentArray takes a size which is equal to the amount of columns in a row.
An interesting article about tables: link
private void CreateListBoxTable(Document pdfDoc)
{
// Create an array where each item has an equal width, and use the entire pdf width
// The CreatePercentArray takes a size which is equal to the amount of columns in a row
// By using percentages, they will automatically adapt
// Use CreatePointArray for exacter measurements
var table = new Table(UnitValue.CreatePercentArray(2)).UseAllAvailableWidth();
if (!MyListBox.Items.IsEmpty)
{
foreach (var listBoxItem in MyListBox.Items)
{
table.AddCell(((ListBoxItem) listBoxItem).Content.ToString());
}
}
// Adds table to document
pdfDoc.Add(table);
// Closes document
pdfDoc.Close();
}
Related
I try to write a pdf file with a header, logo and table using iText7 in c#.
I never used iText7 before and therefore I don't know how to write text in a paragraph to a fixed position.
Right now I am just using tabstops as anchors for my text. But the problem here is, when the string is too long everything following in the line will be shifted by a tabstop and the "columns" in the header aren't aligned anymore.
The following picture is what I want too achieve:
This picture shows what happens if a string gets too long (in this example I used a long username):
Here is a code snippet I use to write one line of the header:
// generate 8 tabstops to split pdf in equal sections
List<TabStop> tabStops = new List<TabStop>();
for (uint i = 0; i < 8; i++)
{
float tabSize = pageSize.GetWidth() / 8;
tabStops.Add(new TabStop(tabSize, TabAlignment.LEFT));
}
Paragraph p = new Paragraph();
p.SetFontSize(10);
// add tabstops to paragraph for text alignment
p.AddTabStops(tabStops);
// add title of header
p.Add(title1).Add("\n");
// write line one of header
p.Add("Serie: ").Add(new Tab()).Add(info.serial.ToString())
.Add(new Tab()).Add(new Tab())
.Add("Input CSV: ").Add(new Tab()).Add(info.inputFileName)
.Add(new Tab()).Add(new Tab()).Add("Out-Series: ")
.Add(info.serial.ToString()).Add("\n");
// line 2...
p.Add("User: ").Add(new Tab()).Add(info.username)
.Add(new Tab()).Add(new Tab()).Add(new Tab())
.Add("qPCR-Datei: ").Add(new Tab()).Add(info.qpcr1FileName)
.Add(new Tab()).Add(new Tab()).Add(new Tab())
.Add("STR-Out: ").Add(strFileName).Add("\n");
I hope someone can help me show me a better way of text alignment or has information where to look at.
Another nice tip would be how I can keep linebreaks in the same tab stop section. for example if a file name gets too long (s. "STR-Out: " in picture) the linebreak will be executed but the part of the filename in the new line should stay at the tab stop behind "STR-OUT: "
Instead of Tab/Tabspace use Tables and Cells so that alignment will be proper.
Create table of column 8 size (Label, Value, space , Label, Value, Space, Label, Value)
Use this sample Code.
PdfPTable table = new PdfPTable(8);
PdfPCell cell;
cell = new PdfPCell();
cell.setRowspan(2); //only if spanning needed
table.addCell(cell);
for(int aw=0;aw<8;aw++){
table.addCell("hi");
}
Thanks #shihabudheenk for pointing me in the right direction with the idea of using a table.
Just had to adjust some code to iText7.
First thing is that
Table headerTable = new Table().SetBorder(Border.NO_BORDER);
has no effect in iText7, you have to set the option for each cell individually like:
Cell cell = new Cell().SetBorder(Border.NO_BORDER);
but here is the problem that
cell.Add()
in iText7 only accepts IBlockElement as parameter so i have too use it like this:
cell.Add(new Paragraph("text");
which is pretty annoying doing that for every cell over and over again. Therefore i used a removeBorder function as suggested here
So the final code I use to build the header looks like this:
// initialize table with fixed column sizes
Table headerTable = new Table(UnitValue.CreatePercentArray(
new[] { 1f, 1.2f, 1f, 1.8f, 0.7f, 2.5f })).SetFixedLayout();
// write headerline 1
headerTable.AddCell("Serie: ").AddCell(info.serial.ToString())
.AddCell("Input CSV: ")
.AddCell(info.inputFileName)
// write remaining lines...
....
// remove boarder from all cells
removeBorder(headerTable);
private static void removeBorder(Table table)
{
foreach (IElement iElement in table.GetChildren())
{
((Cell)iElement).SetBorder(Border.NO_BORDER);
}
}
I'm working wth .NET 4.7.2, Windowsform.
I have a datagridview and I manage to generate a powerpoint file pptx.
I made a first ppt slide and I'd like to add the datagridview content into the second ppt slide given that I need to have the option to change the data within the PPt slide.
Microsoft.Office.Interop.PowerPoint.Application pptApp = new Microsoft.Office.Interop.PowerPoint.Application();
pptApp.Visible = Microsoft.Office.Core.MsoTriState.msoTrue;
Microsoft.Office.Interop.PowerPoint.Slides slides;
Microsoft.Office.Interop.PowerPoint._Slide slide;
Microsoft.Office.Interop.PowerPoint._Slide slide2;
Microsoft.Office.Interop.PowerPoint.TextRange objText;
// Create File
Presentation pptPresentation = pptApp.Presentations.Add(Microsoft.Office.Core.MsoTriState.msoTrue);
CustomLayout customLayout = pptPresentation.SlideMaster.CustomLayouts[PpSlideLayout.ppLayoutText];
// new Slide
slides = pptPresentation.Slides;
slide = slides.AddSlide(1, customLayout);
slide2 = slides.AddSlide(1, customLayout);
// title
objText = slide.Shapes[1].TextFrame.TextRange;
objText.Text = "Bonds Screner Report";
objText.Font.Name = "Haboro Contrast Ext Light";
objText.Font.Size = 32;
Shape shape1 = slide.Shapes[2];
slide.Shapes.AddPicture("C:\\mylogo.png", Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue, shape1.Left, shape1.Top, shape1.Width, shape1.Height);
slide.NotesPage.Shapes[2].TextFrame.TextRange.Text = "Disclaimer";
dataGridViewBonds.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText;
dataGridViewBonds.SelectAll();
DataObject obj = dataGridViewBonds.GetClipboardContent();
Clipboard.SetDataObject(obj, true);
Shape shapegrid = slide2.Shapes[2];
I know I'm not so far by now but I miss smething. Any help would be appreciated !
I am familiar with Excel interop and have used it many times and most likely have become numb to the awkward ways in which interop works. Using PowerPoint interop can be very frustrating for numerous reasons, however, the biggest I feel is the lack of documentation and the differences between the different MS versions.
In addition, I looked for a third-party PowerPoint library and “Aspose” looked like the only option, unfortunately it is not a “free” option. I will assume there is a free third-party option and I just did not look in the right place… Or there may be a totally different way to do this possibly with XML. I am confident I am preaching to the choir.
Therefore, what I have been able to put together may work for you. For starters, looking at your current posted code, there is one part missing that you need to get the “copied” grid cells into the slide…
slide.Shapes.Paste();
This will paste the “copied” cells from the grid into an “unformatted” table into the slide. This will copy the “row header” if it is displayed in the grid in addition to the “new row” if the grids AllowUserToAddRows is set to true. If this “unformatted paste” works for you, then you are good to go.
If you prefer to have at least a minimally formatted table and ignore the row headers and last empty row… It may be easier to simply “create” a new Table in the slide with the size we want along with the correct number of rows and columns. Granted, this may be more work, however, using the paste is going require this anyway “IF” you want the table formatted.
The method (below) takes a power point _Slide and a DataGridView. The code “creates” a new Table in the slide based on the number of rows and columns in the given grid. With this approach, the table will be “formatted” using the default “Table Style” in the presentation. So, this may give you the formatting you want by simply “creating” the table as opposed to “pasting” the table.
I have tried to “apply” one of the existing “Table Styles” in power point, however, the examples I saw used something like…
table.ApplyStyle("{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}");
Which uses a GUID id to identify “which” style to use. I am not sure why MS decided on this GUID approach… this is beyond me, and it worked for “some” styles but not all.
Also, more common-sense solutions that showed something like…
table.StylePreset = TableStylePreset.MediumStyle2Accent2;
Unfortunately using my 2019 version of Office PowerPoint, this property does not exist. I have abandoned further research on this as it appears to be version dependent. Very annoying!
Given this, it may be easier if we format the cells individually as we want. We will need to add the cells text from the grid into the individual cells anyway, so we could also format the individual cells at the same time. Again, I am confident there is a better way, however, I could not find one.
Below the InsertTableIntoSlide(_Slide slide, DataGridView dgv) method takes a slide and a grid as parameters. It will add a table to the slide with data from the given grid. A brief code trace is below.
First a check is made to get the number of total rows in the grid (not including the headers) totRows. If the grids AllowUsersToAddRows is true, then the total rows variable is decremented by 1 to ignore this new row. Next the number of columns in the grid is set to the variable totCols. The top left X and Y point is defined topLeftX and topLeftY to position the table in the slide along with the tables width and height.
ADDED NOTE: Using the AllowUserToAddRows property to determine the number of rows … may NOT work as described above and will “miss” the last row… “IF” AllowUserToAddRows is true (default) AND the grid is data bound to a data source that does NOT allow new rows to be added. In that case you do NOT want to decrement the totRows variable.
Next a “Table” “Shape” is added to the slide using the previous variables to define the base table dimensions. Next are two loops. The first loop adds the header cells to the first row in the table. Then a second loop to add the data from the cells in the grid… to the table cells in the slide.
The commented-out code is left as an example such that you want to do some specific formatting for the individual cells. This was not need in my case since the “default” table style was close to the formatting I wanted.
Also, a note that “ForeColor” is the “Back ground” color of the cell/shape. Strange!
I hope this helps and again, sympathize more about having to use PowerPoint interop… I could not.
private void InsertTableIntoSlide(_Slide slide, DataGridView dgv) {
try {
int totRows;
if (dgv.AllowUserToAddRows) {
totRows = dgv.Rows.Count - 1;
}
else {
totRows = dgv.Rows.Count;
}
int totCols = dgv.Columns.Count;
int topLeftX = 10;
int topLeftY = 10;
int width = 400;
int height = 100;
// add extra row for header row
Shape shape = slide.Shapes.AddTable(totRows + 1, totCols, topLeftX, topLeftY, width, height);
Table table = shape.Table;
for (int i = 0; i < dgv.Columns.Count; i++) {
table.Cell(1, i+1).Shape.TextFrame.TextRange.Text = dgv.Columns[i].HeaderText;
//table.Cell(1, i+1).Shape.Fill.ForeColor.RGB = ColorTranslator.ToOle(Color.Blue);
//table.Cell(1, i+1).Shape.TextFrame.TextRange.Font.Bold = Microsoft.Office.Core.MsoTriState.msoTrue;
//table.Cell(1, i+1).Shape.TextFrame.TextRange.Font.Color.RGB = ColorTranslator.ToOle(Color.White);
}
int curRow = 2;
for (int i = 0; i < totRows; i++) {
for (int j = 0; j < totCols; j++) {
if (dgv.Rows[i].Cells[j].Value != null) {
table.Cell(curRow, j + 1).Shape.TextFrame.TextRange.Text = dgv.Rows[i].Cells[j].Value.ToString();
//table.Cell(curRow, j + 1).Shape.Fill.ForeColor.RGB = ColorTranslator.ToOle(Color.LightGreen);
//table.Cell(curRow, j + 1).Shape.TextFrame.TextRange.Font.Bold = Microsoft.Office.Core.MsoTriState.msoTrue;
//table.Cell(curRow, j + 1).Shape.TextFrame.TextRange.Font.Color.RGB = ColorTranslator.ToOle(Color.Black);
}
}
curRow++;
}
}
catch (Exception ex) {
MessageBox.Show("Error: " + ex.Message);
}
}
I am attempting to dynamically generate a PDF using iTextSharp with a SQL table of phone book data. My program can currently generate a table with all of the wanted data.
Each page of the PDF only has one long table running down the middle though. I am attempting to format the PDF to have two tables running parallel down the page. When the table reaches the bottom of the page I want the next PdfPRow to be the top of the next table. This is my current C# code to attempt this.
I break up my table into an array of PdfPRows. I then iterate through these adding each row to a new table called section. When the amount of rows have been added that I want I then nest the section table one of two cells on the final table. When I attempt to add this final table to the PDF it throws this error:
iTextSharp.text.DocumentException: Object reference not set to an
instance of an object. at iTextSharp.text.pdf.PdfDocument.Add(IElement
element) at iTextSharp.text.Document.Add(IElement element) at
_Default.Page_Load(Object sender, EventArgs e) in e:\Inetpub\wwwroot\app\cts\phonebook\DefaultPDF.aspx.cs:line 211
I take this to mean that some part of the final table was never initialized but I am not sure. The section after the first for loop is me attempting to fix this error. It is probably not necessary.
Is there is a better way to do this or I am just doing it wrong?
Based on the images provided, this is what I would attempt to do as a start.
First, a couple of knowns.
The data in 2 columns does not exceed 1 page. If it does, this gets more complicated.
Pdf table writes the cells across and then down. With a table of 3 columns, it will write the first 3 cells in the first row and then write the 4th cell in the 2 row
I would set the final table up with 7 colmuns, 6 with data and 1 as a spacer between the 2 data sets. I would then split the rows into 2 arrays. You will need to handle the case of an odd number. Then I would add each row cell by cell, taking the first row from each list to write the first data row of the table.
PdfPTable FinalTableN = new PdfPTable( 7 );
PdfPRow[] n = PDFtableN.Rows.ToArray();
int half = n.Length/2;
PdfPRow[] s1 = new PdfPRow[half];
PdfPRow[] s2 = new PdfPRow[half];
for (int h = 0; h < half; h++ )
s1[h] = n[h];
for ( int h = half; h < n.Length; h++ )
s2[h-half] = n[h];
for ( int h = 0; h < half; h++ ) {
FinalTableN.AddCell( s1[h].GetCells()[0] );
FinalTableN.AddCell( s1[h].GetCells()[1] );
FinalTableN.AddCell( s1[h].GetCells()[2] );
FinalTableN.AddCell( "" );
FinalTableN.AddCell( s2[h].GetCells()[0] );
FinalTableN.AddCell( s2[h].GetCells()[1] );
FinalTableN.AddCell( s2[h].GetCells()[2] );
}
You can control the width of the columns by supplying a float[7] of values to the PdfPTable constructor instead of the the number of columns
I am trying to make the application generate an Excel file from a list of string type and a list of image type. I have the user enter a line then makes them take a screenshot and so on until G is pressed and then it should generate an Excel file with the format as so:
Row 1- Title-0-
Row 2- Header-1-
Row 3- Image-0-
Row 4- Header-2-
Row 5- Image-1-
Row 6- Header-3-
Row 7- Image-2-
Row 8- Header-4-
Row 9- Image-3-
Row 10- Header-5-
Row 11- Image-4-
...and so on until its done all in the collections.
I have created the List and List and I know that they both contain Strings and Images before I hit G as I have looked inspected the collections debug mode.
This is the code I have so far and the excel file looks right except there are no Images to be seen however it is re-sizing the rows to the pictures heights. I have never worked with Images before so think I could be missing something important but not sure what.
The Collections are passed into this method from a calling method
String collection is named "withHeadersList",
Image collection is named "withImgList".
Generate Excel Method:
public static bool GenerateTestPlan(List<String> withHeadersList, List<Image> withImgList, string stringOutputPath)
{
ExcelPackage newExcelPackage = CreateExcelPackage(withHeadersList[0]);
ExcelWorksheet newExcelWorksheet = CreateWorkSheet(newExcelPackage, "Sheet1");
SetCellValue(newExcelWorksheet, 1, 1, withHeadersList[0]); //Title
newExcelWorksheet.Row(1).Style.Font.Size = 35;
newExcelWorksheet.Row(1).Style.Font.Bold = true;
int pRowIndex = 3;
int hRowIndex = 2;
for (int i = 1; i < withHeadersList.Count; i++)
{
SetCellValue(newExcelWorksheet, hRowIndex, 1, withHeadersList[i]);
newExcelWorksheet.Row(hRowIndex).Style.Font.Size = 20;
newExcelWorksheet.Row(pRowIndex).Height = withImgList[i - 1].Height; //Set row height to height of screenshot
var img = newExcelWorksheet.Drawings.AddPicture(withHeadersList[i], withImgList[i - 1]); //Add Images (THINK THIS LAST PARAMETER IS THE PROBLEM)
img.SetPosition(pRowIndex, Pixel2MTU(2), 1, Pixel2MTU(2));
img.SetSize(withImgList[i - 1].Width, withImgList[i - 1].Height);
hRowIndex += 2;
pRowIndex += 2;
}
SaveExcelPackage(newExcelPackage, stringOutputPath);
return true;
}
Excel File here
As you see it's like the images are just not being rendered.
Your issue is most certainly with this line:
img.SetPosition(pRowIndex, Pixel2MTU(2), 1, Pixel2MTU(2));
I'm not sure why you are converting pixels to anything considering SetPosition is looking for the offset in pixels. From the metadata:
// Summary:
// Set the top left corner of a drawing. Note that resizing columns / rows after
// using this function will effect the position of the drawing
//
// Parameters:
// Row:
// Start row
//
// RowOffsetPixels:
// Offset in pixels
//
// Column:
// Start Column
//
// ColumnOffsetPixels:
// Offset in pixels
public void SetPosition(int Row, int RowOffsetPixels, int Column, int ColumnOffsetPixels);
I would recommend just passing through small values, such as 2, for the RowOffestPixels and ColumnOffsetPixels parameters:
img.SetPosition(pRowIndex, 2, 1, 2);
I found a method called Pixel2MTU(int pixels) on codeproject from a quick google search. The method is as follows:
public int Pixel2MTU(int pixels)
{
int mtus = pixels * 9525;
return mtus;
}
If this is the same method you are using, your images might be at the very far bottom right of your excel document.
Given something like
Table table;
Cell cell_1 = table.Cell(2,2);
Cell cell_2 = table.Cell(4,4);
I want to select (or highlight) from cell_1 to cell_2 (like how you would if you were doing it by hand).
I originally thought that doing the following would work:
Selection.MoveRight(wdUnits.wdCell, numCells, WdMovementType.wdExtend)
But according to http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.selection.moveright%28v=office.11%29.aspx under remarks, using wdCells as the Unit will default the WdMovementType to wdMove, and I can't think of a workaround.
Here is a workaround I've found to the problem. It isn't the most efficient way, and it doesn't work if the table has merged cells in it. I've discovered that you can select the range of your start cell, and then expand the end point of the range by moving by in units of cells. By discovering the number of cells between the start and end point of the region you want selected, you can iterate those number of cell steps. Here's the general code for that below:
word.Table table;
word.Cell cellTopLeft; //some cell on table.
word.Cell cellBottomRight; //another cell on table. MUST BE BELOW AND/OR TO THE RIGHT OF cellTopLeft
int cellTopLeftPosition = (cellTopLeft.RowIndex - 1) * table.Columns.Count + cellTopLeft.ColumnIndex;
int cellBottomRightPosition = (cellBottomRight.RowIndex - 1) * table.Columns.Count + cellBottomRight.ColumnIndex;
int stepsToTake = cellBottomRightPosition - cellTopLeftPosition;
if (stepsToTake > 0 &&
cellTopLeft.RowIndex <= cellBottomRight.RowIndex && //enforces bottom right cell is actually below of top left cell
cellTopLeft.ColumnIndex <= cellBottomRight.ColumnIndex) //enforces bottom right cell is actually to the right of top left cell
{
word.Range range = cellTopLeft.Range;
range.MoveEnd(word.WdUnits.wdCell, stepsToTake);
range.Select();
}
A much simpler way to do this is to use the Document.Range method to create a range between the two corners of the rectangle. This works equally well with merged cells.
word.Document document;
word.Cell cellTopLeft;
word.Cell cellBottomRight;
document.Range(cellTopLeft.Range.Start, cellBottomRight.Range.End).Select
Note: One can use the range returned by this expression to manipulate the contents of the table without selecting it, but it doesn't work for merging the cells (in the latter case, use cell.Merge(MergeTo) ).