簡體   English   中英

Apache 合並單元格的 POI 和自動行高

[英]Apache POI and Auto Row Height with merged cells

我正在使用 Apache POI,我遇到了一個奇怪的問題。 我可以自動調整行的大小,但前提是該行中沒有合並的單元格。 這是一個例子:

new FileOutputStream('test.xlsx').withStream { OutputStream os ->
    Workbook workbook = new XSSFWorkbook();
    Sheet sheet = workbook.createSheet();

    CellStyle wrapStyle = workbook.createCellStyle();
    wrapStyle.setWrapText(true);

    Row row = sheet.createRow(0); row.setRowStyle(wrapStyle);

    Cell cell = row.createCell(0); cell.setCellStyle(wrapStyle);
    cell.setCellValue("Very long text that needs to be wrapped")

    cell = row.createCell(1); cell.setCellStyle(wrapStyle);
    cell.setCellValue("Short text");

    cell = row.createCell(2); cell.setCellStyle(wrapStyle);
    cell.setCellValue("");

    // These two lines break row auto-height!
    //
    CellRangeAddress cellRangeAddress = new CellRangeAddress(0, 0, 1, 2);
    sheet.addMergedRegion(cellRangeAddress);

    workbook.write(os);
}

此代碼生成以下文檔: 在此處輸入圖像描述

但是,一旦我注釋掉合並兩個單元格的行,output 看起來像這樣: 在此處輸入圖像描述

這是一個錯誤嗎? 有誰知道解決方法?

通過使用 Val Blant 的最后一部分,我做了一些更容易使用但又非常復雜的事情。 請注意,出於個人原因,有一行代碼在單元格高度上添加了一行。 如果您不希望那樣,請將其刪除。 也可以隨意將其更改為非靜態,由於我正在工作的公司使特定類成為靜態,因此我不得不使用靜態方法。

PS:這是我在 stackoverflow 上的第一篇文章,請保持溫和。 :)

解決方法:

public static Boolean isCellMerged(Cell cell) {
        Sheet sheet = cell.getSheet();
        for (CellRangeAddress mergedRegionRange : sheet.getMergedRegions()) {
            Integer cellColumn = cell.getColumnIndex();
            Integer cellRow = cell.getRowIndex();
            if (mergedRegionRange.containsColumn(cellColumn) && mergedRegionRange.containsRow(cellRow)) {
                return true;
            }
        }
        return false;
    }

public static List<List<Cell>> getCellsInRowsInsideRegionRange(Cell cell) {
        Sheet sheet = cell.getSheet();
        List<List<Cell>> mergedRowList = new ArrayList<>();
        List<Cell> mergedCellsList = new ArrayList<>();

        //Nejdříve musíme zjistit sloučenou sekci dané buňky
        for (CellRangeAddress mergedRegionRange : sheet.getMergedRegions()) {
            Integer cellColumn = cell.getColumnIndex();
            Integer cellRow = cell.getRowIndex();
            if (mergedRegionRange.containsColumn(cellColumn) && mergedRegionRange.containsRow(cellRow)) {

                //Protože CellRangeAddress nemá moc metod, musíme si pomoci sami a získat z ní buňky a řádky
                for (Row row : sheet) {
                    for (Cell iteratedCell : row) {
                        Integer iteratedCellColumn = iteratedCell.getColumnIndex();
                        Integer iteratedCellRow = iteratedCell.getRowIndex();
                        if (mergedRegionRange.containsColumn(iteratedCellColumn) && mergedRegionRange.containsRow(iteratedCellRow)) {
                            //Rozdělování jednotlivých řádků
                            //Není-li řádek bez buněk...
                            if (!mergedCellsList.isEmpty()) {

                                //Tak buňku přidáme do Listu buněk...
                                mergedCellsList.add(iteratedCell);

                            } else {
                                //Pokud se jedná o první buňku prvního řádku, tak přidáme rovnou
                                mergedCellsList.add(iteratedCell);
                            }
                        }
                    }
                    //Vložíme List buněk daného řádku do Listu řádků
                    if (!mergedCellsList.isEmpty()) {
                        mergedRowList.add(mergedCellsList);
                    }

                    //A vyresetujeme list buněk (začneme tak nanovo novým řádkem)
                    mergedCellsList = null;
                    mergedCellsList = new ArrayList<>();
                }
                //Vrátíme výsledný List řádků, obsahující Listy buněk ve sloučené sekci.
                if (!mergedRowList.isEmpty()) {
                    return mergedRowList;
                } else {
                    return null;
                }
            }
        }
        return null;
    }

    public static void adjustRowHeightForRowWithNonMergedCells(Row row) {
        row.setHeight((short) -1);
    }

