需要使用到RenderTargetBitmap,可以參考在WPF中存讀圖檔
但是有時候會發現存出來的圖檔被位移或是根本就沒有影像
這是因為RenderTargetBitmap(RTB)在處理視覺化物件的描繪(Render)時
會依照該物件和其父物件(Parent)的相關性做處理
也就是會因為該物件的Margin或其父物件的Padding或BorderThickness
如果完全不做處理,那就會以父物件的左上角為準,多出一個位移
雖然我覺得這應該算WPF的bug
但是依照MicroSoft的解釋RenderTargetBitmap layout offset influence
似乎覺得這是應該發生的,而且不會再改的感覺
基本上目前我知道的解決方法有三種
方法 | 說明 |
---|---|
加一層Border | 簡單,但是會改變視覺物件結構樹 |
用VisualBrush畫出 | 不改動到原本架構,但需要多做一些繪製的處理 |
用Measure和Arrange暫時改變相對位置 | 改動後需要手動改回,而且WPF有針對這兩個函式作最佳化處理,所以不容易瞭解要怎麼用 |
第一個方法最單純,因為RTB只針對父物件作位移
所以就直接在你需要描繪的物件外面直接包一層類似像Border的容器
而且把需要用的margin都移到這個容器
之後就可以直接依照RTB的使用方法直接用了
如果原本的物件是這樣放
<Grid>
<Canvas Margin="20" />
</Grid>
那就改成
<Grid>
<Border Margin="20">
<Canvas />
</Border>
</Grid>
這樣就可以用之前在在WPF中存讀圖檔提到的方法
將物件送進SaveTo()存起來
private void SaveTo(Visual v, string f)
{
/// get bound of the visual
Rect b = VisualTreeHelper.GetDescendantBounds(v);
/// new a RenderTargetBitmap with actual size of c
RenderTargetBitmap r = new RenderTargetBitmap(
(int)b.Width, (int)b.Height,
96, 96, PixelFormats.Pbgra32);
/// render visual
r.Render(v);
/// new a JpegBitmapEncoder and add r into it
JpegBitmapEncoder e = new JpegBitmapEncoder();
e.Frames.Add(BitmapFrame.Create(r));
/// new a FileStream to write the image file
FileStream s = new FileStream(f,
FileMode.OpenOrCreate, FileAccess.Write);
e.Save(s);
s.Close();
}
第二種方法則是先將該視覺物件畫出來
直接將想要繪製的物件傳入修改過的SaveTo()
只是在render之前先將視覺物件作一個轉換
private DrawingVisual ModifyToDrawingVisual(Visual v)
{
/// new a drawing visual and get its context
DrawingVisual dv = new DrawingVisual();
DrawingContext dc = dv.RenderOpen();
/// generate a visual brush by input, and paint
VisualBrush vb = new VisualBrush(v);
dc.DrawRectangle(vb, null, b);
dc.Close();
return dv;
}
這時候要注意,使用context的時候要記得做close()才會真正做動作
如果怕忘記的話可以改用using將使用的區塊包起來
而原本在SaveTo裡面的Render就改寫成
/// render visual
r.Render(ModifyToDrawingVisual(v));
第三種方法是用Measure和Arrange暫時將位置換一下
一樣在render之前將視覺物件做個轉換
private void ModifyPosition(FrameworkElement fe )
{
/// get the size of the visual with margin
Size fs = new Size(
fe.ActualWidth +
fe.Margin.Left + fe.Margin.Right,
fe.ActualHeight +
fe.Margin.Top + fe.Margin.Bottom);
/// measure the visual with new size
fe.Measure(fs);
/// arrange the visual to align parent with (0,0)
fe.Arrange(new Rect(
-fe.Margin.Left, -fe.Margin.Top,
fs.Width, fs.Height));
}
這邊要注意這種方法只能套用在UIElement上面
另外要記得要把位置arrange回來
private void ModifyPositionBack(FrameworkElement fe)
{
/// remeasure a size smaller than need, wpf will
/// rearrange it to the original position
fe.Measure(new Size());
}
因為這時候measure的大小較實際上的大小為小
所以wpf會自動重新做排版,將位置對齊回去
而原本的render則是改寫成
/// render visual
ModifyPosition(v as FrameworkElement);
r.Render(v);
ModifyPositionBack(v as FrameworkElement);
如果對measure和arrange有興趣的可以參考UIElement.Measure 方法
--
參考資料
RenderTargetBitmap layout offset influence
RenderTargetBitmap tips
RenderTargetBitmap and XamChart - Broken, Redux
666
回覆刪除