WPF、Polylineとかの要素をBitmapで取得、ピッタリ収まるサイズで取得、でもまだ不完全
見た目通りのサイズ取得には、
VisualTreeHelper.GetDescendantBoundsメソッドを使う
結果
環境
- Windows 10 Home バージョン 21H2
- Visual Studio Community 2022 Version 17.5.1
- WPF
- C#
- .NET 6.0
コード
2023WPF/20230306_GetShapeBounds/20230306_GetShapeBounds at main · gogowaten/2023WPF
github.com
MainWindow.xaml
<Window x:Class="_20230306_GetShapeBounds.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20230306_GetShapeBounds" mc:Ignorable="d" Title="MainWindow" Height="350" Width="400"> <Grid> <Canvas x:Name="MyCanvas"> <Polyline x:Name="MyPolyline" Points="0,0 0,200" StrokeThickness="80" Stroke="Pink" Canvas.Left="100" Canvas.Top="50"/> <Polyline x:Name="MyPolylineV" Points="40,0 0,100 100,0" StrokeThickness="20" Stroke="Crimson" Canvas.Left="200" Canvas.Top="50"/> <Button Content="test" Click="Button_Click" Width="200" Canvas.Left="100"/> </Canvas> </Grid> </Window>
MainWindow.xaml.cs
using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; //PolylineのBitmapを作成 //VisualTreeHelper.GetDescendantBoundsを使うと見た目通りのサイズが取得できる //サイズ関連のプロパティはいくつかあるけど見た目通りではないものが多い //Width、Height、RenderSize、ActualWidth、ActualHeight、DesiredSize //RenderTransformを使った回転拡大縮小されているPolylineには未対応 namespace _20230306_GetShapeBounds { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { BitmapSource lineBitmap = MakeBitmap(MyPolyline); BitmapSource lineVBitmap = MakeBitmap(MyPolylineV); } /// <summary> /// 指定要素がピッタリ収まるサイズで、見た目通りのBitmapを作成する /// </summary> /// <param name="element">Polyline以外でもできるはず</param> /// <returns></returns> private BitmapSource MakeBitmap(FrameworkElement element) { //ピッタリ収まるサイズ取得 Size desSize = VisualTreeHelper.GetDescendantBounds(element).Size; VisualBrush brush = new(element) { Stretch = Stretch.None }; DrawingVisual visual = new(); using (var context = visual.RenderOpen()) { context.DrawRectangle(brush, null, new Rect(desSize)); } //Bitmapのサイズはint型なので+0.5してからintにキャストすることで //四捨五入してるけど、+1にして切り上げにするのもいい RenderTargetBitmap bitmap = new((int)(desSize.Width + 0.5), (int)(desSize.Height + 0.5), 96, 96, PixelFormats.Pbgra32); bitmap.Render(visual); return bitmap; } } }
前回と違うのはサイズ取得方法
前回:要素.ActualWidthと要素.ActualHeight
今回:VisualTreeHelper.GetDescendantBounds(要素)
感想
Polylineなど太さ指定のStrokeThicknessがある要素は、プロパティのRenderSizeとかActualWidth、ActualHeightでは見た目通りの正確なサイズが取得できない
左のPolylineはStrokeThickness(線の太さ)を80に指定したPolyline、これの幅は見た目通りなら80なんだけど、デザイナー画面上での今選択している要素のサイズを示す四角枠は、右半分だけになっているので、この時点で怪しい
実際にWidthのプロパティを見てみると
40!これじゃない!
40、ActualWidth
40、DesiredSize.Width
40、RenderSize.Width
80、VisualTreeHelper.GetDescendantBounds(MyPolyline).Width
80、VisualTreeHelper.GetDrawing(MyPolyline).Bounds.Width
上の3つは要素のプロパティ、これらでは見た目通りの値が取得できていない
下の2つはVisualTreeHelperクラスのメソッドを使ったもの、これらは見た目通りの値が取得できている、じゃあどっちがいいのかは
GetDescendantBoundsの方がいい、これだと大抵の状況、要素で取得できる
一方GetDrawingメソッドは起動時にはnull、Canvasとかだと常にnullだった
ってことでサイズ取得はVisualTreeHelper.GetDescendantBoundsに決まったんだけど、これでも対応できない状況が回転表示されたPolyline
これを取得するために
取得したRectを回転情報が入ったRenderTransformを使って
回転させて回転後のRect取得、このRectのサイズを使ってBitmapを作った結果が
左はいいけど上下の余白が大きすぎる、これはたぶん
見た目通りのRectが赤枠で、これを回転させた状態がピッタリ収まるRectが黒枠が取得されたってこと。実際そういう処理をしているからそうなったんだなあと納得できるけど、これじゃないんだよなあ
期待するのは
今はPolylineそのものは諦めて、PathのGeometryとかを使ってPolylineを表示して、Geometryの便利なメソッドを使うとできそうかなってところまできた
関連記事
次回は2日後、ついにできたけど…
gogowaten.hatenablog.com
前回(2ヶ月前)のは不完全だった、今回もだけどね…
WPF、Canvas全体やCanvasに配置した要素を画像(png)ファイルにする。回転や拡大変換要素にも対応版 - 午後わてんのブログ