原文位址:https://www.cnblogs.com/younShieh
項目中遇到一個難題,需要将上百個沒有顯示出來的Canvas存儲為圖檔儲存在本地。
- 查閱資料後(百度一下)後得知儲存為本地圖檔可以通過BitmapSource的轉換,通過PngBitmapEncoder() 來實作。具體代碼如下:
//path為儲存路徑
using (FileStream outStream = new FileStream(path, FileMode.Create))
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(outStream);
}
- 如何将位圖轉換成圖檔的方法知道了,接下來就是要把Canvas轉換成Bitmap了。RenderTargetBitmap() 方法就可以實作Visual對象到位圖的轉換。檢視Canvas是繼承于Canvas的,是以理論上應該沒問題。嘗試了一下,确實如此。代碼如下:
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)canvas.ActualWidth, (int)gd.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(canvas);
- 理論上到這裡就已經完了,但是我的Canvas并沒有顯示出來,導緻ActualWidth和ActualHeight兩個屬性值都為0,且不能通過設定大小改變其實際尺寸,這就有點摳腦殼了。那就繼續查資料吧。對于ActualWidth和ActualHeight這兩個屬性,CSDN如是說:
此屬性是基于其他寬度輸入和布局系統的計算的值。
值由布局系統本身基于實際呈現的傳遞,設定,是以可能稍微小于屬性的設定值如Width是作為輸入更改的基礎。
因為ActualWidth是計算後的值,應注意可能有多次或遞增的報告更改為它作為各種操作結果由布局系統。布局系統可能會計算子元素所需的測量空間、父元素的限制等。
盡管您不能設定此屬性從XAML,您可以基于Trigger樣式中其值。
也就是說ActualWidth和ActualHeight是不能設定的,是通過實際呈現來自動計算出來的。但是我的Canvas沒有呈現出來啊,就不能被動計算出來了,這可怎麼辦呢。。。不可以被動計算,就隻有主動去設定,主動計算這條路可以走了。。。又是查閱資料後,還是發現了有路可走的。
https://***.com/questions/5189139/how-to-render-a-wpf-usercontrol-to-a-bitmap-without-creating-a-window
通過學習大佬們的經驗得知,沒有顯示的界面也是可以轉換成位圖的,主要是需要去測量Measure() 和定位Arrange() 來設定Canvas的位置和大小,通過這兩個方法可以形成遞歸布局更新。也就是說通過這兩個方法,設定了父元素為子元素計算的最終大小,也就是實際的尺寸。
canvas.Measure(new Size(300, 300));
canvas.Arrange(new Rect(new Size(300,300)));
- 理論上到這裡就已經完了,但是我的Cancas們還有一個先決條件,就是他們有很多。(問題确實有點多。。。)我需要儲存的Canvas數量太多,是以不負衆望,我的記憶體爆了(要爆啦~)。但是我已經做了自動回收,沒理由啊。 我嘗試查找了很多地方的的可能會出現的問題,毫不吝啬的使用 ==GC.Collect(); Dispose(); using()== 等等等方法,但是卻沒什麼卵用。。。扣破腦殼,一段代碼一段代碼的屏蔽,分布排查到底是哪裡的問題。我以為是位圖轉換導緻的記憶體洩露,但是分步調試後發現其實并不怎麼消耗記憶體,而且都做了合理的回收。慢慢調試後我發現原來是我的測量Measure() 和定位Arrange() 這兩個方法占用的大量的記憶體,而且應該是沒有回收到。
- 首先我想的是我能不能不用這兩個方法。因為我的界面不能顯示,是以我要把他放到記憶體裡面去渲染,如果不渲染的話,直接設定RenderTargetBitmap() 的 尺寸得到的是空白的圖(試過了。。)。查了很久的資料後,确實是沒有找到合适的辦法,能做到對不顯示的圖檔不用Arrange() 方法就能轉換成位圖。是以這兩個方法我必須要用,是以也不能投機取巧了,隻能硬着頭皮上了。
- baidu、google輪番上陣,MSDN、***各路齊飛,,,都沒有找到合适的辦法解決。或許沒有遇到我這麼特殊情況的人吧,也有可能是我的搜尋方式有問題。不過,正在我扣破腦殼之際,我想到會不會Canvas 也有對應的Dispose() 方法呢?(原諒我已經暈了,“Dispose()隻能用于繼承于IDisposable類”的知識點早已飛到九霄雲外)不過,Canvas确實沒有的Dispose() 方法,,,傷心,絕望。那會不會有Arrange() 對應的 Dispose() 方法呢?嘗試在Canvas後輸入Arrage,得到了一個==InvalidateArrange()== 方法:
使元素排列狀态(布局)無效。 排列狀态失效後,該元素将更新其布局,更新将以異步方式發生,除非随後由 System.Windows.UIElement.UpdateLayout強制執行。
使元素排列狀态(布局)無效,不就是釋放布局占用的記憶體資源了?嘗試了一下,果然如此。記憶體占用情況再也不是“一行白鹭上青天”了。。。唉,記憶體爆了的問題就此得以解決,媽媽再也不用擔心我的軟體崩潰啦~~~
附代碼如下:
canvas.Measure(new System.Windows.Size(1920, 1080));
canvas.Arrange(new Rect(0, 0, 1920, 1080));
canvas.Render(elementCanvas);
canvas.InvalidateArrange();
canvas.InvalidateMeasure();
canvas.UpdateLayout();
由此得到的教訓是,代碼還是得自己敲。。。不然掉到坑你都不知道怎麼爬出來。。。
打完收工。