I want a table that has a combo as one of its columnheaders.
I already found out that it is impossible with Table from this question:
Controls (Combo, Radio, Text) in column header SWT
Is there a way around that? I tried TableViewer but didn't find a way to do it with it either.
Is there any way this can be achieved?
You could create your own column headers in a Composite above the table using normal controls.
You will then need to adjust the size of these controls to match the table column sizes. One way to do this is to use a table layout class extending the jface TableColumnLayout and overriding the setColumnWidths method which is called each time the column sizes change, so you can adjust your header control widths.
Note: TableColumnLayout needs to be on a Composite containing just the Table rather than directly on the Table.
So something like this for the layout:
/**
* Table column layout extended to manage a separate table header.
*/
public class TableColumnLayoutWithSeparateHeader extends TableColumnLayout
{
/** Header composite */
private final Composite _headerComposite;
/** Right margin adjust */
private final int _rightMargin;
/**
* Constructor.
*
* #param headerComposite Header composite
* #param rightMargin Right margin value
*/
public TableColumnLayoutWithSeparateHeader(final Composite headerComposite, final int rightMargin)
{
super();
_headerComposite = headerComposite;
_rightMargin = rightMargin;
}
/**
* {#inheritDoc}
* #see org.eclipse.jface.layout.TableColumnLayout#setColumnWidths(org.eclipse.swt.widgets.Scrollable, int[])
*/
#Override
protected void setColumnWidths(final Scrollable tableTree, final int [] widths)
{
super.setColumnWidths(tableTree, widths);
// Update the header composite
final Control [] children = _headerComposite.getChildren();
final int size = Math.min(widths.length, children.length);
for (int index = 0; index < size; ++index) {
final GridData data = (GridData)children[index].getLayoutData();
int width = widths[index];
if (index == (size - 1))
width -= _rightMargin;
data.widthHint = width;
}
_headerComposite.layout();
}
}
Related
In iText5, we can get the PdfPTable's height when we need "public float calculateHeights(boolean firsttime)".
But in iText7, how can we get current table height value (especially before adding the table to its parent element)?
I already tested "table.getHeight()" method, but it returns null.
And I also found that in a table render object I can get this value, but the limitation is that the render need to be triggered when the table is adding into its parent element, so the time is not my need.
Cause sometimes we need this value for calculation to decide the "y-axis" value.
In iText5, elements and information about their position/size were a bit mixed together, which allowed you to call calculateWidths on a PdfPTable element.
In iText7, this functionality is separated, which allows different kind of flexibility for rendering/layouting elements.
Thus, model elements, which a Table instance is an example of, do not know anything about their position or size. And calling table.getHeight results in null because table did not have HEIGHT property previously set to it.
To calculate table height, one would have to make use of the rendering functionality.
For a model element, you can get the subtree of renderers representing this model element and all its children, and layout it in any given area. To really know the height of a table, you would want to create an area which knowingly will be sufficient to place the whole contents of the element.
PdfDocument pdfDoc = ...
Document doc = ...
Table table = new Table(2)
.addCell(new Cell().add(new Paragraph("cell 1, 1")))
.addCell(new Cell().add(new Paragraph("cell 1, 2")));
LayoutResult result = table.createRendererSubTree().setParent(doc.getRenderer()).layout(
new LayoutContext(new LayoutArea(1, new Rectangle(0, 0, 400, 1e4f))));
System.out.println(result.getOccupiedArea().getBBox().getHeight());
The code above prints 22.982422O for me, but the results may vary depending on the configuration and properties of elements.
I would like to point out two important parts of the code:
We pass 1e4f as the height of the LayoutArea, considering that this will be sufficient to place the whole table. Note that if the table cannot be placed into that height, the result will never exceed this given height and thus it will not be correct for your usecase (know the total height of the table). So make sure to pass the height which will be sufficient for placement of the whole table.
.setParent(doc.getRenderer()) part is important here and is used for retrieving inheritant properties. Note that we did not set a lot of properties to table element, even font, but this information is essential to know the area this element would occupy. So this information will be inherited from the parent chain during layout. You can test this by changing the document's font: document.setFont(newFont);, or font size: document.setFontSize(24); and watching the resultant height change
Well, due to the way the renderer framework is written in iText7, there isn't a way (yet) to calculate the height of a layout object before it is added to a parent document, since the actual calculation of the height for the layout objects happens when they are added to the a Document object.
You can however relayout a Document, allowing you to change the content of previously added elements. Using this, you can simulate the rendering of tables and get a hold of their heights when you add new elements. The table.getHeight() still won't work, since it retrieves the height property, and that property is currently not set anywhere in the table rendering process.
In the example below, I've written a convenience method that iterates over the renderer-tree and prints out the area each table occupies in the document, to show you how you can get the calculated heights.
The example itself adds some tables to the document, displays the occupied areas, adds some cells to each table, displays the occupied areas (they're the same since adding to an element that has been added before doesn't trigger a layout), and finally, manually triggers a relayout and displays the final occupied areas.
public class DelayedLayout {
public static String DEST = "target/output/StackOverflow/DelayedLayout/delayed.pdf";
public static void main(String[] args)throws IOException, FileNotFoundException{
File file = new File(DEST);
file.getParentFile().mkdirs();
new DelayedLayout().createPdf(DEST);
}
public void createPdf(String dest) throws IOException, FileNotFoundException{
PdfWriter writer = new PdfWriter(dest);
PdfDocument pdfDoc = new PdfDocument(writer);
boolean immediateFlush = false;
boolean relayout = true;
//Set immediate layout to false, so the document doesn't immediatly write render-results to its outputstream
Document doc = new Document(pdfDoc, PageSize.A4,immediateFlush);
Table tOne = createSimpleTable();
for(int i= 0; i< 5; i++) {
//Add a table and some whitespace
doc.add(tOne);
doc.add(new Paragraph(""));
}
System.out.println("\nInitial layout results");
printOccupiedAreasOfTableRenderers(doc.getRenderer());
System.out.println("\nAdding extra cells to the table");
addToTable(tOne);
printOccupiedAreasOfTableRenderers(doc.getRenderer());
System.out.println("\nForcing the document to redo the layout");
if(relayout)doc.relayout();
printOccupiedAreasOfTableRenderers(doc.getRenderer());
doc.close();
}
/**
* Create a very simple table
* #return simple table
*/
private Table createSimpleTable(){
int nrOfCols = 3;
int nrOfRows = 5;
Table res = new Table(nrOfCols);
for(int i= 0; i<nrOfRows;i++){
for(int j = 0; j<nrOfCols;j++){
Cell c = new Cell();
c.add(new Paragraph("["+i+", "+j+"]"));
res.addCell(c);
}
}
return res;
}
/**
* Add some extra cells to an exisiting table
* #param tab table to add cells to
*/
private void addToTable(Table tab){
int nrOfRows = 5;
int nrOfCols = tab.getNumberOfColumns();
for(int i=0; i<nrOfRows*nrOfCols;i++){
Cell c = new Cell();
c.add(new Paragraph("Extra cell"+ i));
tab.addCell(c);
}
}
/**
* Recursively iterate over the renderer tree, writing the occupied area to the console
* #param currentNode current renderer-node to check
*/
private void printOccupiedAreasOfTableRenderers(IRenderer currentNode){
if(currentNode.getClass().equals(TableRenderer.class)){
System.out.println("Table renderer with occupied area: " + currentNode.getOccupiedArea());
}
for (IRenderer child:currentNode.getChildRenderers()) {
printOccupiedAreasOfTableRenderers(child);
}
}
I have a TableViewer with a ComboBoxCellEditor. When I click into the cell, first I get a text box (similar to the TextCellEditor) with an arrow next to it. If I click the arrow, I get the drop down list with the values I put into it.
Is there any way for me to skip the text box step? I want it to open up the combo box right away when I click/traverse into the cell. Hand in hand with this is that I also don't want to allow any options other than the ones in the list.
I thought maybe this behavior is controlled by a style, but the only styles I found were
/**
* The list is dropped down when the activation is done through the mouse
*/
public static final int DROP_DOWN_ON_MOUSE_ACTIVATION = 1;
/**
* The list is dropped down when the activation is done through the keyboard
*/
public static final int DROP_DOWN_ON_KEY_ACTIVATION = 1 << 1;
/**
* The list is dropped down when the activation is done without
* ui-interaction
*/
public static final int DROP_DOWN_ON_PROGRAMMATIC_ACTIVATION = 1 << 2;
/**
* The list is dropped down when the activation is done by traversing from
* cell to cell
*/
public static final int DROP_DOWN_ON_TRAVERSE_ACTIVATION = 1 << 3;
and they didn't seem to be relevant. In fact, I set all of them, and I wasn't able to get the functionality I'm looking for.
How can I have the dropbox show without having the accompanying text box?
It's possible to change the styling of the underlying Combo widget by using the constructor:
ComboBoxCellEditor(Composite parent, String[] items, int style)
and passing SWT.READ_ONLY as style
I am trying to get the header on a table to have word wrap. I have managed to do this but the first data row is expanding. The code for the table is:
public class GenerateTable extends JTable {
private JCheckBox boxSelect = new JCheckBox();
private JTableHeader hdGen;
public class LineWrapCellRenderer extends JTextArea implements TableCellRenderer {
private static final long serialVersionUID = 1L;
int rowHeight = 0; // current max row height for this scan
#Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column)
{
/*
* row < 0 means header
*/
if(row >= 0) {
setWrapStyleWord(false);
return this;
}
setText((String) value);
setWrapStyleWord(true);
setLineWrap(true);
// current table column width in pixels
int colWidth = table.getColumnModel().getColumn(column).getWidth();
// set the text area width (height doesn't matter here)
setSize(new Dimension(colWidth, 1));
// get the text area preferred height and add the row margin
int height = getPreferredSize().height + table.getRowMargin();
// ensure the row height fits the cell with most lines, row = -1 for header
if (column == 2 || height > rowHeight) {
table.setRowHeight(row, height);
rowHeight = height;
}
return this;
}
}
LineWrapCellRenderer lwHeader = new LineWrapCellRenderer();
public GenerateTable(GenerateTableModel model) {
super(model);
this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
/*
* Select
*/
TableColumn colSelect = this.getColumnModel().getColumn(0);
colSelect.setCellEditor(new DefaultCellEditor(boxSelect));
colSelect.setPreferredWidth(60);
/*
* category
*/
this.getColumnModel().getColumn(1).setResizable(false);
this.getColumnModel().getColumn(1).setPreferredWidth(200);
/*
* Amount values
*/
for (int i=2;i<model.getColumnCount();i++) {
colSelect = this.getColumnModel().getColumn(i);
colSelect.setPreferredWidth(100);
colSelect.setResizable(false);
colSelect.setHeaderRenderer(lwHeader);
}
}
}
The output is:
I have followed the code through in debug and LineWrapCellRenderer is not being called for the data lines. If I take the code out I get a normal table but no wrap on the header. Is this a recognised problem or am I missing something?
Any help appreciated
You can achieve multi-line headers much easier.
As with many Swing components you can use HTML code. In HTML specify <br> elements to indicate where line breaks / new lines should occur.
For example if you use the following header values (column names):
String[] columnNames = {
"<html>First<br>column</html>",
"<html>Second<br>column</html>",
"<html>Third<br>column</html>"
};
Then the headers will be properly rendered in 2 lines. You don't even need to create/use a custom header renderer, the default header renderer properly handles HTML code.
Note: The header height will be determined by the height of the first column. So you have to use a 2-line HTML value for the first column too. If you only have 1 word for the first column, you may additionally add an empty second line like this: "<html>Select<br> </html>"
I need a method that can insert at specified index new tab and tabs after that index should go to the right.
I don't want to remove all tabs after new one and insert them back. I only want to add new tab between existing.
The code:
public class MainTabbedPane extends JTabbedPane {
private SyntaxHighlighterManager syntaxHighlighterManager;
private Map<Integer, Rectangle> tabsBounds = new HashMap<>();
public MainTabbedPane() {
this.syntaxHighlighterManager = SyntaxHighlighterManager.getInstance();
Map<String, Action> actions = MainFrame.getInstance().getActions();
Action closeTabAction = actions.get(CloseTabAction.CLOSE_TAB);
Action closeAllTabsAction = actions.get(CloseAllTabsAction.CLOSE_ALL_TABS);
Action closeAllTabsButThis = actions.get(CloseAllTabsButThisAction.CLOSE_ALL_BUT_THIS);
super.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
(KeyStroke) closeTabAction.getValue(Action.ACCELERATOR_KEY), CloseTabAction.CLOSE_TAB);
super.getActionMap().put(CloseTabAction.CLOSE_TAB, closeTabAction);
super.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
(KeyStroke) closeAllTabsAction.getValue(Action.ACCELERATOR_KEY), CloseAllTabsAction.CLOSE_ALL_TABS);
super.getActionMap().put(CloseAllTabsAction.CLOSE_ALL_TABS, closeAllTabsAction);
super.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
(KeyStroke) closeAllTabsButThis.getValue(Action.ACCELERATOR_KEY),
CloseAllTabsButThisAction.CLOSE_ALL_BUT_THIS);
super.getActionMap().put(CloseAllTabsButThisAction.CLOSE_ALL_BUT_THIS, closeAllTabsButThis);
// super.setUI(new MainTabUI());
// TabReorderHandler.enableReordering(this);
}
/**
* Central method for adding new tab.
*
* #param title
* #param fileViewer
* #param tip
*/
private void addNewTab(String title, Container fileViewer, String tip) {
if (fileViewer != null) {
super.addTab(title, null, fileViewer, tip);
// icon is set in tabComponent MainTabComponent
super.setTabComponentAt(super.getTabCount() - 1, new MainTabComponent(title, this));
tabsBounds.put(super.getTabCount() - 1, super.getUI().getTabBounds(this, super.getTabCount() - 1));
}
}
Method addNewTab adds new tab.
Thank you!
Method in JTabbedPane:
public void insertTab(String title,
Icon icon,
Component component,
String tip,
int index)
Inserts a new tab for the given component, at the given index, represented by the given title and/or icon, either of which may be null.
Parameters:
title - the title to be displayed on the tab
icon - the icon to be displayed on the tab
component - the component to be displayed when this tab is clicked.
tip - the tooltip to be displayed for this tab
index - the position to insert this new tab (> 0 and <= getTabCount())
Throws:
IndexOutOfBoundsException - if the index is out of range (< 0 or > getTabCount())
Source:
http://docs.oracle.com/javase/7/docs/api/javax/swing/JTabbedPane.html#insertTab%28java.lang.String,%20javax.swing.Icon,%20java.awt.Component,%20java.lang.String,%20int%29
I am looking for a method to split wide tables so that they span across multiple pages. The goal is to make tables with large number of columns readable. I found one discussion thread where this topic is covered; however, the example referenced in there is not available. Manning's "iText in Action" (2006) doesn't cover this topic.
Can this be done in version 1.4.8, if not, to which version of iText should I upgrade to?
Please take a look at the examples of chapter 4 of my book, more specifically at the Zhang example. In this example, I have a table with four columns: (1) year, (2) movie title in English, (3) movie title in Chinese, and (4) run length. If you look at the resulting PDF, you will see that this table is split vertically.
Achieving this requires more work then simply adding a table and allowing iText to decide how to split it in between rows. When you want to split in between columns, you need to organize the layout in your code. This is done using the writeSelectedRows()) method.
In my simple book example, I use these lines:
// draw the first two columns on one page
table.writeSelectedRows(0, 2, 0, -1, 236, 806, canvas);
document.newPage();
// draw the remaining two columns on the next page
table.writeSelectedRows(2, -1, 0, -1, 36, 806, canvas);
First I draw the columns from index 0 to index 2. The column with index 0 is the first column, the column with index 2 is the first column that isn't included, namely the third column. I draw the rows from index 0 (first row) until -1. Minus one means: draw all the remaining rows.
You also see minus one on the next page, where I draw the column with index 2 (the third column) until the column with index -1 (meaning: the rest of the columns).
The values (236, 806) and (36, 806) are coordinates: that's where you want the table to start. You can't define "end coordinates". If the table doesn't fit on the page, iText will just continue drawing the table, even if that means that some content exceeds the visible area of the page. This means that you'll have to be very careful when using this method: you'll need to calculate widths and heights of rows and columns before adding the table, otherwise you may end up with parts of the table that aren't visible.
This is the source code of a class that splits your table over multiple pages when your columns don't fit in a single page
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
/**
* Class that writes a <code>PdfPTable</code>, and spans it across multiple
* pages if the columns won't fit on one page
*/
public class PdfPTableWriter {
// Instance variables
private PdfPTable table;
private PdfWriter writer;
private Document document;
// List of how many columns per horizontal page
private List numberOfColumnsPerPage;
// List of how many rows per vertical page
private List numberOfRowsPerPage;
// Offsets if given
private float widthOffset = 20;
private float heightOffset = 70;
/**
* Class Constructor
*/
public PdfPTableWriter(Document document, PdfWriter writer, PdfPTable table) {
this.document = document;
this.writer = writer;
this.table = table;
calculateColumns();
calculateRows();
}
/**
* Writes the table to the document
*/
public void writeTable() throws DocumentException {
// Begin at row 1 (row after the header)
int rowBegin = 1;
int rowEnd = 0;
// Note the size of numberOfRowsPerPage is how many vertical
// pages there are.
Iterator rowsIter = numberOfRowsPerPage.iterator();
while (rowsIter.hasNext()) {
rowEnd = ((Integer) rowsIter.next()).intValue();
writeSelectedRows(rowBegin, rowEnd);
rowBegin = rowEnd;
}
}
/**
* Prints the Report's columns (splitting horizontally if necessary) and
* subsequent rows
*
* #param rowBegin
* #param rowEnd
* #throws DocumentException
*/
private void writeSelectedRows(int rowBegin, int rowEnd) throws DocumentException {
int colBegin = 0;
int colEnd = 0;
float pageHeight = document.getPageSize().getHeight() - heightOffset;
PdfContentByte contentByte = writer.getDirectContent();
Iterator columnsIter = numberOfColumnsPerPage.iterator();
while (columnsIter.hasNext()) {
colEnd = colBegin + ((Integer) columnsIter.next()).intValue();
// Writer table header
writeSelectedRows(colBegin, colEnd, 0, 1, widthOffset, pageHeight);
// Writes selected rows to the document
writeSelectedRows(colBegin, colEnd, rowBegin, rowEnd, widthOffset, pageHeight - table.getRowHeight(0) /*table.getHeaderHeight()*/);
// Add a new page
document.newPage();
colBegin = colEnd;
}
}
public int getTotalPages() {
return numberOfColumnsPerPage.size() * numberOfRowsPerPage.size();
}
public void setHeightOffset(float heightOffset) {
this.heightOffset = heightOffset;
}
public void setWidthOffset(float widthOffset) {
this.widthOffset = widthOffset;
}
private void writeSelectedRows(int colBegin, int colEnd, int rowBegin, int rowEnd, float x, float y) {
PdfContentByte cb = writer.getDirectContent();
table.writeSelectedRows(colBegin, colEnd, rowBegin, rowEnd, x, y, cb);
}
private void calculateColumns() {
numberOfColumnsPerPage = new ArrayList();
float pageWidth = document.getPageSize().getWidth() - widthOffset;
float[] widths = table.getAbsoluteWidths();
if (table.getTotalWidth() > pageWidth) {
// tmp variable for amount of total width thus far
float tmp = 0f;
// How many columns for this page
int columnCount = 0;
// Current page we're on
int currentPage = 0;
// Iterate through the column widths
for (int i = 0; i < widths.length; i++) {
// Add to the temporary total
tmp += widths[i];
// If this column will not fit on the page
if (tmp > pageWidth) {
// Add the current column count to this page
numberOfColumnsPerPage.add(new Integer(columnCount));
// Since this column won't fit, the tmp variable should
// start off the next iteration
// as this column's width
tmp = widths[i];
// Set column count to 1, since we have moved this column to
// the next page
columnCount = 1;
}
// If this is will fit on the page
else {
// Increase the column count
columnCount++;
}
}
// Save the remaining columns
numberOfColumnsPerPage.add(new Integer(columnCount));
}
// All the columns will fit on one horizontal page
// Note: -1 means all the columns
else {
numberOfColumnsPerPage.add(new Integer(-1));
}
}
private void calculateRows() {
numberOfRowsPerPage = new ArrayList();
float pageHeight = document.getPageSize().getHeight() - heightOffset - table.getRowHeight(0) /*table.getHeaderHeight()*/;
// If the table won't fit on the first page
if (table.getTotalHeight() > pageHeight /*table.getHeaderHeight()*/) {
// Temp variables
float tmp = 0f;
// Determine the start and end rows for each page
for (int i = 1; i < table.size(); i++) {
// Add this row's height to the tmp total
tmp += table.getRowHeight(i);
if (tmp > pageHeight - (heightOffset/2)) {
// This row won't fit so end at previous row
numberOfRowsPerPage.add(new Integer(i - 1));
// Since this row won't fit, the tmp variable should start
// off the next iteration
// as this row's height
tmp = table.getRowHeight(i);
}
}
// Last page always ends on totalRows
numberOfRowsPerPage.add(new Integer(table.size()));
}
// All the rows will fit on one vertical page
// Note: -1 means all the rows
else {
numberOfRowsPerPage.add(new Integer(-1));
}
}
}
Well, I'll try to give you some kind of response. First at all, as #mkl says, 1.4.8 is ancient version. Look at http://sourceforge.net/projects/itext/files/iText/ to get something better, the last version is 5.4.5. And as I know, there is no way to split wide table in two pages. If the document is "a bit" wider than the page - rotate the page, but if you have many columns that don't fit - you have to do it your way and this could be painful. There is no automatic function that can check if your columns have too much text and don't fit the page.
Hope this helps you.