So I'm generating tables with cells based on some data that I get from a database.
And my question is simple, how do I know when to create a new page? I would have to calculate the height of each table and make sure that they're within the size of the page, and if the next table isn't, well then create a new page and add it to that one.
The issue is I'm not sure if this is the right approach. Is this how you would do it? And if so, how do I calculate the height of a table because myTable.GetHeight().GetValue(); throws a nullreference exception because GetHeight() returns null.
First of all table.GetHeight().GetValue() returns null because you are %99 not setting your table's height.
For Example :
var table1 = new Table(1);
var height1 = table1.GetHeight().GetValue(); // This throws exception because we are not setting the height in here.
var table2 = new Table(1).SetHeight(20);
var height2 = table2.GetHeight().GetValue(); // This line returns 20, because you have set the value of height
For your main issue,
You can take the default page size with the below code, and set your table's height
var defaultPageHeight = pdfDocument.GetDefaultPageSize().GetHeight();
var table = new Table(1).UseAllAvailableWidth().SetHeight(defaultPageHeight);
But this wouldnt be a good approach, I think a good approach would be using a TableRenderer
Little Example:
public class CustomTableRenderer : TableRenderer
{
protected Document document;
public class CustomTableRenderer(Table modelElement, Document document) : base(modelElement)
{
this.document = document;
}
public override IRenderer GetNextRenderer()
{
// Add a header everytime a new page is created with this renderer
var header = new Table(1).UseAllAvailableWidth();
header.AddCell(new Cell().Add(new Paragraph("Title")));
document.Add(header);
return new CustomTableRenderer((Table)GetModelElement())
}
public override void Draw(DrawContext drawContext)
{
// This is the part where you create your layout for every time this renderer calls.
var defaultPageHeight = drawContext.GetDocument().GetDefaultPageSize().GetHeight();
// And you can do other things
}
}
Related
I found ResizeColumnHideShowLayer class at nattable version 1.6.
(about https://bugs.eclipse.org/bugs/show_bug.cgi?id=521486)
That is work fine for normal column headers only.
But, if I collapse a column group, no adjust size to fit window. (no increasing column size)
How can I solve the problem?
Is there way to resize other columns to fit window automatically increase?
Thank you.
Currently not because the ColumnGroupExpandCollapseLayer is taking care of hiding collapsed columns.
I found solution by myself!
It works fine very well. :-)
I was run based on NatTable v1.6 version.(downloaded yesterday)
I think this is a basic feature, so I hope this feature will be included in the next NatTable version.
In narrow tables, behavior that collapsing column group means that may be someone want to view other column data more widely.
Overview (Problem screen and solved screen)
I explain using two application(before, after) screen shot.
Refer to bottom image if you want understand my issue easily at once.
Problem screen
enter image description here
Improved screen
enter image description here
Solution summary :
Add event listener to ColumnGroupExpandCollapseLayer.
(HideColumnPositionsEvent, ShowColumnPositionsEvent)
Handle above events.
Get column indices which is hidden by collapsed
Execute MultiColumnHideCommand with the indices
Layer structure of my test code
↑ ViewportLayer (top layer)
| SelectionLayer
| ColumnGroupExpandCollapseLayer
| ResizeColumnHideShowLayer
| ColumnGroupReorderLayer
| ColumnReorderLayer
| DataLayer (base layer)
Implementation code is below:
void method() {
...
columnGroupExpandCollapseLayer.addLayerListener(new ILayerListener() {
#Override
public void handleLayerEvent(ILayerEvent event) {
boolean doRedraw = false;
//It works for HideColumnPositionsEvent and ShowColumnPositionsEvent
// triggered by ColumnGroupExpandCollapseCommandHandler
if (event instanceof HideColumnPositionsEvent) {
HideColumnPositionsEvent hideEvent = (HideColumnPositionsEvent)event;
Collection<Range> columnPositionRanges = hideEvent.getColumnPositionRanges();
Collection<Integer> convertIntegerCollection = convertIntegerCollection(columnPositionRanges);
int[] positions = convertIntPrimitiveArray(convertIntegerCollection);
//Execute command to hide columns that was hidden by collapsed column group.
MultiColumnHideCommand multiColumnHideCommand = new MultiColumnHideCommand(resizeColumnHideShowLayer, positions);
resizeColumnHideShowLayer.doCommand(multiColumnHideCommand);
doRedraw = true;
}else if (event instanceof ShowColumnPositionsEvent) {//called by ColumnGroupCollapsedCollapseCommandHandler
ShowColumnPositionsEvent showEvent = (ShowColumnPositionsEvent)event;
Collection<Range> columnPositionRanges = showEvent.getColumnPositionRanges();
Collection<Integer> positions = convertIntegerCollection(columnPositionRanges);
//Execute command to show columns that was hidden by expanded column group.
MultiColumnShowCommand multiColumnShowCommand = new MultiColumnShowCommand(positions);
resizeColumnHideShowLayer.doCommand(multiColumnShowCommand);
//Set whether or not to redraw table
doRedraw = true;
}
if (doRedraw) {
natTable.redraw();
}
}
/**
* Merge position values within several Ranges to Integer collection
*/
private Collection<Integer> convertIntegerCollection(Collection<Range> rangeCollection) {
Iterator<Range> rangeIterator = rangeCollection.iterator();
Set<Integer> mergedPositionSet = new HashSet<Integer>();
while (rangeIterator.hasNext()) {
Range range = rangeIterator.next();
mergedPositionSet.addAll(range.getMembers());
}
return mergedPositionSet;
}
/**
* Convert Integer wrapper object to primitive value
*/
private int [] convertIntPrimitiveArray(Collection<Integer> integerCollection) {
Integer [] integers = (Integer [])integerCollection.toArray(new Integer[integerCollection.size()]);
int [] positionPrimitives = new int[integers.length];
for (int i = 0 ; i < integers.length ; i++) {
positionPrimitives[i] = integers[i].intValue();
}
return positionPrimitives;
}
});
}
I'm using iText 7 to create a PDF document, with a rather complex header.
This header will show a complex table on the first page and a different table on the remaining pages. This table will contain different information that is passed in and will contain the page number and total number of pages.
I know how to create a header with a table.
I know how to create Page X of Y.
I do not know how to create different headers (different tables with different height) that change on some logic when using iText 7:
If page 1 use Table A, if page > 1 use Table B.
Is there some way to solve this with iText 7? Any help would be appreciated.
Btw:
In iText 5 i solved this and have no problem doing this, but I want to use the latest version of iText (7).
you need to implement IEventHandler and add it as an event listener for adding new page
simple example :
public class PdfEventHandler implements IEventHandler {
#Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdfDoc = docEvent.getDocument();
PdfPage page = docEvent.getPage();
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdfDoc = docEvent.getDocument();
PdfPage page = docEvent.getPage();
int pageNum = pdfDoc.getPageNumber(page)
if(pageNum> 1 )
//add table1
else
//add Table 2
}
}
and then add this event handler to you document event listener like this
PdfEventHandler handler = new PdfEventHandler();
pdf.addEventHandler(PdfDocumentEvent.START_PAGE, handler);
I use RPC calls to connect to mySql and bring text data from there.
My page is defined as split Layout.
my problem is that I don't know how to update the main layout with different text.
if i use the clear() method it will remove all the layout !
"p" is the splitLayout.
RPC:
rpcService.getChapterTxt(selectedBook,bookChapters[selectedBook],
new AsyncCallback<List<BibleTxt>>(){
public void onFailure(Throwable caught)
{
Window.alert("Failed getting Chapter");
}
public void onSuccess(List<BibleTxt> result)
{
int i = 0 ;
String verseText ="";
//Label verseLabel = new Label();
PPanel chapterPar = new PPanel();
HTML page= new HTML(verseText);
for(i=0;i<result.size();i++)
{
verseText = result.get(i).getVerseText();
//verseLabel.setText(verseText);
page.setText(page.getText() + verseText);
}
chapterPar.add(page);
//p.clear();
p.add(chapterPar); // adds the main layout
}
});
Why you don't reuse the text component changing its content text instead of continuously detaching/attaching elements to the widget hierarchy. That way should perform better and cause less problems.
I am working with ASP.NET MVC and I am using iTextSharp to render an HTML page to PDF, in detail iTextSharp XMLWorkerHelper tool.
It works fine with all the pages I have to parse and render into PDF when they have a portrait orientation, but it fails when they have a landscape one.
Here is a very simple sample of the HTML I would like to render in landscape:
<table style="border:1px solid red; width:280mm;">
<tr>
<td>
In this cell there is a content that can be very large...
</td>
</tr>
</table>
Note that the table width should be of 280mm (a little less than the full A4 width in landscape mode).
And here is the core of the HTML to PDF conversion:
public override void ExecuteResult(ControllerContext context)
{
IView viewEngineResult;
ViewContext viewContext;
if (string.IsNullOrEmpty(ViewName))
ViewName = context.RouteData.GetRequiredString("action");
context.Controller.ViewData.Model = Model;
var workStream = new MemoryStream();
var sb = new StringBuilder();
Document document = new Document(new Rectangle(842f, 595f));
document.SetPageSize(PageSize.A4.Rotate());
TextWriter tr = new StringWriter(sb);
viewEngineResult = ViewEngines.Engines.FindView(context, ViewName, null).View;
viewContext = new ViewContext(context, viewEngineResult, context.Controller.ViewData,
context.Controller.TempData, tr);
viewEngineResult.Render(viewContext, tr);
Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(sb.ToString()));
PdfWriter pdfWriter = PdfWriter.GetInstance(document, workStream);
pdfWriter.CloseStream = false;
document.Open();
XMLWorkerHelper.GetInstance().ParseXHtml(pdfWriter, document, stream, (Stream)null);
document.Close();
new FileContentResult(workStream.ToArray(), "application/pdf").ExecuteResult(context);
}
The result is a landscape document, with a table with a red border, but with a wrong width. It's easy to understand that the rendered width, on the PDF, is not 280mm, but the max width the table may have on a portrait A4 document.
I tried different ways:
I created a PdfPTable, setting to 100% its width. Then I created a PdfPCell inside the table. Finally I put the ParseXHtml result in that cell. The result was a full "A4 landscape width" cell, with my 280mm red border table inside, but with the usual wrong width (the max of a portrait A4 document)
I tried to insert document.NewPage() before to execute ParseXHtml, because I found on different posts that a "NewPage" invoke is needed due to let the document have right dimensions...but no good results also from this operation.
Maybe I am trying to follow wrong ways, and there is a very simple and stupid way to solve this issue?
Thanks in advance for your suggestions.
UPDATED
I tried to solve out this problema with a workaround.
Between the document.Open() and the document.Close() methods I substituted the:
XMLWorkerHelper.GetInstance().ParseXHtml(pdfWriter, document, stream, (Stream)null);
with the following one:
var handler = new SampleHandler();
using (TextReader sr = new StringReader(sb.ToString()))
{
//Parse
XMLWorkerHelper.GetInstance().ParseXHtml(handler , sr);
}
foreach (var element in handler.elements)
{
document.Add(element);
}
I created a new class:
public class SampleHandler : IElementHandler
{
public List<IElement> elements = new List<IElement>();
public void Add(IWritable w)
{
if (w is WritableElement)
{
// Here all the logic useful to catch the PDFPTable and redefine
// its width or other tricks (such as nested tables redefining)
}
}
}
As I mentioned with the very first issue, I don't know if there is a more simple way to do it. And to be honest, eve if it works, I don't know if this workaround is good or not...
This worked for me
<style>
table,th,td{
width:100%; //your width here
}
</style>
Did you get a functional solution using the code you suggested in your UPDATE ??
I have the same problem and I have done some tests and found that the result of sb.ToString () is an Html that contains all the necessary tags for the correct table formation using the full width in landscape.
public string RenderRazorView(ControllerContext context, string viewName)
{
IView viewEngineResult = ViewEngines.Engines.FindView(context, viewName, null).View;
var sb = new StringBuilder();
using (TextWriter tr = new StringWriter(sb))
{
var viewContext = new ViewContext(context, viewEngineResult, context.Controller.ViewData,
context.Controller.TempData, tr);
viewEngineResult.Render(viewContext, tr);
}
return sb.ToString();
}
So I understand that all the unwanted changes happen in XMLWorkerHelper.GetInstance (). ParseXHtml
So your code is intended to make changes that are already made and will not change the Parser result.
The problem is on the drawing by click or resizing browser. I have TabPanel placed with RowData, two TabItems with Chart (Google Vizualization) on one and Table with the same Data on the next. I create them on the page loading.
Then I click on Load Data (button) from DB, I redraw this two:
public void reDraw(final List<Double> slices, final String[] devices)
{
pcPie.draw(createTable(slices,devices),createOptions("По автомобилям"));
tPie.draw(createTable(slices, devices),CreateTableOptions());
}
That's work only for active TabItem and replace the drawing space from behind with this size (400px;200px) in generated HTML and I find that Data isn't changed at the behind section.
Also, when I resized the browser, Charts and Tables aren't resizing. I've tryed to use some of Layout, they don't work. May be I don't understand exactly how can use them correctly.
So,
How can I resize my Charts and Tables correct in the both of the
section (active and behind)?
How can I resize my Charts and Tables
on the browser resizing events?
Our first problem came from this: when you use the TabPanel component with some TabItems, behind TabItems aren't being created exactly, and you can not redraw them, cause object isn't created. So we change our code in activated section:
public void run() {
tpLineCharts.setBorders(true);
TabItem tiGraph = new TabItem("График");
tableData = createTable();
lcLines = new LineChart(tableData,
createOptions("По компании"));
lcLines.addSelectHandler(createSelectHandler(lcLines));
tiGraph.setLayout(new FitLayout());
tiGraph.add(lcLines);
tpLineCharts.add(tiGraph);
TabItem tiTable = new TabItem("Таблица");
tLine = new Table(tableData, CreateTableOptions());
tiTable.add(tLine);
tiTable.addListener(Events.Select, new Listener<BaseEvent>()
{
#Override
public void handleEvent(BaseEvent be) {
tLine.draw(tableData);
}
});
tpLineCharts.add(tiTable);
}}, CoreChart.PACKAGE, Table.PACKAGE);
where tableData - AbstractTableData. After this modification we can redraw our components:
public void reDrawLineChart(final ArrayList <Double> sumCompanyTraffic,
final ArrayList<Integer> axisName, String title)
{
tableData =createTable(sumCompanyTraffic, axisName);
tLine.draw(tableData, CreateTableOptions());
lcLines.draw(tableData, createOptions(title));
}
Also you need to add this options:
private Options createOptions(String title)
{
Options options = Options.create();
options.setTitleX("Период");
options.setTitle(title);
if(tpLineCharts.isRendered())
options.setSize(tpLineCharts.getWidth(true),
tpLineCharts.getHeight(true));
return options;
}