画面上を流れる数字を暗算して正解を求めるアプリを作ろう(その2)

2014年1月28日(火)
薬師寺 国安

次に、ソリューション・エクスプローラー内のInputAnswerPage.xamlを展開して表示される、InputAnswerPage.xaml.vbをダブルクリックしてリスト5のコードを記述します。

ロジックコードを記述する

リスト5 (InputAnswerPage.xaml.vb)

Imports Windows.Storage

‘ シーケンシャルアクセスストリームおよびランダムアクセスストロームに対する読み取りと書き込みをサポートするクラスの含まれる、 ‘ Windows.Storage.Streams名前空間をインポートします。 Imports Windows.Storage.Streams Imports Windows.UI.Popups Public NotInheritable Class InputAnswerPage Inherits Page Private myPoint As Integer Private readingText As String

ページがアクティブになった時の処理

答えを入力するTextBoxにフォーカスを移します。レベルを表示します。

  Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
    TextBox1.Focus(Windows.UI.Xaml.FocusState.Keyboard)
    levelTextBlock.Text = myLevel
    MyBase.OnNavigatedTo(e)
  End Sub

[判定]ボタンがタップされた時の処理

答えが入力されていない場合は、警告を出して処理を抜けます。答えが入力されていた場合は、変数nenTukiHiに現在の年月日を格納します。答えを入力する欄に、「正解!」または「不正解!」と表示されていた場合は、答えの入力欄をクリアします。スコアの表示も0にします。
TextBox1に入力された値と、ランダムな数値の合計を格納している、モジュール変数SumTotalが同じであった場合は、答えの入力欄に「正解です!」と表示し、スコアを表示します。そして、キャラクタで「正解!」と喋らすsyokoVopiceタスクを実行します。正解で無い場合は「不正解です。」と表示し、音声を喋らすsyokoVoiceタスクを実行します。
変数saveXmlに現在の年月日時間分秒.xmlという名前のファイル名を格納します。
ピクチャライブラリ—のMentalArithmeticXMLフォルダーにアクセスします。
XElement型の変数resultXmlを宣言し、埋め込み式の構文である を用いて要素の値に、nenTukiHi、の値にketaValue、の値にspeedValue、の値に、mojiSizeValue、の値に、bacGroudnValue、の値に、leveltextBlock.Textの値、の値にmyPointを指定して格納します。
XElement.ParseメソッドでresulyXml.ToStringの内容を文字列として読み込みます。
変数resultに読み込んだXMLを格納します。
CreaterFileAsyncメソッドでピクチャライブラリ—のサブフォルダーMentalArithmeticXML内に、現在の年月日時間分秒.xmlを作成し、変数myXmlFileで参照します。
Await myXmlFile.OpenAsync(FileAccessMode.ReadWrite)
でXMLファイルを読み込みと書き込み専用で開き、myStream変数で参照します。
データを出力ストリームに書き込む新しいDataWriterのインスタンスwriterオブジェクトを作成します。これはmyStreamで初期化されています。
エンコードにUTF-8を指定し、WriteStringメソッドでxmldocの内容を出力ストリームに書き込みます。StoreAsyncメソッドで、パッキングストアにバッファーのデータをコミットします。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub hanteiButton_Click(sender As Object, e As RoutedEventArgs) Handles hanteiButton.Click
    If TextBox1.Text = String.Empty Then
      Dim myMessage As New MessageDialog("解答を入力してください。")
      Await myMessage.ShowAsync
      Exit Sub
    Else
      Dim nenTukiHi = DateTime.Now.ToString("yyyy年MM月dd日")
      If TextBox1.Text = "正解!" OrElse TextBox1.Text = "不正解!" Then
        TextBox1.Text = String.Empty
        scoreTextBlock.Text = "0"
        TextBox1.Focus(Windows.UI.Xaml.FocusState.Keyboard)
        Exit Sub
      End If
    
      If CInt(TextBox1.Text).Equals(SumTotal) Then
        TextBox1.Text = "正解です。"
        readingText = "正解です。せんせきを保存しました。"
        myPoint = myLevel * calcNo
        scoreTextBlock.Text = myPoint
        myStackPanel.Visibility = Windows.UI.Xaml.Visibility.Visible
        Await syokoVoice()
      Else
        TextBox1.Text = "不正解です。"
        readingText = "残念。不正解です。"
        Await syokoVoice()
        scoreTextBlock.Text = "0"
        Exit Sub
      End If
      Dim saveXml = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".xml"
      Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
      Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("MentalArithmeticXML", CreationCollisionOption.OpenIfExists)
      Dim resultXml As XElement = <結果><情報><年月日><%= nenTukiHi %></年月日><桁数><%= ketaValue %></桁数><速度><%= speedValue %></速度><文字サイズ><%= mojiSizeValue %></文字サイズ><背景><%= backGroundValue %></背景><レベル><%= levelTextBlock.Text %></レベル><スコア><%= myPoint %></スコア></情報></結果>
      Dim xmldoc As XElement = XElement.Parse(resultXml.ToString)
      Dim result = xmldoc.ToString
      Dim myXmlFile As StorageFile = Await mySubFolder.CreateFileAsync(saveXml, CreationCollisionOption.ReplaceExisting)
      Using myStream As IRandomAccessStream = Await myXmlFile.OpenAsync(FileAccessMode.ReadWrite)
        Dim writer As DataWriter = New DataWriter(myStream)
        writer.UnicodeEncoding = UnicodeEncoding.Utf8
        writer.WriteString(result)
        Await writer.StoreAsync
      End Using
    End If
  End Sub

