キャラクターが声で天気予報を教えてくれるアプリを作る

2014年2月26日(水)
薬師寺 国安

Bing Maps上に今日の天気予報を表示する処理

新しいTextBlockのインスタンス、myDateTextBlockとmyTenkiTextBlockオブジェクトを作成します。また、新しいImageのインスタンスmyImageオブジェクトを作成します。
加えて、新しいTextBlockのインスタンス、maxTextBlockとminTextBlockオブジェクトを作成します。

メンバー変数myInfoCountに要素の個数を格納しておきます。

新しいStackPanelのインスタンス、myTenkiStackPanelオブジェクトを作成します。
Marginに「5」を指定して余白を設けます。背景色に「Navy」を指定します。
myDataTextBlockの文字色は「Gold」、文字サイズは24、パディングは5と指定し、Textプロパティに、1ずつ増減するメンバー変数noに位置する要素の属性”date”の値を指定します。

myImageオブジェクトのSourceプロパティに1ずつ増減するメンバー変数noに位置する要素の子要素の値を指定します。

myTenkiTexBlockの文字色を「Red」とします。文字サイズには「22」を指定し、パディングに「5」、Widthに「300」と指定します。
Textプロパティには、1ずつ増減するメンバー変数noに位置する要素の子要素の値を指定します。

myTenkStackPanelにAddメソッドで、myDateTextBlock、myTenkiTextBlock、myImageオブジェクトを追加します。

maxTextBlockオブジェクトの文字色を「Beige」、文字サイズを「22」、パディングを「5」、Widthを「300」と指定し、Textプロパティに、1ずつ増減するメンバー変数noに位置する要素の子要素の子要素の最初のノード(FirstNode)を指定します。

要素の全ての子孫要素である要素で、1ずつ増減するメンバー変数noに位置する要素の子要素の子要素内の値を、変数tempResultに格納しながら以下の処理を繰り返します。

minTextBlockの文字色を「Beige」とします。文字サイズを「22」、パディングを「5」、Widthに「300」と指定し、TextプロパティにtempResultの取得したノードのインデックスが0の値を指定します。変数lowTempにtempResult.Nodes(0).ToStringを格納しておきます。

キャラクターに喋らせる内容をreadingTenkiに指定します。

新しいButtonのインスタンスnextButtonオブジェクトを作成します。Contentプロパティに「次」と指定します。
メンバー変数noの値がmyInfoCount-1と等しいか大きかった場合は、nextButtonの使用を不可とします。それ以外は、使用を可とします。

新しいButtonのインスタンスprevButtonオブジェクトを作成します。Contentプロパティに「前」と指定します。
もしメンバー変数noの値が0ならprevButtonの使用を不可とします。それ以外は使用可とします。
新しいStackPanelのインスタンスButtonStackPanelオブジェクトを作成します。Orientationに「Horizontal」と指定します。ButtonStackPanelオブジェクトにAddメソッドでnextButtonとprevButtonオブジェクトを追加します。

新しいStackPanelのインスタンスsyokoStackPanelオブジェクトを作成します。Orientationには「Horizontal」と指定します。
新しいImageのインスタンスsyokoImageオブジェクトを作成します。Widthに「141」、Heightに「274」と指定し、Sourceプロパティにソリューション・アクスプロ—ラー内のImageフォルダにある「syoko1.png」を指定します。

syokoStackPanelにAddメソッドでsyokoImageオブジェクトを追加します。
AddHandlerステートメントで、syokoImageがタップされた時のイベントハンドラを追加します。
イベントハンドラ内では、syokoVoiceタスクを実行します。

myTenkiStackPanelオブジェクトにAddメソッドでmaxTextBlock、minTextBlock、ButtonStackPanelオブジェクトを追加します。

新しいStackPanelのインスタンス、resultStackPanelオブジェクトを作成します。Orientationには「Horizontal」を指定します。
resultStackPanelオブジェクトにAddメソッドで、myTenkiStackPanel、syokoStackPanelを追加します。

MapLayer.SetPositionで、指定した緯度と経度の位置にresultStackPanelオブジェクトをセットします。
myMapにAddメソッドでresultStackPanelオブジェクトを追加します。
これで、天気予報が表示された横にキャラクターの画像が表示されます。

