2010年10月10日 星期日

使用 NPOI 在匯出的 Excel 檔中逐列內嵌圖片

相信很多人都有使用 NPOI 匯出 Excel 工作表的經驗了,這次來分享一下在 Excel 中內嵌圖片的範例。

在分享的案例中,我將使用 ASP.NET MVC / C# 利用 Google 搜尋圖片的功能,將關鍵字搜尋到的圖片,導出至 Excel 工作表中的每一列,見圖如下:

image


匯出的 Excel 內容為搜尋到的每張圖片,不過我沒將圖片完整輸出,而是產生縮圖後才輸出。

image


因為重點在以 NPOI 產生內嵌圖片的 Excel 工作表,因此我假設讀者都有使用 NPOI 的基礎,只對產生 HSSFWorkbook 的關鍵程式碼說明,其餘的部分可以參考我上傳的 ASP.NET MVC 2 專案,就不另行解釋。

實際上利用 NPOI 產生 workbook 的程式碼如下,關於本文主旨如何利用 NPOI 在 Excel 中插入圖片,特別留意的就是高亮的那幾行程式碼。

private HSSFWorkbook GenerateSearchResult(IEnumerable urls)
{
    int rowIndex = 0;
    int cellIndex = 0;
    int pictureIndex = 0;

    var request = new WebClient();

    var workbook = new HSSFWorkbook();
    var sheet = workbook.CreateSheet("Search Result");
    var patriarch = sheet.CreateDrawingPatriarch();

    #region 樣式
    HSSFCellStyle headerStyle = workbook.CreateCellStyle();
    headerStyle.Alignment = HSSFCellStyle.ALIGN_CENTER;
    headerStyle.VerticalAlignment = HSSFCellStyle.VERTICAL_CENTER;
    headerStyle.FillBackgroundColor = HSSFColor.BLACK.index;
    headerStyle.FillPattern = HSSFCellStyle.SOLID_FOREGROUND;
    headerStyle.BorderTop = headerStyle.BorderLeft =
        headerStyle.BorderRight = headerStyle.BorderBottom = 1;
    HSSFFont headerFont = workbook.CreateFont();
    headerFont.Color = HSSFColor.WHITE.index;
    headerFont.Boldweight = HSSFFont.BOLDWEIGHT_BOLD;
    headerStyle.SetFont(headerFont);

    HSSFCellStyle cellStyle = workbook.CreateCellStyle();
    cellStyle.Alignment = HSSFCellStyle.ALIGN_LEFT;
    cellStyle.VerticalAlignment = HSSFCellStyle.VERTICAL_CENTER;
    cellStyle.BorderTop = cellStyle.BorderLeft =
        cellStyle.BorderRight = cellStyle.BorderBottom = 1;
    cellStyle.WrapText = true;
    #endregion

    #region 表頭參數
    var headers = new[]
    {
        new { Caption = "URL", Width = 40 },
        new { Caption = "Picture", Width = 20 }
    };
    #endregion

    #region 表頭
    HSSFRow row = sheet.CreateRow(rowIndex++);
    HSSFCell cell;
    foreach (var header in headers)
    {
        if (header.Width > 0)
        {
            sheet.SetColumnWidth(cellIndex, header.Width * 256);
        }
        cell = row.CreateCell(cellIndex++);
        cell.SetCellValue(header.Caption);
        cell.CellStyle = headerStyle;
    }
    HSSFCell colorHeader = row.GetCell(cellIndex - 1);
    #endregion

    #region 表身
    foreach (var url in urls)
    {
        row = sheet.CreateRow(rowIndex++);
        cellIndex = 0;

        cell = row.CreateCell(cellIndex++);
        cell.CellStyle = cellStyle;
        cell.SetCellValue(url);

        cell = row.CreateCell(cellIndex++);
        cell.CellStyle = cellStyle;
        try
        {
            // 下載圖片
            var image = Image.FromStream(request.OpenRead(new Uri(url)));

            // 產生縮圖
            decimal sizeRatio = ((decimal)image.Height / image.Width);
            int thumbWidth = 100;
            int thumbHeight = decimal.ToInt32(sizeRatio * thumbWidth);
            var thumbStream = image.GetThumbnailImage(thumbWidth, thumbHeight, () => false, IntPtr.Zero);
            var memoryStream = new MemoryStream();
            thumbStream.Save(memoryStream, ImageFormat.Jpeg);

            // 將縮圖加入到 workbook 中
            pictureIndex = workbook.AddPicture(memoryStream.ToArray(), HSSFWorkbook.PICTURE_TYPE_JPEG);

            // 將縮圖定位到 worksheet 中
            var anchor = new HSSFClientAnchor(0, 0, 0, 0, cell.CellNum, row.RowNum, 0, 0);
            var picture = patriarch.CreatePicture(anchor, pictureIndex);
            var size = picture.GetImageDimension();
            row.HeightInPoints = size.Height;
            picture.Resize();

            // 為了不讓圖片壓線,必須讓圖片有一點位移,你可以把它移除掉看看會產生什麼情況
            // (我得承認這裡是程式中的魔術數字 Orz,但是一時找不到更好的方法)
            anchor.Dx1 = 5;
            anchor.Dy1 = 2;
        }
        catch (Exception ex)
        {
            // 圖片載入失敗,顯示錯誤訊息
            cell.SetCellValue(ex.Message);
        }
    }
    #endregion

    return workbook;
}