結果をキャラクターが音声で喋る処理

MediaElement型のmyMedia変数を宣言し、MediaElement1で初期化しておきます。
音声機能へのアクセスを提供する、新しいSpeechSynthesizerのインスタンス、synthオブジェクトを作成します。
SynthesizeTextToStreamAsyncメソッドで、指定した文字列から、音声出力を非同期に生成します。
SetSourceメソッドで、指定されたストリームおよびMIME型を使用してSourceプロパティを設定します。Playメソッドで音声を再生します。
音声にどんな言語で、どのような声で喋らすかは、SpeechSynthesizerのVoiceプロパティで参照できます。下記のURLを参照してください。
> SpeechSynthesizer.Voice | voice property

上記URLを見るとJapanese JA は性別が「Female」で、名前は「Haruka」という女性が読み上げるようです。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Function syokoVoice() As Task
    Dim myMedia As MediaElement = Me.MediaElement1
    Dim synth = New Windows.Media.SpeechSynthesis.SpeechSynthesizer
    Dim stream = Await synth.SynthesizeTextToStreamAsync(readingText)
    myMedia.SetSource(stream, stream.ContentType)
    myMedia.Play()
  End Function
End Class

次に、ソリューション・エクスプローラー内のResultShowPage.xamlを展開して表示される、ResultShowPage.xaml.vbをダブルクリックしてリスト6のコードを記述します。

ロジックコードを記述する

リスト6 (ResultShowPage.xaml.vb)

Imports Windows.Storage
Imports Windows.Storage.Streams

‘ DataInfoクラスに文字列型の「年月日」、「桁数」、「速度」、「文字サイズ」、「背景」、「レベル」、「スコア」プロパティ
‘ を定義しておきます。

Public Class DataInfo
  Public Property 年月日 As String
  Public Property 桁数 As String
  Public Property 速度 As String
  Public Property 文字サイズ As String
  Public Property 背景 As String
  Public Property レベル As String
  Public Property スコア As String
End Class

Public NotInheritable Class ResultShowPage
  Inherits Page

‘ 要素の厳密に型指定された読み取り専用のコレクションを表す、IReadOnlyCollection(Of IStorageFile)型の
‘ メンバー変数readFilesを宣言します。
  Private readFiles As IReadOnlyCollection(Of IStorageFile)

ページがアクティブになった時の処理

