以前の記事で ItemsControl を利用してデータ点をプロットする方法をちょろっと紹介しましたが、
よくよく調べてみると、やっぱりデータ点が多いときは描画に時間がかかってしまいます。
そうは言ってもよくわかんないので、
簡単なサンプルを作ってみました。
まずはデータ点のクラスを次のように定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
namespace ItemsControlSample.Model { using System.Windows; using System.Collections.ObjectModel; public class GraphPoint { public Point Point { get; set; } } public class GraphPoints { public ObservableCollection<GraphPoint> Points { get; set; } } public class GraphData : ObservableCollection<GraphPoints> { } } |
折れ線グラフは 1 本だけとは限らないので、
複数本のグラフを保持できるようにしました。
GraphData クラスが複数本のグラフを保持しています。
その下層の GraphPoitns クラスが 1 本分のグラフの点を保持しています。
最下層の GraphPoint クラスがあるグラフのある点の情報を保持しています。
このクラスを用いて ViewModel でグラフを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
namespace ItemsControlSample.ViewModel { using ItemsControlSample.Model; using System.Collections.ObjectModel; using System.Windows; public class MainViewModel : ViewModelBase { private GraphData graphData; /// <summary> /// グラフデータ /// </summary> public GraphData GraphData { get { return graphData; } set { SetProperty(ref graphData, value, "GraphData"); } } private DelegateCommand testCommand; /// <summary> /// コマンド /// </summary> public DelegateCommand TestCommand { get { if (testCommand == null) testCommand = new DelegateCommand(p => { var data = new GraphData(); for (int i = 0; i < 2; i++) { data.Add(new GraphPoints() { Points = new ObservableCollection<GraphPoint>() }); for (int j = 0; j < 1000; j++) { data[i].Points.Add(new GraphPoint() { Point = new Point(5 * i + 5 * j, 5 * j) }); } } GraphData = data; }); return testCommand; } } } } |
View が表示されると同時に描画されてしまうと
グラフを表示するためにかかる時間が実感できないので、
TestCommand が実行されたら描画されるようにしておきます。
31 行目の繰り返し回数でグラフの本数が決まります。
34 行目の繰り返し回数で 1 つのグラフに対するデータ点数が決まります。
上記のコードでは 1000 点のグラフが 2 つできることになります。
グラフの形状は 36 行目で記述している通り、右肩下がりの直線グラフになっています。
蛇足ですが、ViewModelBase クラスは INotifyPropertyChanged を実装しており、
プロパティ変更ヘルパとして SetProperty<T>() メソッドが定義されています。
さて、ここからが本題です。
このグラフを表示するための View を ItemsControl を用いて次のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<Window x:Class="ItemsControlSample.View.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ItemsControlSample.ViewModel" Title="MainView" Height="350" Width="525"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <Grid> <StackPanel> <Button Content="Click me!" Command="{Binding TestCommand}" /> <ItemsControl ItemsSource="{Binding GraphData}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Grid IsItemsHost="True" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <ItemsControl ItemsSource="{Binding Points}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Grid IsItemsHost="True" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Path Fill="Red"> <Path.Data> <EllipseGeometry Center="{Binding Point}" RadiusX="2" RadiusY="2" /> </Path.Data> </Path> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Grid> </Window> |
最初にあるボタンは TestCommand を実行するためのものです。
つまりこのボタンを押すとグラフが描画されます。
ItemsControl は、自分の中に ItemsControl を入れるという、
少々複雑なことになっています。
上位の ItemsControl で複数本のグラフを描画するようにし、
入れ子になっている ItemsControl で 1 つのグラフに対するデータ点すべてを描画させています。
上記の ViewModel ではデータ点は 2000 個ですが、
これくらいだと大して表示に時間がかかるようには感じません。
しかし、ViewModel.cs の 34 行目の繰り返し回数を 10000 回にすると、
つまり、データ点を 20000 個にすると、
途端に表示に時間がかかってしまいます。
グラフを 1 回だけ描画するなら多少我慢できますが、
軸の移動/拡大処理等ダイナミックなグラフ表示を考えているので、
これはちょっと面白くありません。
やっぱり DrawingVisual に手を出すしかないのかな。
ItemsControl を使う方法は XAML をフル活用している感じがして
結構気に入っていたんだけど。
サンプルコード:ItemsControlSample.zip