データ保存・bing検索・簡易縦書き機能の実装

2012年4月10日(火)
PROJECT KySS

入力ページ(データの入力と保存、bing検索)の実装

入力ページのデザイン(xaml)

次に、入力ページを作成します。ソリューションエクスプローラーのプロジェクト名を右クリックして表示される「追加/新しい項目」で、Portrait Pageを追加します。名前は、createHaiku.xamlとします(図10)。

図10:Portrait Pageを追加する(クリックで拡大)

MainPage.xaml同様、LayoutRootのGridのBackgroundに”#FFFFFFFA”を指定し、StackPanel部分をコメントアウトします。

TextBlock、TextBox、Image、Buttonの各コントロールを、図11のようにレイアウトし、名前を付けます。

この中で、「readHaikuButton」という名前のボタンは、ImageコントロールではなくButtonコントロールを使います。このボタンは表示ページへの移動のために使うものですから、俳句が記録されていない場合は機能させてはいけません。そこで、IsEnabledにFalseを指定しておきます。スタイルは、ソリューションエクスプローラー中のApp.xamlをダブルクリックして表示されるApp.xamlのコード内で指定します(図12)。

リスト3のように、要素の子として

図11:入力ページにコントロールをレイアウトする(クリックで拡大)

図12:App.xamlの中で「readHaikuButton」のスタイルを指定する(クリックで拡大)

リスト3 App.xamlの中で指定する「readHaikuButton」のスタイルのコード。

  <Application.Resources>
    <Style x:Key="readHaikuButtonStyle" TargetType="Button">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <Grid>
              <Image x:Name="readHaikuButtonImage" Source="/DailyHaiku;component/Image/readHaikuButton.png" Stretch="Fill"/>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
</Application.Resources>

App.xamlの中にリスト3のコードを追加した直後、図13のようにボタンの画像が表示されていない場合は、「ビルド(B)/ソリューションのビルド(B)」を実行すると表示されます。

図13:App.xamlのコードがデザイナー画面に反映されていない場合はビルドする(クリックで拡大)

このページのレイアウトで注意しておきたいのは、TextBoxにテキストの回り込みを指定し、適切なフォントサイズを指定するという点です。

このアプリでは、全角1文字分が収まる幅の、縦長いTextBox内でテキストを回り込ませることによって、簡易縦書きを実現しています(図14)。つまり、1文字ずつ回転させて表示するのではなく、表示枠の幅を全角1文字分に狭めることによって、横書きのテキストを縦書きのように見せかけています。

この方法では、長音符「―」は縦書きにしても「―」のままですが、バージョンアップ時には英語俳句対応のため横書きに変更することから、このバージョンでは実機内に正しいデータを記録できればよいものとします。

図14:縦長いTextBox内でテキストを回り込ませて、簡易縦書きを実現する(クリックで拡大)

このようにして各コントロールのマージン等のプロパティを調整し、完成したデザイン・コードは、リスト4のようになります。

リスト4 入力ページのデザイン・コード(createHaiku.xaml)

