I'm using POI library to deal with the Excel file, I want to change the ForGroundColor of the particular cells. As I'm not satisfied with the list of IndexedColors I want to create my own by substituting one already existing (in my case HSSFColor.BLUE), the problem is - it saves only the color from the last iteration (all cells have the same color).
The code (convData - two dim double array, normalized to 255):
HSSFPalette hssfPalette = excelFile.getCustomPalette();
CellStyle cellStyle = excelFile.createCellStyle();
hssfPalette = excelFile.getCustomPalette();
cellStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
for (int i=0; i<convData.length; i++) {
Row row = excelSheet.createRow(i);
for (int j=0; j<convData[i].length; j++) {
Cell cell = row.createCell(j);
hssfPalette.setColorAtIndex(HSSFColor.BLUE.index, convData[i][j].byteValue(), convData[i][j].byteValue(), convData[i][j].byteValue());
cellStyle.setFillForegroundColor(hssfPalette.getColor(HSSFColor.BLUE.index).getIndex());
cell.setCellStyle(cellStyle);
}
}
Your problem is that you're creating one single cell style, assigning it to a bunch of cells, then changing it to be blue part way through. As the cell style is global, that blue then applies to everything
Instead, you either need move the "redefine what blue is" outside of the loop, or create a new cell style + apply colour to it for each differently coloured cell. However, there's a limit to the number of colours and cell styles you can have, so make sure you re-use them if you have multiple cells wanting the same colour
Related
I using something like this : I have a global variable for CellStyle
CellStyle cellStyle = workbook.createCellStyle() and trying to fill color for all cells from 4th Column.
......
var cell = row.createCell(colIndex)
if (colIndex == 4) {
cellStyle.setFillForegroundColor(HSSFColor.LIME.index);
cellStyle.setFillPattern(HSSFCellStyle.ALIGN_CENTER);
} else {
cellStyle.setFillForegroundColor(HSSFColor.WHITE.index);
cellStyle.setFillPattern(HSSFCellStyle.ALIGN_CENTER);
}
if (cell != null) {
cell.setCellStyle(cellStyle);
cell.setCellValue(value);
}
by the way I am using HSSFWorkbook and HSSFSheet for reporting.
This way it just fills entire sheet and resulting all cells got filled with LIME color!
The comment is correct CellStyle is shared and there is a limit for to create new cell styles too.
So I have created three methods for three different styles and one of them: excuse me for the xtend code down.
getCellStyle1(HSSFWorkbook sampleWorkBook) {
val font = sampleWorkBook.createFont()
font.setFontName(HSSFFont.FONT_ARIAL)
font.setColor(IndexedColors.BLACK.index)
font.setBoldweight(HSSFFont.COLOR_NORMAL)
if (commonCellStyle == null)
commonCellStyle = sampleWorkBook.createCellStyle()
commonCellStyle.setCellBorderStyle
commonCellStyle.setFillForegroundColor(HSSFColor.WHITE.index)
commonCellStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND)
commonCellStyle.setFont(font)
commonCellStyle.setCellBorderStyle
return commonCellStyle
}
Similarly I have more getCellStyle2 and getCellStyle3 just create one more if found null so that the styles can be shared across various column.
I want to write some records into excel but I got to know that the maximum cell styles in XSSFWorkbook is 64000.But records exceeding more than 64000 and consider I want to apply new cellstyle to each cell or I will clone with the already existing cell style.
Even to clone I need to take default cell style workbook.createCellStyle(); but this exceeds for 64001 record which leads to java.lang.IllegalStateException: The maximum number of cell styles was exceeded..
So is there anyway in POI to know already particular cell style is
present and make use of that or when is necessary to clone/create
default cellstyle and clone.
Reason for cloning is : Sometimes column/row cellstyle and existing refered excel cellstyle may be different, so am taking default cell style and cloning col & row & cell cellstyles to it.
Even I tried to add a default style to a mapmap.put("defStyle",workbook.createCellStyle();) but this wont clone properly, because it will change at first attempt of cloning since It wont get the Object it will copy the reference even object cloning also not possible here because cellstyle doesn't implement cloneable interface.
In general it should not be necessary to create as much cell styles that they exceed the max count of possible cell styles. To format cells depending of their content, there is conditional formatting usable. Also to format rows (odd/even rows different for example) conditional formatting can be used. Also for columns.
So in general not each cell or a big amount of cells should be formatted using cell styles. Instead there should a less count of cell styles be created and then be used as default cell style or in single cases if conditional formatting really will not be possible.
In my example I have a default cell style for all cells and a single row cell style for first row (even this could be achieved using conditional formatting).
To keep the default cell style working after it is applied to all columns, it must be applied to all with apache poi new created cells. For this I have provided a method getPreferredCellStyle(Cell cell). Excel itself will apply the column (or row) cell style to new filled cells automatically.
If it is then nevertheless necessary to format single cells different, then for this CellUtil should be used. This provides "various methods that deal with style's allow you to create your CellStyles as you need them. When you apply a style change to a cell, the code will attempt to see if a style already exists that meets your needs. If not, then it will create a new style. This is to prevent creating too many styles. there is an upper limit in Excel on the number of styles that can be supported." See comments in my Example.
import java.io.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.CellUtil;
import java.util.Map;
import java.util.HashMap;
public class CarefulCreateCellStyles {
public CellStyle getPreferredCellStyle(Cell cell) {
// a method to get the preferred cell style for a cell
// this is either the already applied cell style
// or if that not present, then the row style (default cell style for this row)
// or if that not present, then the column style (default cell style for this column)
CellStyle cellStyle = cell.getCellStyle();
if (cellStyle.getIndex() == 0) cellStyle = cell.getRow().getRowStyle();
if (cellStyle == null) cellStyle = cell.getSheet().getColumnStyle(cell.getColumnIndex());
if (cellStyle == null) cellStyle = cell.getCellStyle();
return cellStyle;
}
public CarefulCreateCellStyles() throws Exception {
Workbook workbook = new XSSFWorkbook();
// at first we are creating needed fonts
Font defaultFont = workbook.createFont();
defaultFont.setFontName("Arial");
defaultFont.setFontHeightInPoints((short)14);
Font specialfont = workbook.createFont();
specialfont.setFontName("Courier New");
specialfont.setFontHeightInPoints((short)18);
specialfont.setBold(true);
// now we are creating a default cell style which will then be applied to all cells
CellStyle defaultCellStyle = workbook.createCellStyle();
defaultCellStyle.setFont(defaultFont);
// maybe sone rows need their own default cell style
CellStyle aRowCellStyle = workbook.createCellStyle();
aRowCellStyle.cloneStyleFrom(defaultCellStyle);
aRowCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
aRowCellStyle.setFillForegroundColor((short)3);
Sheet sheet = workbook.createSheet("Sheet1");
// apply default cell style as column style to all columns
org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol cTCol =
((XSSFSheet)sheet).getCTWorksheet().getColsArray(0).addNewCol();
cTCol.setMin(1);
cTCol.setMax(workbook.getSpreadsheetVersion().getLastColumnIndex());
cTCol.setWidth(20 + 0.7109375);
cTCol.setStyle(defaultCellStyle.getIndex());
// creating cells
Row row = sheet.createRow(0);
row.setRowStyle(aRowCellStyle);
Cell cell = null;
for (int c = 0; c < 3; c++) {
cell = CellUtil.createCell(row, c, "Header " + (c+1));
// we get the preferred cell style for each cell we are creating
cell.setCellStyle(getPreferredCellStyle(cell));
}
System.out.println(workbook.getNumCellStyles()); // 3 = 0(default) and 2 just created
row = sheet.createRow(1);
cell = CellUtil.createCell(row, 0, "centered");
cell.setCellStyle(getPreferredCellStyle(cell));
CellUtil.setAlignment(cell, HorizontalAlignment.CENTER);
System.out.println(workbook.getNumCellStyles()); // 4 = 0 and 3 just created
cell = CellUtil.createCell(row, 1, "bordered");
cell.setCellStyle(getPreferredCellStyle(cell));
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(CellUtil.BORDER_LEFT, BorderStyle.THICK);
properties.put(CellUtil.BORDER_RIGHT, BorderStyle.THICK);
properties.put(CellUtil.BORDER_TOP, BorderStyle.THICK);
properties.put(CellUtil.BORDER_BOTTOM, BorderStyle.THICK);
CellUtil.setCellStyleProperties(cell, properties);
System.out.println(workbook.getNumCellStyles()); // 5 = 0 and 4 just created
cell = CellUtil.createCell(row, 2, "other font");
cell.setCellStyle(getPreferredCellStyle(cell));
CellUtil.setFont(cell, specialfont);
System.out.println(workbook.getNumCellStyles()); // 6 = 0 and 5 just created
// until now we have always created new cell styles. but from now on CellUtil will use
// already present cell styles if they matching the needed properties.
row = sheet.createRow(2);
cell = CellUtil.createCell(row, 0, "bordered");
cell.setCellStyle(getPreferredCellStyle(cell));
properties = new HashMap<String, Object>();
properties.put(CellUtil.BORDER_LEFT, BorderStyle.THICK);
properties.put(CellUtil.BORDER_RIGHT, BorderStyle.THICK);
properties.put(CellUtil.BORDER_TOP, BorderStyle.THICK);
properties.put(CellUtil.BORDER_BOTTOM, BorderStyle.THICK);
CellUtil.setCellStyleProperties(cell, properties);
System.out.println(workbook.getNumCellStyles()); // 6 = nothing new created
cell = CellUtil.createCell(row, 1, "other font");
cell.setCellStyle(getPreferredCellStyle(cell));
CellUtil.setFont(cell, specialfont);
System.out.println(workbook.getNumCellStyles()); // 6 = nothing new created
cell = CellUtil.createCell(row, 2, "centered");
cell.setCellStyle(getPreferredCellStyle(cell));
CellUtil.setAlignment(cell, HorizontalAlignment.CENTER);
System.out.println(workbook.getNumCellStyles()); // 6 = nothing new created
FileOutputStream out = new FileOutputStream("CarefulCreateCellStyles.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
public static void main(String[] args) throws Exception {
CarefulCreateCellStyles carefulCreateCellStyles = new CarefulCreateCellStyles();
}
}
Not easily. I define the styles I need ahead of time, and then apply them explicitly to each cell rather than just creating a new style for each cell. Note, I do not include borders in those pre-defined styles, just fonts, colors, and sometimes fills. For borders, I draw them on to the spreadsheet later to simplify the code.
In my java class I am declaring cell like:
HSSFCell cell = null;
And I am using this cell in many places to create a cell and set values, styles. Like:
cell = row.createCell(1);
cell.setCellValue("1234.00");
setCellStyle(currency, cell, workbook);
cell = row.createCell(2);
setCellValue("2445.00");
Now, surprisingly, the first cell's data format is being applied to 2nd cell.
Anyone has any idea?
I expect the 2nd cell's style to be none. And the 1st cell's style should be with the data format applied by setCellStyle() method.
But, actually I am getting both the cell values with data format applied by setCellStyle() method.
setCellStyle() method:
public void setCellStyle(String currency, HSSFCell cell, HSSFWorkbook workbook){
HSSFCellStyle cellStyle = cell.getCellStyle();//I am using cell.getStyle() because the default cell style is not null for a HSSFCell
HSSFDataFormat dataFormat = workbook.createDataFormat();
if("GBP".equalsIgnoreCase(currency)){
cellStyle.setDataFormat(dataFormat.getFormat("[$£-809]#,##0_);[Red]([$£-809]#,##0)"));
}else (){
cellStyle.setDataFormat(dataFormat.getFormat("$#,##0_);[Red]($#,##0)"));
}
}
Now that you've updated your post to show your setCellStyle method, I can see the problem. Every Cell starts out with a default CellStyle, and they all share the same default CellStyle. When you call setCellStyle for the first Cell, you are changing that default CellStyle that is shared for all cells. This means that any other cells you create, where you don't set the CellStyle have your changes. Additionally, any other time you call your own setCellStyle, it will change the default cell style again.
Instead, create a new CellStyle that only that Cell will have, using Workbook's createCellStyle method.
HSSFCellStyle cellStyle = workbook.createCellStyle();
If you intend to set multiple cells to the same cell style, then you should reuse the CellStyle objects.
It is important to create a new cell style from the workbook otherwise you can end up modifying the built in style and effecting not only this cell but other cells.
I'm working on a import module from a excel file. I have to read and check this file, if something is wrong I have to color the corrisponding cell of a color. Then I implemented the following method
public void fillCell(Workbook wb, Row row, int errorColumn){
Cell cell = row.getCell(j);
CellStyle cs = wb.createCellStyle();
cs.setFillForegroundColor((short) 10);
cs.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
cell.setCellStyle(cs);
}
but i noted that this method changes the data format of the cell. For example if I'm coloring a cell with data value 29/03/2014 i get the colored cell but its value now is 39536, the same thing happen with the numeric value 1534000001629, also in this case i get the colored cell but if i try to change the value from 1534000001629 to 1534000001630 i get 1,534+E12.
How can I fix it?
The problem is that cell styles don't only control the colour of a cell, they also control the formatting applied to it. So, what's happening is that you're replacing a cell style that eg formats as #.#%, and instead applying one that's eg red but has no number/date formatting rules applied to it.
Cell styles are workbook scoped, so you shouldn't create one per cell, thus you should make your logic somewhat like:
// Lookup from number format to the coloured version
Map<String,CellStyle> styles = new Hashmap<String,CellStyle>();
// Method to make the cell a different colour
public void fillCell(Workbook wb, Row row, int errorColumn){
Cell cell = row.getCell(j);
// Try to find a coloured one for this data formatting
String formatStr = cell.getCellStyle().getDataFormatString();
CellStyle cs = styles.get(formatStr);
if (cs == null) {
// Need to create a new coloured one
cs = wb.createCellStyle();
cs.setFillForegroundColor((short) 10);
cs.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
cs.setDataFormat(
wb.getCreationHelper().createDataFormat().getFormat(formatStr));
// Save this for later
styles.put(formatStr, cs);
}
// Apply the coloured form, with the format string
cell.setCellStyle(cs);
}
You could either convert it to a string if you don't need to do any date processing on it later as so:
cell.setCellType(CELL_TYPE_STRING);
therwise you may be able to do it by getting the date value from the cell into Java.Util.Date object and then saving it bacl=k:
Date date=cell.getDateCellValue();
//colour change
cell.setValue(date);
I don't have time to test this right now but let me know if it works and if not I'll look more into it.
You can get more information here
I'm generating a docx file with apache-poi. In the wordfile, I add tables, whose columns have a width I would like to see fixed.
Currently, I use the technique described here: http://apache-poi.1045710.n5.nabble.com/Is-there-a-way-to-set-the-width-of-a-column-in-XWPFTableCell-td5711491.html
Basically, this entails setting
cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(cols[j]));
on each cell of that column.
The problem is that while the file opens perfectly in MS Word, open office interprets the values I set to the columnwidth differently. Whereas MS Word apparantly assumes 20-th of a point as units, open office seems to use points instead and therefore all columns are 20 times wider when I open the generated document in OO.
Usually when I see something weird in the generated output, I unpack the docx file, see what the value should be and change my code. But open office does not seem to be able to save to docx, so I can't change the value in OO save it back and see if Word still interprets the document correctly in order to find a cross-application solution.
Any idea how I set the width of the table column so that both OO and MS Wordt interprets it the same?
Don't touch single cells.
Add a GRID:
XWPFDocument doc = new XWPFDocument();
XWPFTable table = doc.createTable(1,2);
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(6000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(2000));
table.getRow(0).getCell(0).setText("1A");
table.getRow(0).getCell(1).setText("1B");
XWPFTableRow newrow = table.createRow();
newrow.getCell(0).setText("2A");
newrow.getCell(1).setText("2B");
The grid sets widths for entire columns. You don't need to do any cycles to set a width for every cell. It works in LibreOffice and GoogleDocs.
To watch the seted width in MS Word too, you may set widths of cells in the first row:
widthCellsAcrossRow(table, 0, 0, 4000);
widthCellsAcrossRow(table, 0, 0, 5000);
private static void widthCellsAcrossRow (XWPFTable table, int rowNum, int colNum, int width) {
XWPFTableCell cell = table.getRow(rowNum).getCell(colNum);
if (cell.getCTTc().getTcPr() == null)
cell.getCTTc().addNewTcPr();
if (cell.getCTTc().getTcPr().getTcW()==null)
cell.getCTTc().getTcPr().addNewTcW();
cell.getCTTc().getTcPr().getTcW().setW(BigInteger.valueOf((long) width));
}
Answer extracted from question:
It was recently pointed out to me that LibreOffice is able to save to docx. By changing the generated file and saving it back and decompiling the result, I have been able to resolve the issue.
Key is to put an explicit width to the table itself first. Word doesn't seem to care about its presence, and OpenOffice/LibreOffice are able to render the table correctly.
So, after creation of the table, I did as follows:
CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
width.setType(STTblWidth.DXA);
width.setW(BigInteger.valueOf(9072));
Upon creation of the table, the layout is set to "auto" by default hence the width of the cell will always increase to follow the length of the string. As per OpenXML markup, it look's like
w:tblPr
w:tblLayout w:type="auto"
the solution is to set the layout to fixed and set the individual column length
w:tblPr
w:tblLayout w:type="fixed"
Here's the poi code for setting table layout:
XWPFTable table = document.createTable();
CTTblLayoutType type = table.getCTTbl().getTblPr().addNewTblLayout();
type.setType(STTblLayoutType.FIXED);
Here's how to set the individual width:
int[] cols = {
4896,
1872,
4032,
1728,
1440
};
for (int i = 0; i < table.getNumberOfRows(); i++) {
XWPFTableRow row = table.getRow(i);
int numCells = row.getTableCells().size();
for (int j = 0; j < numCells; j++) {
XWPFTableCell cell = row.getCell(j);
CTTblWidth cellWidth = cell.getCTTc().addNewTcPr().addNewTcW();
CTTcPr pr = cell.getCTTc().addNewTcPr();
pr.addNewNoWrap();
cellWidth.setW(BigInteger.valueOf(cols[j]));
}
}
column lengths are in twentieths of a point (dxa) or 1/1440 inch.
This is a major and very tricky element. I solved it using this own generic method of setting the widths of a table cell.
private static void setTableColumnWidths(XWPFTable table) {
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(2000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(3200));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1105));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1105));
}
Based on other answers...
public static void setTableColumnsWidth(XWPFTable table, long... widths) {
CTTblGrid grid = table.getCTTbl().addNewTblGrid();
for (long w : widths) {
grid.addNewGridCol().setW(BigInteger.valueOf(w));
}
}
Usage:
setTableColumnsWidth(table, 1440, 2700, 3000, 1440);