ピクチャライブラリ—のMentalArithmeticXMLフォルダーにアクセスします。フォルダー内のファイルをGetFilesAsyncメソッドで取得し、コレクション変数readFilesに格納します。
新しいDataInfoクラスのリストであるmyDataListオブジェクトを作成します。
取得したファイル内のファイルを変数resultに格納しながら、以下の処理を繰り返します。GetFileAsyncメソッドでresult.Nameで指定したファイルを取得し、変数docで参照します。
Await doc.OpenAsync(FileAccessMode.Read)
と指定して、ファイルのランダムアクセスストリームを開きます。StreamReaderでファイルを読み込みReadToEndでファイルを読み取り変数myResultに格納します。
XElement.ParseメソッドでmyResultに格納されているXMLを文字列として読み込みます。
Descendantsメソッドで全ての子孫要素、情報要素内の値を、変数xmlResultに格納しながら以下の処理を行います。
DataInfoクラスの「年月日」プロパティに要素の値を、「桁数」プロパティに要素の値を、「速度」プロパティに要素の値を、「文字サイズ」プロパティに要素の値を、「背景」プロパティに要素の値を、「レベル」プロパティに要素の値を、「スコア」プロパティに要素の値を指定して、Addメソッドで、myDataListオブジェクトに追加します。GridView1のItemsSourceプロパティにDataListオブジェクトを指定すると、保存していた戦績の一覧が表示されます。
AddHandlerステートメントで、GridViewが読み込まれた時に、GridView1_Loadedイベントハンドラを実行します。

  Protected Overrides Async Sub OnNavigatedTo(e As NavigationEventArgs)
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("MentalArithmeticXML", CreationCollisionOption.OpenIfExists)
    readFiles = Await mySubFolder.GetFilesAsync
 
    Dim myDataList As New List(Of DataInfo)
    Dim myResult As String = String.Empty
    For Each result In readFiles
      Dim doc As StorageFile = Await mySubFolder.GetFileAsync(result.Name)
 
      Using myStream As IRandomAccessStream = Await doc.OpenAsync(FileAccessMode.Read)
        Using reader As StreamReader = New StreamReader(myStream.AsStream, System.Text.Encoding.UTF8)
          myResult = reader.ReadToEnd
        End Using
      End Using
      Dim xmldoc As XElement = XElement.Parse(myResult)
   
      For Each xmlResult In From c In xmldoc.Descendants("情報") Select c
        With myDataList
          .Add(New DataInfo With {.年月日 = xmlResult.Element("年月日").Value, _
                                  .桁数 = xmlResult.Element("桁数").Value, _
                                  .速度 = xmlResult.Element("速度").Value, _
                                  .文字サイズ = xmlResult.Element("文字サイズ").Value, _
                                  .背景 = xmlResult.Element("背景").Value, _
                                  .レベル = xmlResult.Element("レベル").Value, _
                                  .スコア = xmlResult.Element("スコア").Value})
        End With
      Next
    Next
    GridView1.ItemsSource = myDataList
    AddHandler GridView1.Loaded, AddressOf Me.GridView1_Loaded
    MyBase.OnNavigatedTo(e)
  End Sub

GridView1が読み込まれた時の処理

最も新しく追加したGridViewまでScrollIntoViewメソッドでスクロールして、そのデータを選択状態にします。この状態なら、キーボードの「←」や「→」キーで選択ができます。Windows ストアの審査では、このような場合は、キーボードで選択できないとリジェクトの原因になりますので、注意してください。

  Private Sub GridView1_Loaded(sender As Object, e As Object)
    Dim _myIndex As Integer = 0
    _myIndex = readFiles.Count - 1
    If _myIndex < 0 Then
      Exit Sub
    Else
      GridView1.Focus(Windows.UI.Xaml.FocusState.Keyboard)
      GridView1.SelectedIndex = _myIndex
      GridView1.ScrollIntoView(GridView1.SelectedItem)
    End If
  End Sub
End Class

これで2回にわたってお届けした動体視力暗算アプリの解説は終了です。それではまた次回の記事でお会いしましょう。

  • 動体視力暗算アプリ

    『Windows 8.1+Visual Studio 2013によるWindows ストア・アプリ開発実例集』 第4回のサンプルプログラムです。
薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています