時刻とともに、その日の出来事をキャラクターが音声で教えてくれるアプリを作る

2014年3月19日(水)
薬師寺 国安

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

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

リスト2 (MainWindow.xaml.vb)

‘ アプリケーションがスレッドプールを使用して作業項目を実行できるようにするクラスの含まれる、
‘ Windows.System.Threading名前空間をインポートします。

Imports Windows.System.Threading

Imports Windows.UI

‘ XML ドキュメント オブジェクト モデルのサポートを提供するクラスの含まれる、Windows.Data.Xml.Dom名前空間を
‘ インポートします。

Imports Windows.Data.Xml.Dom

‘ ファイル、フォルダおよびアプリケーションの設定を管理するクラスの含まれる、
‘ Windows.Storage名前空間をインポートします。

Imports Windows.Storage

‘ シーケンシャルアクセスストリームおよびランダムアクセスストリームに対する読み取りと書きこみをサポートするクラスの含まれる
‘ Windows.Storage.Streams名前空間をインポートします。

Imports Windows.Storage.Streams

Imports Windows.UI.Popups

‘ 最新の HTTP アプリケーションのプログラミング インターフェイスを提供するクラスの含まれる、

‘ System.Net.Http 名前空間をインポートします。

Imports System.Net.Http Public NotInheritable Class MainPage Inherits Page Private myColorInfo As String ‘ 指定した時間の間隔で、指定した優先順位に処理されるDispacherキューに統合されているタイマーを表す、新しい、 ‘ DispatcherTimerクラスのインスタンスである、myTimerメンバー変数を宣言します。

Private myTimer As New DispatcherTimer Private myFont As String ‘ キャラクタが読み上げる内容を格納する文字列型のメンバー変数readingTextを宣言します。

Private readingText As String ‘ 乱数を発生させるRandomクラス型のメンバー変数Rndを宣言します。

Private Rnd As Random Private myMonth As String Private myDate As String

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

メンバー変数myMonthに現在の月を格納しておきます。メンバー変数myDateに現在の日付を格納しておきます。
ピクチャライブラリ—のXMLサブフォルダにアクセスします。
GetFilesAsyncメソッドでフォルダ内のファイルを取得して、コレクション変数myFileに格納します。ファイルの数が0より大きかった場合は、GetFileAsyncメソッドでXMLフォルダ内のmyColor.xmlを取得し、xmldoc変数で参照します。
xmldocで参照しているファイルを、

Await xmldoc.OpenAsync(FileAccessMode.Read)

と記述して読み取り専用で開き、myStreamで参照します。StreamReaderの新しいインスタンスreaderオブジェクトを作成し、myStreamで参照しているファイルと、エンコーディングUTF8で初期化します。Reader.ReadToEndでファイルを最後まで読み取り変数resultに格納します。
XElement.Parseメソッドでresult変数の内容を文字列として読み取ります。
変数_Colorに要素の値を、変数_Fontに 要素の値を格納します。
_Color変数の値によって条件分岐を行います、RedからSalmonであった場合の処理を記述します。例えば_Colorが「Red」であった場合は、CascadingTextBlock1の文字色を「Red」に指定し、redRadioButtonにチェックを付けます。他の色の場合も同じ処理を行います。
CascadingTextBlock1のTextプロパティに現在の時刻を表示します。

CascadingTextBlock1.Text = DateTime.Now.ToString()

と指定しています、これを

CascadingTextBlock1.Text = DateTime.Now.ToString(“yyyy年MM月dd日HH時mm分ss秒”)

と指定するとエラーになります。CascadingTextBlockが日本語表示に対応していないようです。
CasCadingTextBlockのFontFamilyに_Font変数の値を指定します。myTimerのIntervalに10秒を指定します。
AddHandlerステートメントで、タイマー間隔が経過すると発生するTickイベントにイベントハンドラを指定します。

イベントハンドラ内では以下の処理を行います。
新しいRandomクラスのインスタンスRndを作成します。最少が「1」で最大が「5」のランダムな数字を発生させます。しかし最大値の「5」は含まれませんので、実際には「1~4」のランダムな数字が作成されます。
変数myRndの値で条件分岐を行います。「1~4」の間のランダムな数字に合わせてsyokoImageのSourceプロパティに、ソリューション・エクスプローラー内のImageフォルダに配置していたPNG画像を指定します。
CascadingTextBlock1に現在の時刻を表示し、キャラクタに喋らす内容をメンバー変数readingTextに格納します。
キャラクタが音声を発して喋るsyokoVoiceタスクを実行します。CascadingTextBlock1のAnimatedLoadedにTrueを指定し、

Await CascadingTextBlock1.BeginCascadingTransitionAsync()

