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

2012年4月10日(火)
PROJECT KySS

表示ページ(簡易縦書き表示)の実装

最後に、表示ページを作成します。

このページは、メインページで選択された季節の俳句が一句でも記録されていた場合、入力ページにある「一覧」ボタンがタップされた時に、表示します。その際、入力ページから英語季節名が渡されるので、「英語季節名.xml」のデータを表示します。

このページの処理のポイントは、スクリーンの右から左へと表示する簡易縦書き表示です。コンテンツが一画面を超える場合の左右フリックによる表示を実現するために、Panoramaコントロールを使います。ソリューションエクスプローラーのプロジェクト名を右クリックして表示される「追加/新しい項目」でPanorama Pageを追加してください。名前は、haikuShow.xamlとします(図16)。

図16:左右フリックでコンテンツ全体を表示できるPanorama Pageを追加する(クリックで拡大)

表示ページのデザイン(xaml)

まず、MainPage.xaml同様、LayoutRootのGridのBackgroundに” #FFFFFFFA”を指定します。

図16:左右フリックでコンテンツ全体を表示できるPanorama Pageを追加する(クリックで拡大)

図17のように、計算した座標値を格納しておくためのTextBlock コントロールを1個配置します、その前面にPanorama コントロールを配置し、その中にCanvas コントロールを1個、配置します。

ページ送り用のButtonを3個配置し、名前を付けます。「forwardButton」は1つ前の句を表示し、「backButton」は次の句を表示し、「headButton」は1句目を表示します。これらのボタンには画像を貼り、先の入力ページの「一覧」ボタン同様、App.xamlの中にスタイルを追加します。

図17:表示ページにコントロールをレイアウトした(クリックで拡大)

このアプリでは、俳句の数によって、表示に必要な領域の幅が変わります。また、画面の左上隅から表示領域の左上隅までの距離も変わります。登録された句が増えるほど、表示領域の幅は広くなり、画面の左上隅からの距離も長くなります。

そこで、表示領域の幅を計算してCanvas コントロールに適用し、これをその祖先であるPanorama コントロールの Width プロパティに一方向でバインドすることにより、Panoramaの幅を変えます。

また、画面の左上隅原点からコンテンツの左上隅までの左マージンを算出してTextBlock に格納し、この値を Panorama コントロールの Marginプロパティに一方向でバインドします。

図17のようにレイアウトした各コントロールのプロパティを調整し、完成したデザイン・コードは、リスト7になります。

リスト7 表示ページのデザイン・コード(haikuShow.xaml)

<phone:PhoneApplicationPage 
  x:Class="DailyHaiku.haikuShow"
  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:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
  mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800"
  FontFamily="{StaticResource PhoneFontFamilyNormal}"
  FontSize="{StaticResource PhoneFontSizeNormal}"
  Foreground="{StaticResource PhoneForegroundBrush}"
  SupportedOrientations="Portrait"  Orientation="Portrait"
  shell:SystemTray.IsVisible="False" Language="ja-JP" IsEnabled="True">
 
  <!--LayoutRoot contains the root grid where all other page content is placed-->
  <Grid x:Name="LayoutRoot" Background="#FFFFFFFA">
  
    <Image Height="80" HorizontalAlignment="Left" Margin="15,670,0,0" Name="createHaikuPage" Stretch="Fill" VerticalAlignment="Top" Width="80" Source="/DailyHaiku;component/Image/CreateButton.png" />
    <Button Height="80" Style="{StaticResource BackButtonStyle}"  HorizontalAlignment="Left" Margin="110,670,0,0" Name="backButton" VerticalAlignment="Top" Width="80" IsEnabled="False" />
    <Button Style="{StaticResource HeadButtonStyle}"  Height="80" HorizontalAlignment="Left" Margin="200,670,0,00" Name="headButton" VerticalAlignment="Top" Width="80" IsEnabled="False" />
    <Button Style="{StaticResource ForwardButtonStyle}"  Height="80" HorizontalAlignment="Left" Margin="290,670,0,00" Name="forwardButton" VerticalAlignment="Top" Width="80" IsEnabled="False" />
    <Image Height="80" HorizontalAlignment="Left" Margin="385,670,0,0" Name="toMainPage" Stretch="Fill" VerticalAlignment="Top" Width="80" Source="/DailyHaiku;component/Image/toMainPageButton.png" />
  
    <TextBlock Height="20" Name="TextBlock1" />
    <controls:Panorama Name="Panorama1" Height="660" VerticalAlignment="Top" Width="{Binding Path=Width, Mode=OneWay, ElementName=Canvas1}" Margin="{Binding Path=Margin, Mode=OneWay, ElementName=TextBlock1}">
      <controls:PanoramaItem>
        <Grid>
          <Canvas Name="Canvas1" Margin="0,0,0,0"></Canvas>
        </Grid>
      </controls:PanoramaItem>
    </controls:Panorama>
  
  </Grid>