AddHandlerステートメントでnextButtonがクリックされた時に、nextButton_Clickイベントハンドラを実行します。
AddHandlerステートメントでprevButtonがクリックされた時に、prevButton_Clickイベントハンドラを実行します。

天気予報をキャラクターが喋るsyokoVoiceタスクを実行します。
非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

    Private Async Function DataShow() As Task
    myDateTextBlock = New TextBlock
    myTenkiTextBlock = New TextBlock
    myImage = New Image
    maxTextBlock = New TextBlock
    minTextBlock = New TextBlock
    
    myInfoCount = query.Descendants("info").Count

    myTenkiStackPanel = New StackPanel
    myTenkiStackPanel.Children.Clear()
    myTenkiStackPanel.Margin = New Thickness(5)
    myTenkiStackPanel.Background = New SolidColorBrush(Colors.Navy)
 
    With myDateTextBlock
      .Foreground = New SolidColorBrush(Colors.Gold)
      .FontSize = 24
      .Padding = New Thickness(5)
      .Text = query.Descendants("info")(no).Attribute("date").Value
    End With

    myImage.Stretch = Stretch.None
    myImage.Source = New BitmapImage(New Uri(query.Descendants("info")(no).Element("img").Value, UriKind.Absolute))

    myTenkiTextBlock.Foreground = New SolidColorBrush(Colors.Red)
    myTenkiTextBlock.FontSize = 22
    myTenkiTextBlock.Padding = New Thickness(5)
    myTenkiTextBlock.Width = 300
  
    myTenkiTextBlock.TextWrapping = TextWrapping.Wrap
    myTenkiTextBlock.Text = "【天気】=" & query.Descendants("info")(no).Element("weather").Value
    myTenkiStackPanel.Children.Add(myDateTextBlock)
 
    myTenkiStackPanel.Children.Add(myTenkiTextBlock)
    myTenkiStackPanel.Children.Add(myImage)
    maxTextBlock.Foreground = New SolidColorBrush(Colors.Beige)
    maxTextBlock.FontSize = 22
    maxTextBlock.Padding = New Thickness(5)
    maxTextBlock.Width = 300
    maxTextBlock.TextWrapping = TextWrapping.Wrap
    maxTextBlock.Text = "【最高気温】=" & query.Descendants("info")(no).Element("temperature").Element("range").FirstNode.ToString & "度"
 
    Dim lowTemp As String = String.Empty
    For Each tempResult In query.Descendants("info")(no).Elements("temperature").Elements("range")
      minTextBlock.Foreground = New SolidColorBrush(Colors.Beige)
      minTextBlock.FontSize = 22
      minTextBlock.Padding = New Thickness(5)
      minTextBlock.Width = 300
      minTextBlock.TextWrapping = TextWrapping.Wrap
      minTextBlock.Text = "【最低気温】=" & tempResult.Nodes(0).ToString & "度"
      lowTemp = tempResult.Nodes(0).ToString
    Next
 
    readingTenki = areaData & "の" & myDateTextBlock.Text & "の天気予報。" & "天気は" & query.Descendants("info")(no).Element("weather").Value & "です。" & "最高気温は" & query.Descendants("info")(no).Element("temperature").Element("range").FirstNode.ToString & "度で" & "最低気温は" & lowTemp & "度です。"
    nextButton = New Button
    nextButton.Content = "次"
    If no >= myInfoCount - 1 Then
      nextButton.IsEnabled = False
    Else
      nextButton.IsEnabled = True
    End If

    prevButton = New Button
    prevButton.Content = "前"
    If no = 0 Then
      prevButton.IsEnabled = False
    Else
      prevButton.IsEnabled = True
    End If
 
    Dim ButtonStackPanel As New StackPanel
    ButtonStackPanel.Orientation = Orientation.Horizontal
    ButtonStackPanel.Children.Add(nextButton)
    ButtonStackPanel.Children.Add(prevButton)
  
    Dim syokoStackPanel As New StackPanel
    syokoStackPanel.Orientation = Orientation.Horizontal
    Dim syokoImage As New Image
    With syokoImage
      .Width = 141
      .Height = 274
      .Source = New BitmapImage(New Uri("ms-appx:///Image/syoko1.png", UriKind.Absolute))
      .Stretch = Stretch.None
    End With
    syokoStackPanel.Children.Add(syokoImage)
 
    AddHandler syokoImage.Tapped, Async Sub()
                                    Await syokoVoice()
                                  End Sub

    myTenkiStackPanel.Children.Add(maxTextBlock)
    myTenkiStackPanel.Children.Add(minTextBlock)
    myTenkiStackPanel.Children.Add(ButtonStackPanel)
 
    Dim resultStackPanel As New StackPanel
    resultStackPanel.Orientation = Orientation.Horizontal
    resultStackPanel.Children.Add(myTenkiStackPanel)
    resultStackPanel.Children.Add(syokoStackPanel)

    MapLayer.SetPosition(resultStackPanel, New Location(CDbl(myLatitude), CDbl(myLongitude)))
    myMap.Children.Add(resultStackPanel)
 
 
   
    AddHandler nextButton.Click, AddressOf nextButton_Click
    AddHandler prevButton.Click, AddressOf prevButton_Click
    Await syokoVoice()
  End Function

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

myTenkiStackPanelを表示状態にします。

メンバー変数noがmyInfoCount-1より小さかった場合は、メンバー変数noを1ずつ加算します。[前]ボタンの使用を可能にし、DataShowタスクを実行します。
そうでない場合は、[次]ボタンの使用を不可とし、[前]ボタンの使用を可とします。メンバー変数noをmyInfoCount-1で初期化します。処理を抜けます。

非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub nextButton_Click(sender As Object, e As RoutedEventArgs)
    myTenkiStackPanel.Visibility = Xaml.Visibility.Visible
 
    If no < myInfoCount - 1 Then
      no += 1
      prevButton.IsEnabled = True
      Await DataShow()
    Else
      nextButton.IsEnabled = False
      prevButton.IsEnabled = True
      no = myInfoCount - 1
      Exit Sub
    End If

End Sub

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

myTenkiStackPanelを表示状態にします。

メンバー変数noが0か0より小さかった場合は、noを0で初期化します。[前]ボタンの使用を不可とします。
それ以外は、メンバー変数noの値を1ずつ減少させます。[次]、[前]ボタンの使用を可とします。
DataShowタスクを実行して処理を抜けます。

  Private Async Sub prevButton_Click(sender As Object, e As RoutedEventArgs)
    myTenkiStackPanel.Visibility = Xaml.Visibility.Visible
   
    If no <= 0 Then
      no = 0
      prevButton.IsEnabled = False
 
    Else
      no -= 1
      nextButton.IsEnabled = True
      prevButton.IsEnabled = True
      Await DataShow()
      Exit Sub
    End If

  End Sub

天気予報をキャラクターが音声で喋る処理

[次]、[前]ボタンを非表示にします。

MediaElement型のmyMedia変数を宣言し、MediaElement1で初期化しておきます。

音声機能へのアクセスを提供する、新しいSpeechSynthesizerのインスタンス、synthオブジェクトを作成します。

SynthesizeTextToStreamAsyncメソッドで、指定した文字列から、音声出力を非同期に生成します。

SetSourceメソッドで、指定されたストリームおよびMIME型を使用してSourceプロパティを設定します。Playメソッドで音声を再生します。

音声にどんな言語で、どのような声で喋らすかは、SpeechSynthesizerのVoiceプロパティで参照できます。下記のURLを参照してください。Japanese JA は性別が「Female」で、名前は「Haruka」という女性が読み上げるようです。
> SpeechSynthesizer.Voice | voice property

非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Function syokoVoice() As Task
    nextButton.Visibility = Xaml.Visibility.Collapsed
    prevButton.Visibility = Xaml.Visibility.Collapsed
    Dim myMedia As MediaElement = Me.MediaElement1
    Dim synth = New Windows.Media.SpeechSynthesis.SpeechSynthesizer
    Dim stream = Await synth.SynthesizeTextToStreamAsync(readingTenki)
    myMedia.SetSource(stream, stream.ContentType)
    myMedia.Play()
  End Function

キャラクターが天気予報を喋り終わった時の処理

[次]、[前]ボタンを表示します。

  Private Sub MediaElement1_MediaEnded(sender As Object, e As RoutedEventArgs) Handles MediaElement1.MediaEnded
    nextButton.Visibility = Xaml.Visibility.Visible
    prevButton.Visibility = Xaml.Visibility.Visible
  End Sub
End Class

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

  • キャラクター音声天気予報アプリのサンプル

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

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

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