2009年7月16日

輕鬆使用MS Chart Control - 匯出圖片

雖然在圖片上直接按右鍵就可以另存圖片 (沒設定權限的話
不過客戶還是想要有個按鈕能讓圖片下載
所以坎尼只好再想辦法滿足客戶的需求

UI設定

首先,在畫面上準備好下載按鈕和圖表
順便設定一下樣式和內容,這部分之前講過很多次就不多談了

撰寫按鈕事件

先宣告 System.IO.MemoryStream 物件,用來承接 Chart 的 binary 資料
再利用 Chart.SaveImage 方法將資料流放到 MemoryStream 中
把 Stream 轉為 byte[] 陣列後,再丟到匯出的方法中處理

Chart.SaveImage 儲存方式有兩種:FilePath Stream
但用 FilePath 的方式是儲存在 Server 端,要另外再多做個動作才能讓使用者下載
所以坎尼就選用 Stream 來處理

接著是匯出 byte[] 裡的二進位資料
這邊坎尼是參考董大偉老師的 如何在ASP.NET中下載檔案
另外函式做了點小修改,也可以用來匯出其他二進位的資料流


執行程式,點下載圖片後會直接將圖片匯出

噹噹噹噹噹,圖片就這樣下載到使用者的電腦上啦


此次的範例檔下載

小結

上面有講到明明右鍵就可以下載,但為什麼還要弄個按鈕出來,是要累死工程師嗎?
最近坎尼有看到一篇 Web Design 的文章講的很好
(原文是英文,坎尼大致上翻譯一下意思)
「盡量把操作弄成最愚笨的方式,使用者會比較容易使用」
「但仍要保留給進階使用者進階技巧的使用權利,他們會希望用一組快速鍵達到滑鼠點三次的要求」

所以設計者們,記得有機會就多留點隱藏技巧,令使用者有驚喜的感覺
不過不要留了後門給駭客進出啊 XDDDD

21 則留言:

chicken 提到...

供獻點小技巧... 其實不用透過 MemoryStream 啦,直接存到 Response.OutputStream 也可以..

坎尼 提到...

感謝 chicken 大大提供意見
不過剛剛坎尼照 MSDN 上的 Response.OutputStream範例去測,怎麼都做不出來 Orz

一開始是想把 OutputStream 丟到 byte[] 裡,可是取 length 的時候就發生例外了

再來是照範例:設定 ContentType 後,呼叫SaveImage寫到 OutputStream 後直接 Response.Flush()
但目前還是測不出來

chicken大可以指點一下嗎? 感謝 ^^

chicken 提到...

突然想到,你大概踩到一個地雷了... 你是輸出 PNG 格式嗎? 會 GDI+ error...

我貼一下我測的 sample code 片段:

Response.ContentType = "image/jpeg";
Chart1.SaveImage(Response.OutputStream, ChartImageFormat.Jpeg);

這兩行就搞定了。不過格式不指定的話,預設是 PNG,它輸出的 OutputStream 必需要能夠 SEEK,不過.. Response.OutputStream 不支援,只能讓你一路一直寫下去...

運氣還不錯,JPEG 輸出就沒這限制,不在乎圖檔髒一點的話,就用JPEG吧 :D

不然推薦另一個作法,用 MemoryStream 碰到大圖大概會吃掉一堆 memory, 不如輸出成暫存檔,用 Response.TransmitFile( ) 來輸出檔案,可以避開單一頁面載入過多 memory 的問題

坎尼 提到...

還真是踩到 png 這個地雷,囧rz

JPEG 出來的圖是真的蠻醜的
看來還是把檔案存在 Server 上,再把圖檔送給 user 下載會是比較好的作法

但這就要記得定期去清空 Server 上Create的暫存圖檔,以免Server的空間被偷偷吃掉

匿名 提到...

您好:
請問一下,為什麼我匯出時的圖檔是空白的?

坎尼 提到...

hello 匿名你好,
先確認一下你的 Chart 是否有開啟 ViewState?
EnableViewState=true

若是沒有開啟 ViewState,記得在匯出圖片前,再讓 Chart 重新 Render 圖片

本篇有附範例檔,可以把範例下載下來參考一下 ^^

匿名 提到...

坎尼兄:
謝謝你,依據你所說明的,已經可以了!

mo 提到...

不好意思, 請教一下, chart control 製作的圖, 可以匯出在 Excel 嗎?

坎尼 提到...

hi mo,
理論上應該是可以,不過坎尼沒實作這部份,倒是不敢向你保證
剛好接下來的那篇會講 Excel 匯出
坎尼就順便測試看看

作法的流程可能為:
1.圖片存在 Server 端,將圖片路徑內嵌於 Excel 中
2.圖片存為 binary 方式,再用 excel xml 的方式匯出

以上是目前想到的進行方式,有實作出來的讀者也可以交換一下心得 ^^

坎尼 提到...

hi mo,

已經有將圖片匯出至 Excel 的文章
請參考 http://dotnetmis91.blogspot.com/2010/03/chart-control-excel-npoi.html

匿名 提到...

Response.ContentType = "image/jpeg";
Chart1.SaveImage(Response.OutputStream, ChartImageFormat.Jpeg);
請問我執行的結果會變成在網頁上show(_self的方式),而不是出現下載視窗,請問是為什麼呢,謝謝

匿名 提到...

上面沒打好,不好意思@@
執行的結果會變成直接在網頁上show出jpg圖片,而不是出現下載視窗,請問是為什麼呢,謝謝

坎尼 提到...

hi 匿名,

請問你圖片的 binary 資料流,有經過範例中的 ExportStream() 方法處理嗎?

這方法是把原本直接在畫面上顯示的動作,改為檔案下載的方式 (修改 html header)

若非如此,請麻煩再提供更詳細的資訊 ^^

匿名 提到...

坎尼大你好,
我確實少了ExportStream()步驟,
現在已可正常下載圖片了,謝謝你的指導~

初學者 提到...

坎尼大你好,
想下載你提供的範例檔,但無法下載。不知是否有機會可以下載該範例

坎尼 提到...

hi 初學者,

本篇範例坎尼看來是找不到了 orz

本文重點 source code 都有抓圖且有說明,先自行試著照做看看~
這個範例不難,如果有遇到問題再發問吧

初學者 提到...

謝謝坎尼大的幫忙^__^

usein 提到...

HI 這按鈕好像一次只能抓一張 
如果網頁上有5張CHART 討厭的客戶想要有個按鈕 按一下 就把這5張圖下載下來 請問該怎麼做呢? THX 

坎尼 提到...

hi usein,

一次要下載5張, 只要重覆做 btnDownloadPicture_Click 裡的動作 (用迴圈把 Chart1~Chart5 各跑過一次)

如果想要壓縮成 zip,可以去查 System.IO.Compression Namespace 裡的 class,把 5 張圖片一次打包給客戶

黑小胡 提到...

hi 坎尼大大

我照您的方法下去試驗,有下載圖下來,但是Chart上有外框沒內容,有我chart的標頭,EnableViewState=true <<這也設定了。

可以請大大幫我嗎?

我的資料是用datatable做繫結的
DataTable M1_C = this.queryDataTable("M1_ZERO", "CLAMP");
Chart1.DataSource = M1_C;
Chart1.DataBind();

黑小胡 提到...

痾.....坎尼大大

我解掉了.....SORRY,犯了個超級笨的錯誤。

抱歉阿~~~~^^

Google Spreadsheet 裡用規則運算式

最近因為工作關係,遇到要用 Google Form 及 Google Sheet 所以研究了 Google Sheet 裡的一些 function 怎麼用 首先,分享一下如何在 Google Sheet 裡用規則運算 :D