public static void adjustRowHeightForRowWithMergedCells(Row row) {
        Sheet sheet = row.getSheet();
        Cell longestTextCell = null;

        //Potřebujeme získat buňku s nejdelším textem
        for (Cell iteratedCell : row) {
            String iteratedTextString = iteratedCell.getStringCellValue();

            if (longestTextCell != null && StringUtils.isNotBlank(longestTextCell.getStringCellValue())) {
                if (iteratedTextString.length() > longestTextCell.getStringCellValue().length()) {
                    longestTextCell = iteratedCell;
                }
            } else {
                longestTextCell = iteratedCell;
            }

        }

        //Z textově nejobsáhlejší buňky potřebujeme dostat údaje
        String longestText = "";

        if (StringUtils.isNotBlank(longestTextCell.getStringCellValue()) && longestTextCell != null) {
            longestText = longestTextCell.getStringCellValue();

            //Protože textově nejobsáhlejší buňka nemusí nutně být sloučeného typu, je zapotřebí to všude ošetřit
            Boolean isLongestTextCellMerged = isCellMerged(longestTextCell);
            Float longestCellWidthInPixels = 0f;
            Float longestMergedCellWidthInPixels = 0f;

            //Získat šířku nesloučené nejobsáhlejší buňky je jednoduché
            if (!isLongestTextCellMerged) {
                Integer longestCellColumnIndex = longestTextCell.getColumnIndex();
                longestCellWidthInPixels = sheet.getColumnWidthInPixels(longestCellColumnIndex);

            } else {

                //Musíme přijít na šířku sloučené buňky namísto buňky uvnitř sloučené buňky
                List<List<Cell>> cellsInMergedRegion = getCellsInRowsInsideRegionRange(longestTextCell);
                longestMergedCellWidthInPixels = 0f;

                //Projdeme řádky
                for (List<Cell> iteratedCell2List : cellsInMergedRegion) {
                    Float iteratedMergedCell2WidthInPixels = 0f;

                    //Projdeme jednotlivé buňky ve sloučené buňce na řádku a sečteme jejich šířky
                    for (Cell iteratedCell2 : iteratedCell2List) {
                        Integer iteratedCell2ColumnIndex = iteratedCell2.getColumnIndex();
                        Float iteratedCell2ColumnWidthInPixels = sheet.getColumnWidthInPixels(iteratedCell2ColumnIndex);

                        iteratedMergedCell2WidthInPixels = iteratedMergedCell2WidthInPixels + iteratedCell2ColumnWidthInPixels;
                    }

                    //Získáme šířku nejširší sloučené buňky na řádku
                    if (iteratedMergedCell2WidthInPixels > longestMergedCellWidthInPixels) {
                        longestMergedCellWidthInPixels = iteratedMergedCell2WidthInPixels;
                    }

                    //Resetujeme sčítání
                    iteratedMergedCell2WidthInPixels = 0f;
                }
            }

            //Uložíme si nejširší buňku dle toho, zda je sloučená či nikoliv
            Float longestWidthInPixels;
            if (isLongestTextCellMerged) {
                longestWidthInPixels = longestMergedCellWidthInPixels;
            } else {
                longestWidthInPixels = longestCellWidthInPixels;
            }

            //Potřebujeme font
            Workbook wb = sheet.getWorkbook();
            Short fontIndex = longestTextCell.getCellStyle().getFontIndex();
            Font excelFont = wb.getFontAt(fontIndex);

            //Potřebujeme i jeho styl
            Integer excelFontStyle = java.awt.Font.PLAIN;
            if (excelFont.getBold()) excelFontStyle = java.awt.Font.BOLD;
            if (excelFont.getItalic()) excelFontStyle = java.awt.Font.ITALIC;

            //Potřebujeme získat skutečný font i s velikostí
            java.awt.Font currentFont = new java.awt.Font(excelFont.getFontName(), excelFontStyle, excelFont.getFontHeightInPoints());

            //Získáme řetězec s vlastností
            AttributedString attributedString = new AttributedString(longestText);
            attributedString.addAttribute(TextAttribute.FONT, currentFont);

            //Použijeme LineBreakMeasurer k zjištění kolik řádků bude text potřebovat
            FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
            LineBreakMeasurer measurer = new LineBreakMeasurer(attributedString.getIterator(), fontRenderContext);

            Integer nextPosition = 0;
            Integer lineCount = 0;

            while (measurer.getPosition() < longestText.length()) {
                nextPosition = measurer.nextOffset(longestWidthInPixels);

                //Také musíme ošetřit případ manuálně zadaných LineBreaků pro všechny možné techtle mechtle :-S
                String textLine = StringUtils.substring(longestText, measurer.getPosition(), nextPosition);
                Boolean containsNewLine = StringUtils.containsIgnoreCase(textLine, "\r") || StringUtils.containsIgnoreCase(textLine, "\\r") || StringUtils.containsIgnoreCase(textLine, "\n") || StringUtils.containsIgnoreCase(textLine, "\\n");

                if (containsNewLine) {

                    if (StringUtils.containsIgnoreCase(textLine, "\r\n") || StringUtils.containsIgnoreCase(textLine, "\\r\\n")) {
                        lineCount = lineCount + StringUtils.countMatches(textLine, "\n");
                    } else {

                        if (StringUtils.containsIgnoreCase(textLine, "\r") || StringUtils.containsIgnoreCase(textLine, "\\r")) {
                            lineCount = lineCount + StringUtils.countMatches(textLine, "\r");
                        }
                        if (StringUtils.containsIgnoreCase(textLine, "\n") || StringUtils.containsIgnoreCase(textLine, "\\n")) {
                            lineCount = lineCount + StringUtils.countMatches(textLine, "\n");
                        }

                    }

                    lineCount = lineCount + StringUtils.countMatches(textLine, "\\r?\\n");
                }

                lineCount++;
                measurer.setPosition(nextPosition);
            }

            //Máme počet řádků, zbývá konečný dopočet výšky řádku a jeho použití
            if (lineCount > 1) {

                Float fontHeight = currentFont.getLineMetrics(longestText, fontRenderContext).getHeight();

                //Pro jistotu přidáme jeden řádek navíc, člověk nikdy neví...
                lineCount = lineCount + 1;

                //Potřebujeme získat poslední řádek
                Row lastRow = null;

                if (isCellMerged(longestTextCell)) {
                    List<List<Cell>> mergedCellsInRows = getCellsInRowsInsideRegionRange(longestTextCell);
                    Integer lastRowInMergedSectionIndex = mergedCellsInRows.size() - 1;
                    List<Cell> lastRowInMergedSection = mergedCellsInRows.get(lastRowInMergedSectionIndex);
                    lastRow = lastRowInMergedSection.get(0).getRow();
                } else {
                    lastRow = longestTextCell.getRow();
                }

                //Je potřeba ošetřit velikosti, pokud má sloučená buňka vícero řádků
                Float cellsMergedAboveHeight = 0f;
                if (isCellMerged(longestTextCell)) {
                    if (getCellsInRowsInsideRegionRange(longestTextCell).size() > 1) {
                        List<List<Cell>> mergedCellsInRows = getCellsInRowsInsideRegionRange(longestTextCell);
                        for (List<Cell> rowsWithCells : mergedCellsInRows){
                            if (!lastRow.equals(rowsWithCells.get(0).getRow())){
                                cellsMergedAboveHeight = cellsMergedAboveHeight + rowsWithCells.get(0).getRow().getHeight();
                            }
                        }
                    }
                }
                //Vzorec je ((Velikost fontu krát počet řádků plus (počet řádků krát volný prostor mezi řádky)) krát přepočet Excelu) mínus výška sloučených buněk nad posledním řádkem.
                Short finalRowHeight = (short) (((fontHeight * lineCount + (lineCount * 15))* 10) - cellsMergedAboveHeight);

                //A výsledek nastavíme na poslední řádek, protože jinak to przní sloupce vlevo a vpravo od vyšších řádků
                lastRow.setHeight(finalRowHeight);

            }
        }
    }