</phone:PhoneApplicationPage>

このページでレイアウトしたButtonコントロールには、前掲の図12の手順に従って、スタイルを指定してください。App.xamlのコード内の要素の子としてリスト8のように、

リスト8  App.xaml内に追加するスタイルのコード(App.xaml)

<Style x:Key="readHaikuButtonStyle" TargetType="Button">
  ~略(前掲のリスト3で、readHaikuButtonStyleを設定済み)~
    <Style x:Key="BackButtonStyle" TargetType="Button">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <Grid>
              <Image x:Name="BackButtonImage" Source="/DailyHaiku;component/Image/BackButton.png" Stretch="Fill"/>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
 
    <Style x:Key="HeadButtonStyle" TargetType="Button">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <Grid>
              <Image x:Name="HeadButtonImage" Source="/DailyHaiku;component/Image/HeadButton.png" Stretch="Fill"/>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
 
    <Style x:Key="ForwardButtonStyle" TargetType="Button">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Button">
            <Grid>
              <Image x:Name="HeadButtonImage" Source="/DailyHaiku;component/Image/ForwardButton.png" Stretch="Fill"/>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

表示ページのロジック(haikuShow.xaml.vb)

では、俳句を表示する処理を記述していきます。このページでは、次の6つの処理を実装します。

(1)この俳句表示ページがロードされた時の処理
ページがロードされた時、入力ページから渡された季節名のXML 文書ファイルを読み込み、これを走査して要素のクエリを取得します。それぞれのデータの行数(句の数)と字数を計算し、句の数だけ TextBlockを生成します。Widthはフォントサイズとし、TextWrappingを指定して、簡易縦書きを実現します。生成した各TextBlockは Canvas要素に追加します。
行数と行間および、最終行と1行目の間の余白の幅を利用して 表示領域の幅をもとめ、 Canvas コントロールに適用します。この値をPanorama コントロールの Width プロパティにバインドすることで幅を広げます。
Panorama コントロールの左マージンを計算して一度ダミーの TextBlock に格納し、これをPanorama コントロールの Marginプロパティにバインドして表示位置を調整します。
(2)「一句後へ」ボタンがタップされた時の処理
(3)「一句目へ」ボタンがタップされた時の処理
(4)「一句前へ」ボタンがタップされた時の処理
俳句は一画面に6句を表示します。7句以上になると画面に収まりきらないので、ユーザーが確認しやすいように、これらの3個のボタンで、前の句や後の句に送ったり、一句目にジャンプしたりできるようにします。
各ボタンをタップした時、左マージンを計算してPanoramaコントロールの位置を調整します。「一句後へ」ボタンを繰り返しタップして最終句が画面左端に達した時や、「一句前へ」ボタンを繰り返しタップして一句目が画面右端に達した時の処理も書きます。
(5)「俳句入力」ボタンがタップされた時の処理
「俳句入力」ボタンがタップされた時は、入力ページに戻って、俳句を追加できるようにします。その際、最新のエントリに移動させます。
※GoBack メソッドの構文については、MSDN内を参照してください。
→参照:
(6)「季節選択」ボタンがタップされた時の処理
メインページに戻った時に「ホームに戻ったことを示す情報」を渡し、メインページでは履歴をクリアする処理を実行します。前回記事「複数ページ間の連携処理」の項を参照してください。

なお、このアプリでは1行ずつの簡易縦書き表示を実装していますが、短歌のような複数行にまたがる表示を実装する方法については、MSDNコードレシピ中の「日本語の詩句を、絵巻物のように縦書きで表示するには」を参照してください。
→参照:[XAML/VB] 日本語の詩句を、絵巻物のように縦書きで表示するには (msdn)

以上を実装したプログラムはリスト9のようになります。

リスト9 表示ページのロジック・コード(haikuShow.xaml.vb)

Option Strict On
Imports System.IO.IsolatedStorage
Imports System.IO
Imports System.Xml.Linq

