I am using iTextSharp PdfPTtable for creating tables from a database. When the table is lengthy (long but just with 3 columns), I managed to get the table flowing (or continuing) to the next PDF page. But I want them to continue in the right side (or column) of the same page. And after that it has to continue to next page (left column and then the right column and so on...).
Your requirement is (almost) an exact match with one of the examples of my book.
Please take a look at page 3 and higher of column_table.pdf.
The book has the Java version of the example, but there's also a version ported to C#.
Basically, you need to add the PdfPTable to a ColumnText object and go() as long as there is content in the column:
// Column definition
float[][] x = {
new float[] { document.Left, document.Left + 380 },
new float[] { document.Right - 380, document.Right }
};
column.AddElement(yourTable);
int count = 0; // can be 0 or 1 if your page is divided in 2 parts
float height = 0;
int status = 0;
// render the column as long as it has content
while (ColumnText.HasMoreText(status)) {
// add the top-level header to each new page
if (count == 0) {
AddFooterTable(); // for you to implement to add a footer
height = AddHeaderTable(); // for you to implement to add a header
}
// set the dimensions of the current column
column.SetSimpleColumn(
x[count][0], document.Bottom,
x[count][1], document.Top - height - 10
);
// render as much content as possible
status = column.Go();
// go to a new page if you've reached the last column
if (++count > 1) {
count = 0;
document.NewPage();
}
}
document.NewPage();
Related
I have single PdfPTable with single column. One page fit 50 rows. If I add some text data to table (for an example, 300 rows), report work fine. When I add a PdfPTable into cell (for example, 20 string cells, PdfPTable(with 20 rows in it or less), and 270 string cells), all work fine too:
/--------
20 string rows
inner table (20 rows)
10 string rows
/-------
...additional rows
But, when my inner table have more rows (mainTable[20 string rows, innerTable[90 string rows], 270 string rows], report break first page, and start innerTable output on the second page.
/---------
20 string rows
whitespace for 30 rows
/---------
inner table (50 rows from 90)
/---------
inner table (40 rows from 90)
..additional data
And I need this:
/---------
20 string rows
inner table (30 rows from 90)
/---------
inner table (50 rows from 90)
/---------
inner table (10 rows from 80)
..additional data
Anybody know solution?
ps itext version - 2.1.7
Please take a look at the NestedTables2 example. In this example, we tell iText that it's OK to split cells as soon as a row doesn't fit on a page:
This is not the default: by default, iText will try to keep a row intact by forwarding it to the next page. Only if the row doesn't fit the page, the row will be split.
Changing the default involves adding a single line to your code. That line is called: table.setSplitLate(false);
This is the full method that created the PDF shown in the screen shot:
public void createPdf(String dest) throws IOException, DocumentException {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
PdfPTable table = new PdfPTable(2);
table.setSplitLate(false);
table.setWidths(new int[]{1, 15});
for (int i = 1; i <= 20; i++) {
table.addCell(String.valueOf(i));
table.addCell("It is not smart to use iText 2.1.7!");
}
PdfPTable innertable = new PdfPTable(2);
innertable.setWidths(new int[]{1, 15});
for (int i = 0; i < 90; i++) {
innertable.addCell(String.valueOf(i + 1));
innertable.addCell("Upgrade if you're a professional developer!");
}
table.addCell("21");
table.addCell(innertable);
for (int i = 22; i <= 40; i++) {
table.addCell(String.valueOf(i));
table.addCell("It is not smart to use iText 2.1.7!");
}
document.add(table);
document.close();
}
Note that you should not use iText 2.1.7. If you do, you could have a problem with your employer, customer, investor. Read more about this in the legal section of the free ebook The Best iText Questions on StackOverflow
I have a problem where I can either update by one row (and that's it) or by four at once.
The issue is with tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1) on the last two lines, comments are indicating what happens when the statement is placed there.
There will be a limit of 6, but I can't figure out why I can't update one row at a time more than once with the code I've got.
The for loops are only allowing four cells per row and no more than 4 rows (not including the initial row at start).
Can you point me in the right direction please?
C#:
public void addRows_Click1(object sender, EventArgs e)
{
rmvRows.Visible = true;
// rows
for (int rowCount = 0; rowCount < 4; rowCount++ )
{
tr1 = new TableRow();
// cells
for (int cellCount = 0; cellCount < 4; cellCount++)
{
tc1 = new TableCell();
tb1 = new TextBox();
tb1.ID = "tbID" + cellCount.ToString();
tc1.Controls.Add(tb1);
tr1.Cells.Add(tc1);
}
tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1);// will add four more rows
}
tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1)// adds one one but no more
}
This line:
tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1);// will add four more rows
is within a for loop that is running 4 times (from 0 to 3):
for (int rowCount = 0; rowCount < 4; rowCount++ )
{
tr1 = new TableRow();
/* code omitted */
tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1);// will add four more rows
}
The line itself only adds one row each time it is called, but it is getting called 4 times. Therefore, the table has 4 additional rows after the for loop's execution.
As for your second line:
tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1)// adds one one but no more
This is outside of any loop, so, like any other normal code, it only executes once. Since it only executes once, it only adds one row.
Additionally, since tr1 is not changed after the for loop, the final row added by that last line is going to be a duplicate of whatever the last row created by the loop was.
tbl.Rows.AddAt(tbl.Rows.Count - 1, tr1)
Only adds one row regardless were it is called because tr1 is just one row.
The AddAt will only allow one row to be added at a curtain position.
The AddAt used in the loop should work just fine.
Hope that helps!
I have to generate a SSRS multiple page report. I have a list "data" and have to do following.
data contains name, salary and address
data[0] = abc,1000,def
data[1] = pqr,2000,xyz
for(int i =0; i<data.Count; i++)
{
//when the value of i is 0 information must be printed on 1st page.
//when value of is 1 information must be printed on 2nd page and so on...
}
What I believe you are looking for is a for loop construct like so:
for (int i = 0; i < data.Length;)
{
int page = ++i;
// Write the data on the page in the int variable above.
}
I have a WPF window with a grid with 2 rows and some columns with text and buttons, then when I click a button the code adds some rows and populates them with random textblocks and images via for loops. For example, the method I call to add a TextBlock with text s in the (i,j) cell of the grid is
public void createTextBlock(int i, int j, string s)
{
TextBlock tb = new TextBlock();
//Properties
tb.Foreground = Brushes.Navy;
tb.FontFamily = new FontFamily("Century Gothic");
tb.FontSize = 16;
tb.FontWeight = FontWeights.UltraBold;
tb.TextWrapping = TextWrapping.Wrap;
tb.VerticalAlignment = VerticalAlignment.Center;
tb.HorizontalAlignment = HorizontalAlignment.Center;
tb.Text = s;
//Add to the Grid
MyGrid.Children.Add(tb);
Grid.SetRow(tb, i);
Grid.SetColumn(tb, j);
}
and the method to add an image is similar. The problem is that when I click again, new textblocks and images are added above the old ones and I don't know how to update the content or clear it before adding another.
It is puzzling because the code (before adding rows) checks if the rows are more than 2, and if this is the case it clears all the exceeding rows:
if (MyGrid.RowDefinitions.Count > 2)
{
MyGrid.RowDefinitions.RemoveRange(2, MyGrid.RowDefinitions.Count-2);
}
but somehow this is not sufficient to clear their content... how can I do it?
EDIT (to clarify):
To add rows I use something like this (a little different because there is a switch call but it does not modify the essence)
public void createGrid(int n)
{
//remove rows if present
if (MyGrid.RowDefinitions.Count > 2)
{
MyGrid.RowDefinitions.RemoveRange(2, MyGrid.RowDefinitions.Count-2);
}
//permutation
int[] permutation = shuffle(deck.Count);
for (int i = 2; i < n + 2; i++)
{
RowDefinition row = new RowDefinition();
MyGrid.RowDefinitions.Add(row);
row.Height = new GridLength(200, GridUnitType.Pixel);
//add image
createImage(i, 0, deck[permutation[i - 2]].ImmPath);
//add textblock in center column with text chosen
//from a jagged array
createTextBlock(i, 1, value[0][i-2]);
//add textblock in right column
createTextBlock(i, 2, deck[permutation[i - 2]].Meaning);
}
So the idea is not to add new rows every time but to update the exsisting ones (or add them if needs be, createGrid can be called with different values for n). So I came up with the idea to wipe out the rows exceeding the first 2 (which contains the title and buttons) every time I call that method and add only the needed ones. This is the reason for the first check and RemoveRange.
Assuming you know the row and column of the control you want to remove you could do this
foreach (UIElement control in MyGrid.Children)
{
if (Grid.GetRow(control) == row && Grid.GetColumn(control) == col)
{
MyGrid.Children.Remove(control);
break;
}
}
The problem is this
It is puzzling because the code (before adding rows) checks if the
rows are more than 2, and if this is the case it clears all the
exceeding rows
If you are including items that you want to show below the current items, you need to increase the number of RowDefinitions instead of maintain the same.
I need to align rows in different tables that are layed out horizontally. I'd prefer to put the html code in a single web user control so I can create as many instances of that control as I want and lay them out horizontally. The problem is, the text in the rows needs to wrap. So some rows may expand vertically and some may not (see the example below). When that happens, the rows in the other tables aren't aligned horizontally. I know I can accomplish all this by using a single table, but that would mean I'd have to duplicate the name, address and phone html code instead of dynamically creating new instances of my user control (in reality there are many more fields than this, but I'm keeping it simple). Is there any way to do this whether with div's, tables or something else?
Here's the problem: Mary Jane's address field expands 2 lines, causing her phone field to not align properly with John's and Bob's.
Name: John Doe Name: Mary Jane Name: Bob Smith
Address: 123 broadway Address: Some really long address Address: Short address
Phone: 123-456 that takes up multiple lines Phone: 111-2222
Phone: 456-789
I'm not restricted in any way how to do this (other than using asp.net), but I'd prefer to use a single web control that I instantiate X times at design time (in this example, it's 3 times). I'm using VS2008, and .Net 3.5
Render your data, then use javascript (jQuery please) client-side to find all your td.address (for example) cells, find the one with the greatest height, and set the height of all others to that. You mention other fields, so the logic might be a little more involved, but the principle stands.
Some quick code:
<script type="text/javascript">
$(function() {
var cells = $('td.address');
var height;
// some code to foreach on all relevant cells to find max size
cells.each(function(index) {
$(this).height(height));
});
});
</script>
Just so everyone knows, this is possible. I finished the solution using jquery and it works pretty well. I assigned each table a specific css class and used that to identify which tables need to be resized (don't want to use an id since each one must be unique). It works in the 4 major browsers. For IE7 make sure to add a space in the empty cells for this to work. Here's the Javascript:
function ResizeTableRows() {
// select tables by css class name using jquery
var tables = $('.myCssClassName');
// all tables should have the save number of rows, so just use the first one
var totalRows = tables[0].rows.length;
for (var rowNumber = 0; rowNumber < totalRows; rowNumber++) {
var maxRowHeight = GetMaxRowHeight(tables, rowNumber);
for (var i = 0; i < tables.length; i++) {
if (maxRowHeight > 0) {
tables[i].rows[rowNumber].height = maxRowHeight;
SetCellHeight(tables[i].rows[rowNumber].cells, maxRowHeight);
}
}
}
}
function GetMaxRowHeight(tables, rowNumber) {
var maxRowHeight = 0;
for (var i = 0; i < tables.length; i++) {
var row1 = tables[i].rows[rowNumber];
var cell1 = row1.cells[0];
var rowHeight = row1.clientHeight;
if (rowHeight <= 0) {
rowHeight = row1.height;
}
if (rowHeight <= 0) {
rowHeight = cell1.clientHeight;
}
if (rowHeight > maxRowHeight) {
maxRowHeight = rowHeight;
}
}
return maxRowHeight;
}
function SetCellHeight(cells, maxRowHeight) {
for (var i = 0; i < cells.length; i++) {
cells[i].height = maxRowHeight;
}
}
Here's the code to start the process. Add it to the main page and not the web control (if you're using .net)
<script type="text/javascript">
// runs automatically after this page has been loaded and rendered
$(document).ready(function() {
ResizeTableRows();
});
</script>