<phone:PhoneApplicationPage 
  x:Class="DailyHaiku.createHaiku"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
  xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  FontFamily="{StaticResource PhoneFontFamilyNormal}"
  FontSize="{StaticResource PhoneFontSizeNormal}"
  Foreground="{StaticResource PhoneForegroundBrush}"
  SupportedOrientations="Portrait" Orientation="Portrait"
  mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
  shell:SystemTray.IsVisible="True" Language="ja-JP">
 
  <!--LayoutRoot is the root grid where all page content is placed-->
  <Grid x:Name="LayoutRoot" Background="#FFFFFFFA">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
 
    <!--ContentPanel - place additional content here-->
      <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
      <TextBlock Height="49" HorizontalAlignment="Left" Margin="20,35,0,0" Name="season" Text="季節" VerticalAlignment="Top" Width="164" Foreground="#FF73903B" Visibility="Collapsed" />
 
      <Image Height="80" HorizontalAlignment="Left" Margin="388,40,0,0" Name="rubyLAbel" Stretch="Fill" VerticalAlignment="Top" Width="28" Source="/DailyHaiku;component/Image/rubyLabel.png" />
  
      <Rectangle Height="660" HorizontalAlignment="Left" Margin="315,35,0,0" Name="rubyBoxBack" Stroke="#FF00004B" StrokeThickness="1" VerticalAlignment="Top" Width="70" Fill="White" />
  
      <TextBox Height="660" HorizontalAlignment="Left" Margin="320,35,0,0" Name="rubyBox" Text="" VerticalAlignment="Top" Width="70" TextWrapping="Wrap" Background="White" FontSize="22" BorderBrush="{x:Null}" MaxLength="19" />
 
      <Image Height="80" HorizontalAlignment="Left" Margin="264,40,0,0" Name="Image1" Stretch="Fill" VerticalAlignment="Top" Width="28" Source="/DailyHaiku;component/Image/haikuLabel.png" />
      <Rectangle Height="660" HorizontalAlignment="Left" Margin="180,35,0,0" Name="kuBoxBack" Stroke="#FF00004B" StrokeThickness="1" VerticalAlignment="Top" Width="80" Fill="White" />
      <TextBox Height="660" HorizontalAlignment="Left" Margin="186,35,0,0" Name="kuBox" Text="" VerticalAlignment="Top" Width="80" TextWrapping="Wrap" Background="White" FontSize="25" BorderBrush="{x:Null}" MaxLength="19" />
     
      <Image Height="100" HorizontalAlignment="Left" Margin="30,250,0,0" Name="searchKigoBack" Stretch="Fill" VerticalAlignment="Top" Width="100" Source="/DailyHaiku;component/Image/searchKigoBack.png" />
      <TextBlock Height="90" HorizontalAlignment="Left" Margin="40,250,0,0" Name="searchKigoSeasonName" VerticalAlignment="Top" Width="80" FontSize="72" Opacity="0.7" FontWeight="Bold" Foreground="#FF4D4DA7"/>
      <Image Height="100" HorizontalAlignment="Left" Margin="30,250,0,0" Name="searchKigo" Stretch="Fill" VerticalAlignment="Top" Width="100" Source="/DailyHaiku;component/Image/searchKigoButton.png" />
  
      <Image Height="100" HorizontalAlignment="Left" Margin="30,100,0,0" Name="saveButton" Stretch="Fill" VerticalAlignment="Top" Width="100" Source="/DailyHaiku;component/Image/saveButton.png" /> 
      <Image Height="100" HorizontalAlignment="Left" Margin="30,400,0,0" Name="selectSeasonButton" Stretch="Fill" VerticalAlignment="Top" Width="100" Source="/DailyHaiku;component/Image/selectSeasonButton.png" />
  
      <Button Height="100" Style="{StaticResource readHaikuButtonStyle}"  HorizontalAlignment="Left" Margin="30,550,0,0" Name="readHaikuButton" VerticalAlignment="Top" Width="100" IsEnabled="False" />
    </Grid>
  
  </Grid>
</phone:PhoneApplicationPage>

参照の追加とXMLの構造の確認

入力ページの処理を書く前に、これから扱うXML文書の構造を再確認しておきましょう。

このページでは、入力されたデータからXML文書を生成します。そして、メインページで選択した季節に応じたファイルにデータを書き込み、保存します。春の場合は「spring.xml」、夏の場合は「summer.xml」、秋の場合は「autumn.xml」、冬の場合は「winter.xml」です。これらのXMLの構造は、リスト5のとおりです。

リスト5 俳句を記録する XML文書の構造(季節名.xml)

<?xml version="1.0" encoding="utf-8" ?>
<俳句集 季節=”ユーザーが選択した季節名”>
  <俳句 日時=”年月日”>
    <ルビ>ルビ欄に入力されたルビやコメントなど</ルビ>
    <句>俳句</句>
  </俳句>
  <俳句>要素、繰り返し
</俳句集>

このXML データの処理には LINQ to XML を使いますので、「プロジェクト/参照の追加」からSystem.Xml.Linq を追加しておきます(図15)。

図15:LINQ to XML を使ってXMLデータを処理するためSystem.Xml.Linqを追加しておく(クリックで拡大)

入力ページのロジック(createHaiku.xaml.vb)

では、俳句の入力と保存の処理を記述していきましょう。このページでは、次の6つの処理により、入力されたデータをもとに、俳句を記録します。

(1)メインページから渡された「季節」の値を受け取る処理

メインページから渡された「季節」の値(spring、summer、autumn、winterのいずれか)を受け取ります。

(2)この入力ページがロードされた時の処理

ページがロードされた時、「季節」の値をファイル名に利用します。また、「春」「夏」「秋」「冬」のいずれかに置き換えて季語検索に利用します。
選択した季節の俳句がまだ何も記録されていない場合は、「一覧」ボタンは色を薄くして、使用不可のままにしておきます。一句でも記録されている場合は、色を濃くして、使用可にします。