Partial Public Class haikuShow
  Inherits PhoneApplicationPage

  Public Sub New()
    InitializeComponent()
  End Sub
 
  Dim readSeason As String = String.Empty'●入力ページから渡された英語季節名。ファイル名指定に利用

  Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
    Dim param As IDictionary(Of String, String) = NavigationContext.QueryString
    readSeason = param("Name")
    MyBase.OnNavigatedTo(e)
  End Sub
 
  Dim myFontSize As Integer = 25 '●文字サイズ 
  Dim myLineHeight As Integer = 80 '●行間 
  Dim edge As Integer = 80 '●最終行と1行目の間のマージン 
  Dim myMargin, panoramaWidth As Integer

(1) この俳句表示ページがロードされた時の処理

  Private Sub haikuShow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
  
    Canvas1.Children.Clear()'●Canvas要素の中の、各俳句を表示するTextBlockを一度消去
    
    forwardButton.Opacity = 0.3'●「一句前へ」ボタンの色を薄くしておく
    headButton.Opacity = 0.3'●「一句目へ」ボタンの色を薄くしておく
    backButton.Opacity = 0.3'●「一句後へ」ボタンの色を薄くしておく
 
    Dim itemDoc As XDocument
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("DailyHaikuData", readSeason & ".xml")'●DailyHaikuDataフォルダ内の英字季節名.xmlを指定

    '●俳句ファイルがあればデータを読み込む
    If storage.FileExists(filePath) = True Then
      Dim myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
      Dim reader As StreamReader = New StreamReader(myStream, System.Text.Encoding.UTF8)
      Dim readXmldoc As String = reader.ReadToEnd
      itemDoc = XDocument.Parse(readXmldoc)
      reader.Close()
      myStream.Close()
   
      '●<句>要素のクエリを取得
      Dim myDataQuery As IEnumerable(Of XElement) = From c In itemDoc.Descendants("句") Select c
 
      '●俳句を表示するTextBlockの生成とレイアウト
      Dim myAllLines As Integer = 0'●全句数を初期化
      myAllLines = myDataQuery.Count() '●句数(行数) 
      Dim myLine As Integer = 1 '●何句目かを代入(行目) 
    
      '●各俳句について処理を繰り返す
      For Each myResult In myDataQuery
        '●俳句が6句までで、1ページ内に収まる場合は、最大表示句数を6句とする
        If myAllLines < 7 Then
          myAllLines = 6
        End If
 
        '●各句の行の左上隅までの左マージンを算出
        Dim layoutLeft As Integer = (myAllLines - myLine) * myLineHeight
 
        '●TextBlockを生成して、レイアウト位置とデザインを設定
        Dim myTextBlock As New TextBlock
        With myTextBlock
          .Text = myResult.Value '●一句ずつ俳句データを表示
          .FontSize = myFontSize
          .Width = myFontSize'●簡易縦書きのためTextBlockの幅はフォントサイズで固定
          .Height = 660
          .TextWrapping = TextWrapping.Wrap'●簡易縦書きのためテキストを折り返し
          .Margin = New Thickness(layoutLeft, 0, 0, 0)'●算出した左マージンを適用
          .Foreground = New SolidColorBrush(Colors.Black)
          .IsHitTestVisible = False'●タップに反応しないようにしておく
        End With
        Canvas1.Children.Add(myTextBlock)'●Canvasに生成したTextBlockを追加
        myLine = myLine + 1'●追加処理を句の数だけ繰り返し
      Next
   
      '●全体のレイアウト調整
      panoramaWidth = myAllLines * myLineHeight + edge '●Panoramaに適用する幅を算出
      Canvas1.Width = panoramaWidth'●算出した幅をあらかじめCanvasに適用
 
      myMargin = 480 - panoramaWidth + edge'●実機の解像度 d:DesignWidth="480"から左マージンを算出
    
      TextBlock1.Margin = New Thickness(myMargin, 0, 0, 0) '●算出したマージンをあらかじめダミーのTextBlock1に適用
 
      '●俳句が7句以上で、1ページ内に収まらない場合、3個の行送りボタンを表示
      If myAllLines > 6 Then
        forwardButton.IsEnabled = True
        forwardButton.Opacity = 1
        headButton.IsEnabled = True
        headButton.Opacity = 1
        backButton.IsEnabled = True
        backButton.Opacity = 1
      End If
    Else
      Exit Sub
    End If
  End Sub