參考資料

11 則留言:

匿名 提到...

感謝您的分享 讓人獲益良多

anderson hsin 提到...

站長大人, 您好:
因為程式需求, 需要在EXCEL插入產品圖片, 剛好拜讀到您所寫的範例, 的確有所收穫。但是我按照您的範例所修改的程式, 只秀出來第一張圖片, 而沒能像站長您所提供的範例一般, 每列都可以秀出圖片。不曉得是不是有哪些地方, 我沒有注意到的呢?
冒昧的請您幫忙, 真是不好意思。謝謝。

Unknown 提到...

我猜有可能是圖疊圖了,你可將圖片拖拉看看檢查一下;或者不妨將你的 code 貼上來 ^^

anderson hsin 提到...

站長大人, 您好:
本來也以為是圖疊圖的關係, 但拖拉之後, 確定只有第一張圖有出現。也試著用trace找出問題, 確定有執行到且也有帶入正確的圖檔, 路徑等資訊。如果刻意讓程式產生exception, 該插入圖片的地方, 則會有 exception message。

因為功力不夠, 我的程式碼又臭又長, 超過了意見的限定, 不曉得站長大人還有沒有什麼建議? 感恩。

anderson hsin 提到...

站長大人, 您好:
重新檢視及比對範例程式碼後, 問題已經解決了。問題應該是誤將

Dim patriarch As HSSFPatriarch = MySheet.CreateDrawingPatriarch()

放到迴圈之中, 導致問題的發生。多次打擾, 真不好意思, 最後再次謝謝您。

Unknown 提到...

to anderson,

good job!

Unknown 提到...

感謝分享
我想知道圖片可以放置於"頁首頁尾"嗎?
在EXCEL操作上是可以的,但是用NPOI卻不清楚
官網也沒特意說明這些XD

Unknown 提到...

@陳建穎

基本上這個作法是將絕對定位的圖片位置, 對齊於儲存格(Cell), 參考 line 87

如果沒有誤會你的意思, 頁首頁尾的需求應該是有可能用建立儲存格的方式再對齊圖片達成的喔

Andy Ip 提到...

請問一下可以在Excel中才壓縮圖碼?因為壓縮了才放入Excel質素會差了

Unknown 提到...

@Andy Ip

可以不壓縮圖片的 , 只是可想而知生成的 excel 檔案會隨之變大

我的範例只是順手作了縮圖處理 , 並非一定需要 :)

Andy 提到...

不,我需要壓縮圖片,但不是在C#壓縮,需要在Excel內壓縮
想問NPOI有resize圖功能嗎?