(3)「保存」ボタンがタップされた時の処理

俳句欄やルビ欄に入力された文字のバイト数と文字数を比較し、全角でなければメッセージを表示します。簡易縦書きはTextBoxを全角1文字分の幅にして実現するため、半角では2文字分が横書きになってしまいます。そのため基本は全角としています。
バイト数との比較に用いているGetByteCount の構文については、MSDN内のUTF8Encoding.GetByteCount メソッドを参照してください。

必須項目の俳句が入力済みの場合は、フォルダ名(DailyHaikuData固定)とファイル名(英字季節名.xml)を指定して、XMLツリーを生成、保存します。ファイルがない場合はルート要素から新規生成し、ある場合は子要素以下を生成します。

生成したツリーを書き込んだ後、保存した旨のメッセージを表示し、データ入力ボックスを空にします。データが1件でも保存されたら、「一覧」ボタンの色を濃くして使用可にします。必須項目の俳句が入力されていない場合は、メッセージを表示して入力を促します。

(4)「一覧」ボタンがタップされた時の処理

メインページから渡された英語季節名を、表示ページに渡します。

(5)「季語検索」ボタンがタップされた時の処理

「春」「夏」「秋」「冬」のいずれかの文字列と半角スペース、「季語」を連結したものを検索キーに指定して、bing検索を実行します。

(6)「季節選択」ボタンがタップされた時の処理

メインページに戻った時に「ホームに戻ったことを示す情報」を渡します。これにより、ユーザーが一度入力ページに入ったことが明らかとなるので、メインページでは履歴をクリアする処理を実行します。前回記事「複数ページ間の連携処理」の項も、参照してください。

リスト6 入力ページのロジック・コード(createHaiku.xaml.vb)

Option Strict On
Imports System.IO.IsolatedStorage
Imports System.IO
Imports Microsoft.Phone.Tasks
Imports System.Xml.Linq
Imports System.Text

Partial Public Class createHaiku
  Inherits PhoneApplicationPage

  Public Sub New()
    InitializeComponent()
  End Sub

(1) メインページから渡された「季節」のデータを受け取る処理

  Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
    Dim param As IDictionary(Of String, String) = NavigationContext.QueryString
    season.Text = param("Name")
    MyBase.OnNavigatedTo(e)
  End Sub
 
  Dim ruby As String = String.Empty'●ルビ
  Dim ku As String = String.Empty'●俳句
  Dim myDate As String = DateTime.Today.ToString("d")'●年月日
  Dim mySeason As String = String.Empty'●季節。spring、summer、autumn、winterのいずれかが入る
  Dim kigoSeason As String = String.Empty'●春、夏、秋、冬のいずれかが入る

(2) この入力ページがロードされた時の処理

  Private Sub createHaiku_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
 
    '●表示ページへ移動するための「一覧」ボタンの色を薄くしておく。
    readHaikuButton.Opacity = 0.3
 
    '●メインページから渡された英語季節名。ファイル名に利用
    mySeason = season.Text
 
    '●日本語季節名に置き換え。季語検索に利用
    If mySeason = "spring" Then
      kigoSeason = "春"
    ElseIf mySeason = "summer" Then
      kigoSeason = "夏"
    ElseIf mySeason = "autumn" Then
      kigoSeason = "秋"
    ElseIf mySeason = "winter" Then
      kigoSeason = "冬"
    End If
 
    '●「季語検索」ボタンに季節名を表示
    searchKigoSeasonName.Text = kigoSeason
 
    '●俳句保存先のファイル名、DailyHaikuDataフォルダ内の英字季節名.xmlを指定
    Dim haikuStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim haikuFilePath As String = Path.Combine("DailyHaikuData", mySeason & ".xml")
 
    '●俳句ファイルがあれば表示ボタンを使用可とする
    If haikuStorage.DirectoryExists("DailyHaikuData") = True And haikuStorage.FileExists(haikuFilePath) = True Then
      readHaikuButton.Opacity = 1.0
      readHaikuButton.IsEnabled = True
    End If
  End Sub

(3) 「保存」ボタンがタップされた時の処理

  Private Sub saveButton_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles saveButton.Tap
 
    '●入力されたルビと俳句を代入
    ruby = rubyBox.Text
    ku = kuBox.Text
 
    '●ルビ、俳句ともに、バイト数と文字数を取得
    Dim rubyNum As Integer = Encoding.GetEncoding("utf-8").GetByteCount(ruby)
    Dim rubyLen As Integer = ruby.Length
 
    Dim kuNum As Integer = Encoding.GetEncoding("utf-8").GetByteCount(ku)
    Dim kuLen As Integer = ku.Length
 
    '●ルビ、俳句いずれかが全角でなければ、メッセージ表示
    If kuNum <> kuLen * 3 OrElse rubyNum <> rubyLen * 3 Then
      MessageBox.Show("全角で入力してください。")
      Exit Sub
    End If
 
    '●必須の俳句が入力済みの場合
    If ku <> String.Empty Then
    
      Try
        Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
        '●DailyHaikuDataフォルダがない場合は生成
        If storage.DirectoryExists("DailyHaikuData") = False Then
          storage.CreateDirectory("DailyHaikuData")
        End If
 
        '●俳句保存先のファイル名、DailyHaikuDataフォルダ内の英字季節名.xmlを指定
        Dim filePath As String = Path.Combine("DailyHaikuData", mySeason & ".xml")
 
        '●ファイルがない場合は、ルート要素から新規生成して保存
        If storage.FileExists(filePath) = False Then
          Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                                    <俳句集 季節=<%= kigoSeason %>>
                                      <俳句 日時=<%= myDate %>>
                                        <ルビ><%= ruby %></ルビ>
                                        <句><%= ku %></句>
                                      </俳句>
                                    </俳句集>
          Using myStream As IsolatedStorageFileStream = New IsolatedStorageFileStream(filePath, FileMode.Create, FileAccess.Write, storage)
            xmldoc.Save(myStream)'●XMLファイルを保存
            MessageBox.Show("今日の俳句を保存しました。")'●保存後、メッセージを表示
         
          End Using
         
        Else
 
        '●ファイルがある場合は読み込み、子要素を生成して追加
          Dim addStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
          Dim reader As StreamReader = New StreamReader(addStream)
          Dim readXmldoc As String = reader.ReadToEnd
          Dim addDoc As XElement = XElement.Parse(readXmldoc)
          Dim addXml As XElement = <俳句 日時=<%= myDate %>>
                                     <ルビ><%= ruby %></ルビ>
                                     <句><%= ku %></句>
                                   </俳句>
          addDoc.Add(addXml)
          addStream.Dispose()
          Using stream As IsolatedStorageFileStream = New IsolatedStorageFileStream(filePath, FileMode.Create, FileAccess.Write, storage)
              If storage.FileExists(filePath) = True Then
                addDoc.Save(stream)'●XMLデータを追加
                reader.Dispose()
                MessageBox.Show("今日の俳句を保存しました。")'●追加後、メッセージを表示
              End If
            End Using
          End If
          
          '●データ保存後、入力ボックス内を空にしておく
          rubyBox.Text = String.Empty
          kuBox.Text = String.Empty
 
          '●データが1件でも保存されたら「一覧」ボタンの色を濃くして使用可能にする
          readHaikuButton.Opacity = 1.0
          readHaikuButton.IsEnabled = True
        Catch
          Exit Sub
        End Try
      Else
        MessageBox.Show("俳句を入力してください。")'●必須の俳句が入力されておらず保存しようとした場合はメッセージ表示
        Exit Sub
      End If
 
   End Sub

(4) 「一覧」ボタンがタップされた時の処理

  Private Sub readHaikuButton_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles readHaikuButton.Tap
    '●メインページから渡された英語季節名を、表示ページに渡して移動する
    NavigationService.Navigate(New Uri("/haikuShow.xaml?Name=" & mySeason, UriKind.Relative))
  End Sub

(5) 「季語検索」ボタンがタップされた時の処理

  Private Sub searchKigo_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles searchKigo.Tap
    Dim myTask As New SearchTask
    '●「春」「夏」「秋」「冬」のいずれかに半角スペースと「季語」を連結して検索キーに指定
    myTask.SearchQuery = kigoSeason & " 季語"
    myTask.Show()
  End Sub

(6) 「季節選択」ボタンがタップされた時の処理

  Private Sub selectSeasonButton_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles selectSeasonButton.Tap
    '●メインページに戻った時に「ホームに戻ったことを示す情報」を渡す
    NavigationService.Navigate(New Uri("/MainPage.xaml?home=true", UriKind.Relative))
  End Sub
End Class
  • 「一日一句」プロジェクトファイル

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

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

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

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

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