(2) 「一句後へ」ボタンがタップされた時の処理

  Private Sub backButton_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles backButton.Tap
    '●他の2個のボタンを使用可にする
    forwardButton.IsEnabled = True
    forwardButton.Opacity = 1
    headButton.IsEnabled = True
    headButton.Opacity = 1
    myMargin = myMargin + myLineHeight'●左マージンに行間を追加する
    If myMargin > -edge Then'●最終句が画面左端に達した時の処理
      TextBlock1.Margin = New Thickness(0, 0, 0, 0)'●表示領域の左マージンを0にする
      backButton.IsEnabled = False'●「一句後へ」ボタンは使用不可にする
      backButton.Opacity = 0.3
    Else'●最終句が画面左端に達するまでの処理
      TextBlock1.Margin = New Thickness(myMargin, 0, 0, 0)'●「一句後へ」ボタンをタップする都度算出した左マージンを適用する
    End If
  End Sub

(3) 「一句目へ」ボタンがタップされた時の処理

  Private Sub headButton_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles headButton.Tap
    '●「一句後へ」ボタンのみ使用可にする
    backButton.IsEnabled = True
    backButton.Opacity = 1
    forwardButton.IsEnabled = False
    forwardButton.Opacity = 0.3
    headButton.IsEnabled = False
    headButton.Opacity = 0.3
    '●ページロード時の表示状態に戻す
    myMargin = 480 - panoramaWidth + edge
    TextBlock1.Margin = New Thickness(myMargin, 0, 0, 0)
  End Sub

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

  Private Sub forwardButton_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles forwardButton.Tap
    '●「一句後へ」ボタンを使用可にする
    backButton.IsEnabled = True
    backButton.Opacity = 1
    myMargin = myMargin - myLineHeight'●左マージンから行間を減算する
    If myMargin < 480 - panoramaWidth + edge + myLineHeight Then'●一句目が画面右端に達した時の処理
      TextBlock1.Margin = New Thickness(480 - panoramaWidth + edge, 0, 0, 0)'●ページロード時の表示状態に戻す
      '●「一句前へ」ボタンと「一句目へ」ボタンを使用不可にする
      forwardButton.IsEnabled = False
      forwardButton.Opacity = 0.3
      headButton.IsEnabled = False
      headButton.Opacity = 0.3
    Else
      TextBlock1.Margin = New Thickness(myMargin, 0, 0, 0)●「一句前へ」ボタンをタップする都度算出した左マージンを適用する
    End If
  End Sub

(5) 「俳句入力」ボタンがタップされた時の処理

  Private Sub createHaikuPage_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles createHaikuPage.Tap
  '●入力ページに戻る時には、最新のエントリに移動する 
    If Me.NavigationService.CanGoBack Then
      Me.NavigationService.GoBack()
    End If
  End Sub

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

  Private Sub toMainPage_Tap(sender As System.Object, e As System.Windows.Input.GestureEventArgs) Handles toMainPage.Tap
    '●メインページに戻った時に「ホームに戻ったことを示す値」を渡す
    NavigationService.Navigate(New Uri("/MainPage.xaml?home=true", UriKind.Relative))
  End Sub

End Class

これでアプリの基本部分の処理は完成です。ここで一度、実際に動作させてデバッグしてみましょう。本記事冒頭の図1のように、俳句を詠むことができたでしょうか。

次回は、ヘルプ周りの実装と、第8回より解説するfacebook投稿に対応した「一日一句 投稿対応版」へのバージョンアップの準備について解説します。

【関連リンク】

Windows Phone 実機に関する参考ページです。こちらも参照してください。

その他、開発に役立つリンク集を紹介します。

開発者向け技術情報サイト MSDN
全ての情報は、ここから。
App Hub / Windows Phone マーケットプレイス Windows Phone フォーラム
App Hub の利用や Windows Phone マーケットプレイスに関する話題や情報交換の場です。
UX-TV
アプリケーション開発に関する情報をエバンジェリストの方々が定期的に発信しています。Windows Phone開発に関する番組が充実しています。
マーケットプレイスへのアプリ申請については、9月14日(水)、9月16日(金)、9月21日(水) の番組を参照してください。
Windows Phone 開発者向け技術情報
facebook 公式 Fan Page「Windows Phone Japan」
Windows Phone 関連ブログ
コミュニティ: Windows Phone Arch

<リンク先最終アクセス:2012.04>

  • 「一日一句」プロジェクトファイル

四国の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メルマガ会員のサービス内容を見る

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