我遇到了類似的問題,其中行高未針對合並的單元格進行調整。 我必須編寫自定義 function 來調整該特定行的高度。 這是我的代碼:

我將工作表中的列寬固定為 24,默認字體大小為 11。每當我的字體大小 > 11 並且單元格文本長度溢出列寬或由於較大而無法正確顯示時,我需要調整行高長度。

    private void adjustRowHeightMergedCells(final XSSFCell mergedCell) {

        DataFormatter dataFormatter = new DataFormatter();
        int defaultCharWidth = SheetUtil.getDefaultCharWidth(mergedCell.getRow().getSheet().getWorkbook());
        XSSFRow row = mergedCell.getRow();

        // Getting merged cell width value
        double cellValueWidth = SheetUtil.getCellWidth(mergedCell, defaultCharWidth, dataFormatter, true);

        // If cell width value > 24 (Default value), calculate how much extra row height is needed
// This happends when text length is larger than the column width (24)
        float extraRowHeightDueToCellTextLength = (float) Math.floor(cellValueWidth / 24) * row.getHeightInPoints();
        float extraRowHeightDueToCellTextFontSize = 0;

        // If cell font size > 11 (Default value), calculate how much extra row height is needed
        short cellTextFontSize = mergedCell.getCellStyle().getFont().getFontHeightInPoints();
        if (cellTextFontSize > 11) {
            extraRowHeightDueToCellTextFontSize = (cellTextFontSize-ExcelConstants.DEFAULT_FONT_SIZE) * (5f/3f); 
        }
// 5f/3f in above calculation is custom number which assumed by thinking that for font size 11, my row height shud be 15, and for example custom font size 20, row height shud be 30, hence the factor 5f/ 3f( per extra 1 point increase in font size above 11, row height shud be increased by 5/3)
        // Larger of two adjustment values will be taken and added to current row height
        float extraRowHeightAdjustment = Math.max(extraRowHeightDueToCellTextFontSize,extraRowHeightDueToCellTextLength);
        if(extraRowHeightAdjustment > 0) {
            row.setHeightInPoints(row.getHeightInPoints() + extraRowHeightAdjustment);
        }
    }

