Chartコントロールを使ってWindows Phoneに棒グラフを表示する
Windows Phone 縦向きのページの作成(DataInPage.xaml)
VS2010メニューの「プロジェクト(P)/新しい項目の追加(W)」と選択して、「Windows Phone 縦向きのページ」を選択します。「名前(N)」には「DataInPage.xaml」指定し、[追加(A)]ボタンをクリックします。
コントロールの配置とXAMLの編集
PageTitleという名前を持つTextBlockのTextプロパティに「血圧入力」と指定します。
ツールボックスから、TextBlockコントロールを3個、TextBoxコントロールを3個、Buttonコントロールを2個配置します(図10)。
図10:各種コントロールを配置した(クリックで拡大) |
ichiranButtonは最初の状態では、IsEnabledのチェックを外して、使用不可としておきます。データが1件でも記録されれば、[データ一覧]ボタンは使用可能となります。
書き出されるXAMLコードは省略します。
ソリューションエクスプローラー内のMainPage.xamlを展開して表示される、MainPage.xaml.vbをダブルクリックして、リスト2のコードを記述します。
ロジックコードを記述する
リスト2 (MainPage.xaml.vb)
Option Strict On Imports System.Windows.Documents Imports System.Windows.Input Imports System.Windows.Media Imports System.Windows.Media.Animation Imports Microsoft.Phone.Controls Imports System.Xml.Linq Imports System.Collections.ObjectModel Imports System.IO
仮想ファイルシステムの作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。
Imports System.IO.IsolatedStorage
BloodPressureという名前のクラス内に、文字列の型の「記録日」と数値型の「最高血圧、最低血圧」プロパティを定義しておきます。
Public Class BloodPressure Property 記録日 As String Property 最高血圧 As Integer Property 最低血圧 As Integer End Class Partial Public Class MainPage Inherits PhoneApplicationPage ' コンストラクター Public Sub New() InitializeComponent() ' ListBox コントロールのデータ コンテキストをサンプル データに設定します DataContext = App.ViewModel End Sub ' ViewModel Items のデータを読み込みます
XML要素を表すXElementクラス型のメンバ変数xmldocを宣言します。
Dim xmldoc As XElement
1ページに表示するデータ件数を4件とし、変数numberに格納しておきます。
Dim number As Integer = 4 Dim no1 As Integer = 0 Dim no2 As Integer = 0 Dim Index As Integer = 0 Dim myIndex As Integer = 0
PivotItemクラス型のメンバ変数myPivotItem2を宣言します。
Dim myPivotItem2 As PivotItem
新しいPivotItemクラスのインスタンスmyPivoをメンバ変数として宣言します。PivotItemクラスは、ピボットコントロール内の項目のコンテナを表すクラスです。
Dim myPivo As New PivotItem
表示するデータの開始インデックスを変数startPosに格納します。
Dim startPos As Integer = 0
表示するデータの最終インデックスを変数endPosに格納します。
Dim endPos As Integer = 3
BloodPressureクラス型の新しいObservableCollectionのインスタンス、myBloodPressureListをメンバ変数として宣言します。ObservableCollectioクラスは、項目が追加、削除された時、またはリスト全体が更新された時に通知する動的なデータコレクションを表すクラスです。
Dim myBloodPressureList As New ObservableCollection(Of BloodPressure) Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles Me.Loaded If Not App.ViewModel.IsDataLoaded Then App.ViewModel.LoadData() End If End Sub
ページがアクティブになった時の処理
変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。
Path.CombineでRecordBloodPressureというフォルダとBloodPressure.xmlというXMLファイルを連結し、変数filePathに格納しておきます。
RecordBloodPressureフォルダ内にBloodPressure.xmlが存在する場合は、IsolatedStorageFileクラスのOpenFileメソッドでRecordBloodPressureフォルダ内のBloodPressure.xml ファイルを、指定したファイルアクセスを使用して指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り、変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。
最初の4件のデータと、PivotItemのHeaderとなる「年,月,日~年,月,日」までを表示する処理である、FirstShowプロシージャを実行します。
RecordBloodPressureフォルダ内にBloodPressure.xmlが存在しない場合は、以下の処理を行います。RecordBloodPressureフォルダが存在しない場合は、CreateDirectoryメソッドで、フォルダを作成します。Visual Basic の埋め込み式を用いて、XML宣言とルート要素であるだけのXMLを作成します。IsolatedStorageFileクラスのCreateFileメソッドで、RecordBloodPressureフォルダ内にBloodPressure.xmlファイルを作成します。
RecordBloodPressureフォルダ内にBloodPressure.xmlが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドでルート要素だけのXMLを、ストリームに書き込み、データを入力するDataInPage.xamlに遷移します。血圧のデータが、XMLファイルに1件も記録されていない場合は、ルート要素だけのXMLファイルを作成して、データ入力ページに遷移するということです。
Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs) Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication Dim filePath As String = Path.Combine("RecordBloodPressure", "BloodPressure.xml") If storage.FileExists(filePath) = True Then Using myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read) Using reader As StreamReader = New StreamReader(myStream, System.Text.Encoding.UTF8) Dim readXmldoc As String = reader.ReadToEnd xmldoc = XElement.Parse(readXmldoc) End Using End Using FirstShow() Else If storage.DirectoryExists("RecordBloodPressure") = False Then storage.CreateDirectory("RecordBloodPressure") End If If storage.FileExists(filePath) = False Then Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?> <血圧記録></血圧記録> Using stream As IsolatedStorageFileStream = storage.CreateFile(filePath) End Using If storage.FileExists(filePath) = True Then Using writer As StreamWriter = New StreamWriter(storage.OpenFile(filePath, FileMode.Open, FileAccess.Write)) writer.Flush() writer.Write(xmldoc.ToString) End Using NavigationService.Navigate(New Uri("/DataInPage.xaml", UriKind.Relative)) End If End If End If MyBase.OnNavigatedTo(e) End Sub
最初の4件のデータと、PivotItemのHeaderとなる「年,月,日~年,月,日」までを表示する処理
BloodPressure.xmlの要素を選択するクエリを定義します。要素の子要素を選択するqueryCountクエリを定義します。
Countプロパティで要素の個数を取得し、個数が0か0より小さい場合は、入力ページであるDataInPage.xamlに遷移し、処理を抜けます。
要素の個数が4より小さい場合は、最終インデックスを格納するendPos変数に要素の個数から-1した値を格納します。-1しているのは、Countプロパティは1から始まるのに対し、XMLの要素の個数は0から始まるためです。4より大きい場合は、3を格納します。
変数day1にメンバ変数startPosに該当する要素の子要素の値を格納します。最初の日付が格納されます。変数day2にメンバ変数endPosに該当する要素の子要素の値を格納します。開始日から、最大で3日後の日付が格納されます。
新しいPivotItemのインスタンスmyPivotItemを作成し、Headerプロパティに変数day1とday2の値を指定します。
新しいmCharts.Windows.QuickCharts.SerialChartのインスタンス、mainChartを作成します。
棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chartを作成します。同じく、棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chart2を作成します。
chartの棒グラフのTitleプロパティに「最高血圧」、Brushプロパティに青色、ValueMemberPathプロパティに「最高血圧」と指定します。
ValueMemberPathプロパティは、このグラフのデータソースの保持値にメンバへのパスを設定するプロパティです。同様にchart2のTitleプロパティに「最低血圧」、Brushプロパティに黄色、ValueMemberPathプロパティに「最低血圧」と指定します。最高血圧のグラフの色が青色、最低血圧のグラフの色が黄色で表示されます。
SerialChartのオブジェクトmainChartのAxisForegroundプロパティに赤色、PlotAreaBackgroundプロパティにグレーを指定します。
AxisForegroundプロパティは、グラフの軸のフォアグラウンドカラーを設定します。デフォルトは黒です。PlotAreaBackgroundプロパティは、プロットエリア(軸の内側の領域)の背景として使用するブラシを設定します。デフォルトは白です。CategoryValueMemberPathプロパティに「記録日」と指定します。CategoryValueMemberPathプロパティは、データソース内のカテゴリの値を保持するプロパティへのパスを設定します。DataSourceプロパティにBloodPressureクラス型のObservableCollectionであるmyBloodPressureListを戻り値とするDataプロパティを指定します。
SerialChartにAddメソッドでchartとchart2を追加します。
を選択していたクエリのTakeメソッドに4を指定して、4個の要素内を反復処理する変数resultに、各要素を格納しながら、myBloodPressureListオブジェクトにAddメソッドで、BloodPressureクラスの、各プロパティに要素の子要素の値を指定して、追加していきます。
PivotItemのオブジェクトmyPivotItemのContentプロパティにmainChartオブジェクトを指定します。Pivot1にAddメソッドで棒グラフの設定のされた、myPivotItemを追加します。
最終の日付となるPivotのHeaderを作成します。
増分が4で、3から要素の個数から-1した分だけ繰り返し処理を行います。PivotItemクラスのインスタンスmyPivotItem2オブジェクトを作成します。要素の個数から-1した個数を4で除算して余りを求め、その余りを変数amariに格納します。
繰り返し変数iの値に4を加算した値が、要素の個数から-1した値と同じか、それより大きい場合はiの値に要素の個数から-1した値を代入し、変数numberに0を代入します。変数lastday1にインデックスから変数amari分を差し引いた位置の要素の子要素の値を格納します。変数lastday2にインデックス位置の要素の子要素の値を格納します。
PivotItemクラスのインスタンスmyPivotItem3オブジェクトを生成します。Headerプロパティにlastday1とlastday2の値を指定します。PivotのAddメソッドでHeaderに日付の設定されたmyPivotItem3を追加します。
繰り返し変数iの値に4を加算した値が、要素の個数から-1した値より小さい場合は、変数dya3にi+1のインデックスに位置する要素の子要素の値を格納します。変数dya4にi+numberのインデックスに位置する要素の子要素の値を格納します。
myPivotItem2オブジェクトのHeaderプロパティにday3とday4の値を指定します。PivotのAddメソッドでHeaderに日付の設定されたmyPivotItem2を追加します。
Private Sub FirstShow() Dim query = From c In xmldoc.Descendants("情報") Select c Dim queryCount = From c In xmldoc.Descendants("情報") Select c.Element("記録日") If queryCount.Count <= 0 Then NavigationService.Navigate(New Uri("/DataInPage.xaml", UriKind.Relative)) Exit Sub End If If query.Count < 4 Then endPos = query.Count - 1 Else endPos = 3 End If Dim day1 = xmldoc.Descendants("情報")(startPos).Element("記録日").Value Dim day2 = xmldoc.Descendants("情報")(endPos).Element("記録日").Value Dim myPivotItem As New PivotItem myPivotItem.Header = day1 & "から" & day2 Dim mainChart As New AmCharts.Windows.QuickCharts.SerialChart Dim chart As New AmCharts.Windows.QuickCharts.ColumnGraph Dim chart2 As New AmCharts.Windows.QuickCharts.ColumnGraph chart.Title = "最高血圧" chart.Brush = New SolidColorBrush(Colors.Blue) chart.ValueMemberPath = "最高血圧" chart2.Title = "最低血圧" chart2.Brush = New SolidColorBrush(Colors.Yellow) chart2.ValueMemberPath = "最低血圧" mainChart.AxisForeground = New SolidColorBrush(Colors.Red) mainChart.PlotAreaBackground = New SolidColorBrush(Colors.Gray) mainChart.CategoryValueMemberPath = "記録日" mainChart.DataSource = Data mainChart.Graphs.Add(chart) mainChart.Graphs.Add(chart2) myBloodPressureList.Clear() For Each result In query.Take(4) With myBloodPressureList .Add(New BloodPressure With {.記録日 = result.Element("記録日").Value, .最高血圧 = CInt(result.Element("最高血圧").Value), .最低血圧 = CInt(result.Element("最低血圧").Value)}) End With myPivotItem.Content = mainChart Next Pivot1.Items.Add(myPivotItem) For i = 3 To query.Count - 1 Step number myPivotItem2 = New PivotItem Dim amari As Integer = (query.Count - 1) Mod 4 If i + number >= query.Count - 1 Then i = query.Count - 1 number = 0 Dim lastday1 = xmldoc.Descendants("情報")((i + number) - amari).Element("記録日").Value Dim lastday2 = xmldoc.Descendants("情報")(i + number).Element("記録日").Value Dim myPivotItem3 As New PivotItem myPivotItem3.Header = lastday1 & "から" & lastday2 Pivot1.Items.Add(myPivotItem3) Exit For Else Dim day3 = xmldoc.Descendants("情報")(i + 1).Element("記録日").Value Dim day4 = xmldoc.Descendants("情報")(i + number).Element("記録日").Value myPivotItem2.Header = day3 & "から" & day4 End If Pivot1.Items.Add(myPivotItem2) Next End Sub
AddHandlerステートメントで、動的にピボットアイテムのコンテンツをロードしたり、変更する、イベントハンドラを呼び出す処理
Private Sub DataShow() AddHandler Pivot1.LoadingPivotItem, AddressOf Me.pivot1_LoadingPivotItem End Sub
ピボットアイテムのコンテンツがロードされた時の処理
Pivotの選択されたインデックスを変数Indexに格納しておきます。Indexの値が0かそれより大きい場合の処理です。要素を選択するクエリを定義します。要素の個数を4で除算し、余りを変数amariに格納しておきます。変数no1にIndexに4を乗算した値を格納します。変数no2に変数no1に3を加算した値を格納します。これらの値は後ほどFor~Nextステートメントで使用されます。データの開始と最後を指定する変数です。余りが0より大きくかつ変数no2の値が要素の個数から-1した値より大きいか等しい場合は、no1にはIndexに4を乗算した値が、no2には要素の個数から-1した値が格納されます。
PivotクラスのmyPivot変数を宣言し、senderオブジェクトが保持しているPivotの情報を取得します。Pivotがフリックされない場合は処理を抜けます。Pivotがフリックされた場合は、以下の処理を行います。
Dispatcher.BeginInvokeで非同期処理を行います。
反復変数iを使って、変数no1~no2の処理を繰り返します。
新しいmCharts.Windows.QuickCharts.SerialChartのインスタンス、mainChartを作成します。
棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chartを作成します。同じく、棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chart2を作成します。
chartの棒グラフのTitleプロパティに「最高血圧」、Brushプロパティに青色、ValueMemberPathプロパティに「最高血圧」と指定します。ValueMemberPathプロパティは、このグラフのデータソースの保持値にメンバへのパスを設定するプロパティです。同様にchart2のTitleプロパティに「最低血圧」、Brushプロパティに黄色、ValueMemberPathプロパティに「最低血圧」と指定します。最高血圧のグラフの色が青色、最低血圧のグラフの色が黄色で表示されます。
SerialChartのオブジェクトmainChartのAxisForegroundプロパティに赤色、PlotAreaBackgroundプロパティにグレーを指定します。
AxisForegroundプロパティは、グラフの軸のフォアグラウンドカラーを設定します。デフォルトは黒です。PlotAreaBackgroundプロパティは、プロットエリア(軸の内側の領域)の背景として使用するブラシを設定します。デフォルトは白です。CategoryValueMemberPathプロパティに「記録日」と指定します。CategoryValueMemberPathプロパティは、データソース内のカテゴリの値を保持するプロパティへのパスを設定します。
DataSourceプロパティにBloodPressureクラス型のObservableCollectionであるmyBloodPressureListを戻り値とするDataプロパティを指定します。
SerialChartにAddメソッドでchartとchart2を追加します。
myBloodPressureListオブジェクトにAddメソッドで、BloodPressureクラスの、各プロパティに変数iに該当する要素の子要素の、各値を指定して、追加していきます。最高血圧と最低血圧の値はCInt関数で数値に変換しています。
PivotItemのインスタンスのContentプロパティにmainChartオブジェクトを指定します。
Private Sub pivot1_LoadingPivotItem(sender As Object, e As PivotItemEventArgs) Index = Pivot1.SelectedIndex If Index >= 0 Then Dim query = From c In xmldoc.Descendants("情報") Select c Dim amari As Integer = (query.Count) Mod 4 no1 = Index * 4 no2 = no1 + 3 If amari > 0 AndAlso no2 >= query.Count - 1 Then no1 = Index * 4 no2 = query.Count - 1 End If End If Dim myPivot As Pivot = DirectCast(sender, Pivot) If e.Item Is myPivot.Items(Index) = False Then Exit Sub If e.Item Is myPivot.Items(Index) = True Then Dispatcher.BeginInvoke(Sub() For i As Integer = no1 To no2 Dim mainChart As New AmCharts.Windows.QuickCharts.SerialChart Dim chart As New AmCharts.Windows.QuickCharts.ColumnGraph Dim chart2 As New AmCharts.Windows.QuickCharts.ColumnGraph chart.Title = "最高血圧" chart.Brush = New SolidColorBrush(Colors.Blue) chart.ValueMemberPath = "最高血圧" chart2.Title = "最低血圧" chart2.Brush = New SolidColorBrush(Colors.Yellow) chart2.ValueMemberPath = "最低血圧" mainChart.AxisForeground = New SolidColorBrush(Colors.Red) mainChart.PlotAreaBackground = New SolidColorBrush(Colors.Gray) mainChart.CategoryValueMemberPath = "記録日" mainChart.Graphs.Add(chart) mainChart.Graphs.Add(chart2) mainChart.DataSource = Data With myBloodPressureList .Add(New BloodPressure With {.記録日 = xmldoc.Descendants("情報")(i).Element("記録日").Value, .最高血圧 = CInt(xmldoc.Descendants("情報")(i).Element("最高血圧").Value), .最低血圧 = CInt(xmldoc.Descendants("情報")(i).Element("最低血圧").Value)}) End With e.Item.Content = mainChart Next End Sub) End If End Sub
BloodPressureクラス型のObservalCollectionであるDataというプロパティを定義します。myBloodPressureListを戻り値とします。
Public ReadOnly Property Data() As ObservableCollection(Of BloodPressure) Get Return myBloodPressureList End Get End Property
Pivotの現在選択されている項目が変更された場合に発生する処理
RemoveHandlerステートメントで、pivot1_LoadingPivotItemイベントハンドラを削除します。myBloodPressureListオブジェクトをクリアして、動的にピボットアイテムのコンテンツをロードするイベントハンドラを実行しているDataShowプロシージャを実行します。イベントハンドラの削除とmyBloodPressureListオブジェクトのクリアを指定しておかないと、フリックした場合にデータが重複して表示されますので、注意してください。
Private Sub Pivot1_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles Pivot1.SelectionChanged RemoveHandler Pivot1.LoadingPivotItem, AddressOf Me.pivot1_LoadingPivotItem myBloodPressureList.Clear() DataShow() End Sub
アプリケーションバーメニューの「データ入力」をタップした場合の処理
DataInPage.xamlに遷移します。
Private Sub DataRecordPage(sender As Object, e As EventArgs) NavigationService.Navigate(New Uri("/DataInPage.xaml", UriKind.Relative)) End Sub
アプリケーションバーメニューの「最後のデータに移動」をタップした場合の処理
入力されているデータから要素を選択するクエリを定義します。
要素の個数から-1した値から、Math.Floorを使って、最も小さい整数を求め変数myIndexに格納します。Pivotの選択されたインデックスにmyIndexの値を指定します。これで、一番最後のページに移動します。
Private Sub LastDataRecordPage(sender As Object, e As EventArgs) Dim query = From c In xmldoc.Descendants("情報") Select c myIndex = CInt(System.Math.Floor((query.Count - 1) / 4)) Pivot1.SelectedIndex = myIndex End Sub End Class
「Chartコントロールを使ってWindows Phoneに棒グラフを表示する」サンプルプログラム