2016年8月19日金曜日

WPFでPolyPolyline

GDIにはPolyPolylineという関数があります。Polylineは節点を指定して折れ線を描く関数ですが、PolyPolylineはその折れ線を複数同時に描くことができます。Polylineを複数回呼び出すより効率がよく、また、「Polylineを複数回呼び出す」という制御すら必要なくなるので非常に重宝した関数でした。

私みたいな老害は、とりあえずこういうクラシカルな関数が新しい描画システムに無かったら怒るわけです。「なんでWPFにPolyPolylineは無いんだ!」と。(Polylineはあります)

特に、WPFはGDIを使ったクラシカルなアプリとは違い、UIとロジックの分離が基本になります。あまり細かく言うと怖い人達にマサカリを投げられそうなのでやめておきますが、ロジック部が作りだした接点の配列を使って複数の線を動的に描画したいとき、XAMLでは動的にPolylineを複数回呼び出すことができず困ってしまいます。PointCollectionの配列をバインディングしたら複数の線を描画してくれるようなPolyPolylineが欲しい!!!!1!!!


しばらく悩んでいましたが、ふと思いつきました。

「ListViewでItemTemplateを使うと複数のアイテムの描画をかなり強力にカスタマイズできるよな…?ならば、そのシステムで複数の線を描画させればいいのでは?」

ListViewには、その大元となる親クラスとしてItemsControlというものがあります。
これでPointCollectionの配列を受け取って、ItemTemplateで配列の個々のPointCollectionについてPolylineで描画することで、PolyPolylineと同等の機能が実現できるはずです。
<ItemsControl ItemsSource="{Binding PointCollections}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="PointCollection">
            <Polyline Points="{Binding}" Stroke="Black" StrokeThickness="1" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>
ItemsControlのItemsSourceでPointCollectionの配列をバインディングし、ItemTemplateでPolylineを使って描画しています。また、ItemsPanelでCanvasを指定してあげることによって、Polylineを描画するエリア(ItemsControlコントロールが貼られている場所)をCanvasにすることができます。
至ってシンプルです。

あとはViewModel側でPointCollectionの配列を与えてあげます。


描画した結果がこちらです。


1万個を超えるアイテム数ですが、これでも難なく表示してくれちゃうWPFさんイケメン!(GDIだと(時代も時代ですが)リソース不足で落ちるのが関の山(ry

もちろん、ItemsControlを使っていますので、PointCollectionへの配列ではなく座標情報以外に別の情報も含んだクラスへの配列にすることで、例えばある一部分だけ色を変えたり線の太さを変えたり、はたまた点線にしたりすることもできるでしょう。そこまでやると、さすがにGDIのPolyPolylineにはできないことになりますね。強力なWPF+XAMLだからこそできることになるでしょう。