午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF、Polylineとかの要素をBitmapで取得、ピッタリ収まるサイズで取得、でもまだ不完全

見た目通りのサイズ取得には、
VisualTreeHelper.GetDescendantBoundsメソッドを使う

結果

用意したPolyline2つ

実際の表示

取得したBitmapをBitmapSourceVisualizerで確認1

取得したBitmapをBitmapSourceVisualizerで確認2

取得したBitmapをPixtack紫陽花に貼り付けて確認



環境




コード

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のプロパティを見てみると

Widthプロパティ
40!これじゃない!

Width取得一覧
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だった

V字型Polyline、これもGetDescendantBoundsなら取得できる

ってことでサイズ取得はVisualTreeHelper.GetDescendantBoundsに決まったんだけど、これでも対応できない状況が回転表示されたPolyline

回転表示したPolyline

これを取得するために

36行目を37、38行目に変更
取得したRectを回転情報が入ったRenderTransformを使って 回転させて回転後のRect取得、このRectのサイズを使ってBitmapを作った結果が

回転PolylineのBitmap
左はいいけど上下の余白が大きすぎる、これはたぶん
こういうこと?
見た目通りのRectが赤枠で、これを回転させた状態がピッタリ収まるRectが黒枠が取得されたってこと。実際そういう処理をしているからそうなったんだなあと納得できるけど、これじゃないんだよなあ
期待するのは
このサイズを取得したい
今はPolylineそのものは諦めて、PathのGeometryとかを使ってPolylineを表示して、Geometryの便利なメソッドを使うとできそうかなってところまできた




関連記事

次回は2日後、ついにできたけど…
gogowaten.hatenablog.com

前回(2ヶ月前)のは不完全だった、今回もだけどね…
WPFCanvas全体やCanvasに配置した要素を画像(png)ファイルにする。回転や拡大変換要素にも対応版 - 午後わてんのブログ

gogowaten.hatenablog.com