有點 hacky 解決方案,但適用於我的情況,您可以根據您的要求修改它。

經過更多研究,事實證明這是 Excel 本身的問題,而不是 POI。 Excel 確實失去了自動調整行以適應所有已合並單元格的行的內容的能力。 有關更多信息,請參閱:

http://excel.tips.net/T003207_Automatic_Row_Height_For_Merged_Cells_with_Text_Wrap.html http://blog.contextures.com/archives/2012/06/07/autofit-merged-cell-row-height/

解決方法是基於預測行的最大單元格中的行數,然后手動調整行高。 該代碼基於此討論:

http://mail-archives.apache.org/mod_mbox/poi-user/200906.mbox/%3C24216153.post@talk.nabble.com%3E

RowInfoNestedCellInfo是我的自定義數據結構,用於跟蹤工作表布局。 你應該能夠用你的等價物和輔助函數替換這些。

private void adjustRowHeights(Sheet sheet, List<RowInfo> rows, SortedSet<Integer> createdColumnNumbers) {
    SortedMap<Integer, Float> columnWidthsInPx = [] as TreeMap;
    createdColumnNumbers.each {
        columnWidthsInPx.put(it,  sheet.getColumnWidthInPixels(it));
    }
    rows.each { RowInfo rowInfo ->
        if ( rowInfo.hasMergedCells ) {
            Row excelRow = sheet.getRow(rowInfo.rowIndex);

        // Find the column with the longest text - that's the one that will determine
        // the row height
        //
        NestedCellInfo longestCell = rowInfo.getCellWithLongestContent();
        String cellText = longestCell.getText();
        if ( cellText != null && cellText.size() > 5 ) {
            int colIdx = rowInfo.cells.indexOf(longestCell);

            // Figure out available width in pixels, taking colspans into account
            //
            float columnWidthInPx = columnWidthsInPx[colIdx];
            int numberOfMergedColumns = longestCell.colSpan;
            (numberOfMergedColumns - 1).times {
                columnWidthInPx += columnWidthsInPx[colIdx + it];
            }

            // Setup the font we'll use for figuring out where the text will be wrapped
            //
            XSSFFont cellFont = longestCell.getCellFont();
            int fontStyle = Font.PLAIN;
            if ( cellFont.getBold() ) fontStyle = Font.BOLD; 
            if ( cellFont.getItalic() ) fontStyle = Font.ITALIC;

            java.awt.Font currFont = new java.awt.Font(
                cellFont.getFontName(), 
                fontStyle, 
                cellFont.getFontHeightInPoints());

            AttributedString attrStr = new AttributedString(cellText);
            attrStr.addAttribute(TextAttribute.FONT, currFont);

            // Use LineBreakMeasurer to count number of lines needed for the text
            //
            FontRenderContext frc = new FontRenderContext(null, true, true);
            LineBreakMeasurer measurer = new LineBreakMeasurer(attrStr.getIterator(), frc);
            int nextPos = 0;
            int lineCnt = 0;
            while (measurer.getPosition() < cellText.length()) {
                nextPos = measurer.nextOffset( columnWidthInPx );
                lineCnt++;
                measurer.setPosition(nextPos);
            }

            if ( lineCnt > 1 ) {
                excelRow.setHeight((short)(excelRow.getHeight() * lineCnt * /* fudge factor */ 0.7));
            }
        }
    }
}

這個解決方案遠非完美,但它使我能夠繼續前進。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM