[C# WPF] StreamGeometryのLineTo()の長さ制限

2019年4月18日

WPFでStreamGeometryを使って直線を描画していると、ときどき直線が表示されなくなる現象に遭遇しました。
割と大きいスクロールビューに描画をしているところだったので、その辺の問題だろうと思って調べると、どうも.NETの不具合っぽい。

…っていう下書きを書いていたのですが、寝かせている間にMicrosoft Connect自体が消滅してしまって問題のページがどこに行ったかわからない。。。現行のMicrosoft Developer Communityの中を探してみましたが見つからない。
以前はDisappearing Path (WPF)というタイトルでフォーラムに書いてありました。
現在はMicrosoft Connect Has Been Retiredのページにリダイレクトされてしまいます。。。

とりあえず同じ問題にぶつかった方向けに、問題の起こし方と力技対策を書いておきます。
ちなみに件のフォーラムには、描画に使用するPenのThicknessの125,000倍の長さを超える線を描画しようとすると起きると書いてありました。

スポンサーリンク

環境

  • Windows 10 64bit
  • Visual Studio 2015
  • .NET Framework 4.5.2 / 4.6.1 / 4.7.2

問題の現象

挙動は不定なようなので、他の現象が起きたりうまく動いたりする場合もありそうです。
以下は簡易なテストコードですが、スクロールビュー上に描画した際には、線が表示されなかったり、途中で切れたり、という現象が起きました。

<Window x:Class="Sample_StreamGeometry.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:Sample_StreamGeometry"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Canvas>
        <local:LineControl/>
    </Canvas>

</Window>
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Sample_StreamGeometry
{
    public class LineControl : Control
    {
        protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);

            var pen = new Pen()
            {
                Brush = Brushes.Red,
                Thickness = 1
            };
            pen.Freeze();

            var geometry = new StreamGeometry();
            using (var context = geometry.Open())
            {
                var pt = new Point(0, 100);
                context.BeginFigure(pt, false, false);
                pt.X = 125001;
                context.LineTo(pt, true, false);
            }
            geometry.Freeze();

            drawingContext.DrawGeometry(null, pen, geometry);
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

上記のソースコードの

pt.X = 125001;

の部分を

pt.X = 125000;

に変えると描画されるようになります。

どうしても描画したい

そもそもこんな長い線を書くなという話はおいておいて、どうしても描画したい場合にはThicknessの125,000倍を超えないように分割して描画するしかなさそうです。

メリット?

  • 一応描ける

デメリット

  • 描画オブジェクトが増えるので、負荷が増加する
  • 区切り位置ぴったりのところから新しい線を描き始めると、OS側のズーム設定に寄っては線分と線分の間が空いてしまう場合があり、調整がすごい面倒
  • なんでこんな実装にしないといけないかを知らない人にとっては意味不明な実装になるので、メンテナンス性が落ちる、あるいは書き直されて問題が再発する恐れがある

Pathを試す

Pathでの描画を試してみましたが、こちらもだめでした。
この方法だとOnRender()での描画はできなくなるので、例えばView側でCanvasにPathを追加する感じになります。
Styleを定義したいのでResourceDictionaryを追加しています。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:Sample_StreamGeometry">

    <Style x:Key="LineStyle" TargetType="Path">
        <Setter Property="Stroke" Value="#ff0000"/>
        <Setter Property="StrokeThickness" Value="1"/>
        <Setter Property="SnapsToDevicePixels" Value="False"/>
    </Style>

</ResourceDictionary>
<Window x:Class="Sample_StreamGeometry.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:Sample_StreamGeometry"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <ResourceDictionary Source="/Sample_StreamGeometry;component/CustomStyle.xaml"/>
    </Window.Resources>

    <Canvas x:Name="xCanvas"/>

</Window>
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Sample_StreamGeometry
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var lineStyle = FindResource("LineStyle") as Style;
            if (lineStyle == null)
            {
                return;
            }

            // Path - LineTo() : NG
            {
                var geometry = new StreamGeometry();
                using (var context = geometry.Open())
                {
                    var pt = new Point(0, 100);
                    context.BeginFigure(pt, false, false);
                    pt.X = 125001;
                    context.LineTo(pt, true, false);
                }
                geometry.Freeze();

                var path = new Path
                {
                    Data = geometry,
                    Style = lineStyle
                };
                xCanvas.Children.Add(path);
            }

            // Path - PolyLineTo() : NG
            {
                var geometry = new StreamGeometry();
                using (var context = geometry.Open())
                {
                    var pt = new Point(0, 100);
                    context.BeginFigure(pt, false, false);
                    pt.X = 125001;
                    context.PolyLineTo(new Point[] { pt }, true, false);
                }
                geometry.Freeze();

                var path = new Path
                {
                    Data = geometry,
                    Style = lineStyle
                };
                xCanvas.Children.Add(path);
            }
        }
    }
}

もうちょっとましな対策を思いついたら追記します。

おしまい。

スポンサーリンク

C#,WPFC#,StreamGeometry,WPF

Posted by peliphilo