と指定しカスケーディングトラジッションを伴って時刻の表示を開始します。
非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
    myMonth = DateTime.Now.Month.ToString
    myDate = DateTime.Now.Day.ToString
    Dim result As String
    Dim _Color As String
    Dim _Font As String = String.Empty
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("XML", CreationCollisionOption.OpenIfExists)
    Dim myFile = Await mySubFolder.GetFilesAsync()
    If myFile.Count > 0 Then
      Dim xmldoc As StorageFile = Await mySubFolder.GetFileAsync("myColor.xml")
      Using myStream As IRandomAccessStream = Await xmldoc.OpenAsync(FileAccessMode.Read)
        Using reader As StreamReader = New StreamReader(myStream.AsStream, System.Text.Encoding.UTF8)
          result = reader.ReadToEnd
        End Using
      End Using
      Dim doc As XElement = XElement.Parse(result)
      _Color = doc.Descendants("Color").Value
      _Font = doc.Descendants("Font").Value
      Select Case _Color
        Case "Red"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Red)
          redRadioButton.IsChecked = True
        Case "Blue"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Blue)
          blueRadioButton.IsChecked = True
        Case "Green"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Green)
          greenRadioButton.IsChecked = True
        Case "White"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.White)
          whiteRadioButton.IsChecked = True
        Case "Yellow"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Yellow)
          goldRadioButton.IsChecked = True
        Case "Pink"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Pink)
          pinkRadioButton.IsChecked = True
        Case "Salmon"
          CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Salmon)
          salmonRadioButton.IsChecked = True
      End Select
    End If
    CascadingTextBlock1.Text = DateTime.Now.ToString()
    If _Font <> String.Empty Then CascadingTextBlock1.FontFamily = New FontFamily(_Font)
    myTimer.Interval = New TimeSpan(0, 0, 10)
 
    AddHandler myTimer.Tick, Async Sub()
                              Rnd = New Random
                              Dim myRnd = Rnd.Next(1,5)
                              Select Case myRnd
                                Case 1
                                  syokoImage.Source = New BitmapImage(New Uri("ms-appx:///Image/syoko1.png", UriKind.Absolute))
                                  Exit Select
                                Case 2
                                  syokoImage.Source = New BitmapImage(New Uri("ms-appx:///Image/syoko2.png", UriKind.Absolute))
                                  Exit Select
                                Case 3
                                  syokoImage.Source = New BitmapImage(New Uri("ms-appx:///Image/syoko3.png", UriKind.Absolute))
                                  Exit Select
                                Case 4
                                  syokoImage.Source = New BitmapImage(New Uri("ms-appx:///Image/syoko4.png", UriKind.Absolute))
                                  Exit Select
                              End Select
                              CascadingTextBlock1.Text = DateTime.Now.ToString
                              readingText = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & "です。"
                              Await syokoVoice()
                              CascadingTextBlock1.AnimateOnLoaded = True
                              Await CascadingTextBlock1.BeginCascadingTransitionAsync()
                            End Sub
    myTimer.Start()
  End Sub

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

タイマーを停止します。

  Protected Overrides Sub OnNavigatedFrom(e As Navigation.NavigationEventArgs)
    myTimer.Stop()
    MyBase.OnNavigatedFrom(e)
  End Sub

redRadioButtonがチェックされた時の処理

CascadingTextBlock1の文字色を「Red」に指定します。メンバー変数myColorInfoに「Red」を格納します。
これ以後のpinkRadioButton_Checkedまでの処理も、この処理と同じですので、説明は省略します。

  Private Sub redRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles redRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Red)
    myColorInfo = "Red"
  End Sub
 
  Private Sub blueRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles blueRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Blue)
    myColorInfo = "Blue"
  End Sub
 
  Private Sub greenRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles greenRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Green)
    myColorInfo = "Green"
  End Sub
 
  Private Sub whiteRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles whiteRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.White)
    myColorInfo = "White"
  End Sub
 
  Private Sub goldRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles goldRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Yellow)
    myColorInfo = "Yellow"
  End Sub

  Private Sub salmonRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles salmonRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Salmon)
    myColorInfo = "Salmon"
  End Sub
 
  Private Sub pinkRadioButton_Checked(sender As Object, e As RoutedEventArgs) Handles pinkRadioButton.Checked
    CascadingTextBlock1.Foreground = New SolidColorBrush(Colors.Pink)
    myColorInfo = "Pink"
  End Sub

[保存]アイコンがタップされた時の処理

XElement型の変数colorXmlを宣言し、埋め込み式の構文である を用いて要素の値に、myColorInfo、の値にmyFontを指定して格納します。
XElement.ParseメソッドでcolorXml.ToStringの内容を文字列として読み込みます。変数resultにXMLの内容を格納します。
ピクチャライブラリ—内のXMLサブフォルダにアクセスします。CreatFileAsyncメソッドでXMLサブフォルダ内にmyColor.xmlファイルを作成し、myXmlFileで参照します。
OpenAsyncメソッドでmyXmlFileを読み込み書きこみ専用で開き、変数myStreamで参照します。
myStreamオブジェクトで初期化された新しいDataWriterのインスタンスwriterを作成します。DataWriterクラスは、データを出力ストリームに書きこむクラスです。ユニコードにUtf8を指定し、WriteStringメソッドでXMLのデータを格納しているresult変数の内容を書きこみます。StoreAsyncメソッドでパッキングストアにバッファーのデータをコミットします。
保存した旨のメッセージを表示します。

  Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
    Dim colorXml As XElement = <ColorInfo><Color><%= myColorInfo %></Color><Font><%= myFont %></Font></ColorInfo>
    Dim xmldoc As XElement = XElement.Parse(colorXml.ToString)
    Dim result As String = xmldoc.ToString
    Dim myStorageFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myStorageFolder.CreateFolderAsync("XML", CreationCollisionOption.OpenIfExists)
    Dim myXmlFile As StorageFile = Await mySubFolder.CreateFileAsync("myColor.xml", 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
    Dim message As New MessageDialog("設定を保存しました。")
    Await message.ShowAsync
  End Sub

CountDownControlが最後までカウントされた時の処理

syokoImageを表示状態にします。CountDownControlを非表示にします。

  Private Sub CountdownControl1_CountdownComplete(sender As Object, e As RoutedEventArgs) Handles CountdownControl1.CountdownComplete
    syokoImage.Visibility = Xaml.Visibility.Visible
    CountdownControl1.Visibility = Xaml.Visibility.Collapsed
  End Sub

フォントを表示しているComboBoxからフォントが選択された時の処理

変数myComboItemにComboBoxから選択された項目名を格納します。
選択された項目名によって、条件分岐を行います。例えば、「文字2」が選択された場合は、メンバー変数myFontにソリューション・エクスプローラー内のFontフォルダにあるフォント名を次のように指定します。

myFont = "Font/YournameS7FinancialFull.ttf#YournameS7FinancialFull"

Fontフォルダの前に”ms-appx:///”の記述はこの場合不要です。あっても問題はありません。「文字5」までに対して同様の処理を行います。
CascadingTextBlock1のFontFamilyにmyFontを指定すると、指定されたフォントで時刻が表示されます。

  Private Sub fontComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles fontComboBox.SelectionChanged
    Dim myComboItem = DirectCast(fontComboBox.SelectedItem, ComboBoxItem).Content.ToString
    Select Case myComboItem
      Case "文字1"
        myFont = "Font/meiryob.ttc#meiryob"
        Exit Select
      Case "文字2"
        myFont = "Font/YournameS7FinancialFull.ttf#YournameS7FinancialFull"
        Exit Select
      Case "文字3"
        myFont = "Font/YournameS12Financial.ttf#YournameS12Financial"
        Exit Select
      Case "文字4"
        myFont = "Font/YournameL12SquareRoundFull.ttf#YournameL12SquareRoundFull"
        Exit Select
      Case "文字5"
        myFont = "Font/YournameS7FinancialHalf.ttf#YournameS7FinancialHalf"
        Exit Select
    End Select
    CascadingTextBlock1.FontFamily = New FontFamily(myFont)
  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

CascadingTextBlockのアニメ—ションが完了した場合の処理

「今日は何の日」APIを使用します。下記のURLを参照してください。
> 「今日は何の日」API

変数whatsDayUriに

String.Format("http://www.mizunotomoaki.com/wikipedia_daytopic/api.cgi/{0}", myMonth & "/" & myDate)

と指定します。引数に現在の月(myMonth)と現在の日(myDate)をスラッシュ(/)で連結して指定します。
新しいHttpClientのインスタンスmyHttpClientオブジェクトを作成します。
GetStringAsyncメソッドで、指定したURIにGET要求を送信し、非同期処理で応答本体を文字列として取得し、変数resultに格納します。
XElement.Parseメソッドで変数resultの値を文字列として読み込みます。
変数queryにインデックスが0番目の要素の子要素要素の子要素の値を格納します。インデックスが0番目を指定しているには、「今日は何の日」APIで取得されるデータは非常に多く存在するため、ここでは1件のデータを表示させるために、インデックス0番目、つまり一番初めのデータだけを選択しています。
MessageTextBlockにquery変数の内容を表示します。

  Private Async Sub CascadingTextBlock1_CascadeCompleted(sender As Object, e As EventArgs) Handles CascadingTextBlock1.CascadeCompleted
    Dim whatsDayUri = String.Format("http://www.mizunotomoaki.com/wikipedia_daytopic/api.cgi/{0}", myMonth & "/" & myDate)
    Dim myHttpClient As New HttpClient
    Dim result = Await myHttpClient.GetStringAsync(whatsDayUri)
    Dim xmldoc As XElement = XElement.Parse(result)
    Dim query = xmldoc.Descendants("kinenbi_detail")(0).Elements("item").Elements("description").Value
    MessageTextBlock.Text = "今日は【" & query & "】の日です。"
  End Sub
End Class

今回はここまでです。また次回の記事でお会いしましょう。

  • 時刻とともに、その日に起こった出来事をキャラクターが教えてくれるアプリ

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

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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