この記事は機械翻訳されたものです。

UI の最前線

Windows Phone 7 でのページ遷移 (機械翻訳)

Charles Petzold

コード サンプルをダウンロードします。

Charles Petzold3 のルールのいくつかの異なる分野に存在します。 喜劇、たとえば、ルールを冗談を開始、観客の関心は、魅力的な 3 つの命令の予想を上げると、最後に、予想よりもやや少ない面白い。

経済学では、3 のルールの 3 つの主要な競争相手、特定の市場での存在に関係しています。 プログラミングでは、それのような 3 ストライク ルールです: 3 回のようなコードの塊を表示するたびに、それは共通のループまたはメソッドにリファクタリングする必要があります。 (私はもともとこのルールとして「3 つ以上ですか? 聞いたこと 使用するには」。または多分そのジングルを自作です。

これは、テキストのレイアウトの件名に私たちをもたらします。 時々 プログラマーは、1 つの画面に合わせるには長すぎるドキュメントを表示する必要があります。 おそらく、最も望ましいアプローチ ドキュメントに収まるまで、フォント サイズを縮小します。 従来のソリューションは、スクロール バーです。 近年では — と錠剤や携帯電話などの小型のデバイスで特に — ドキュメント改ページ調整と、ユーザーは、ページを実際に印刷された本や雑誌を読む場合は、フリップことの傾向があった。

ページ番号付きドキュメントの表示も含む、ルール 3: 最も流体のページ遷移には、UI を 3 つの異なるページをサポートする必要があります-現在のページ、次ページ、前のページ。 ユーザー ページを転送して、現在のページ、前のページになります次のページ現在のページになります。 旧バージョンと、次のページでは、現在のページになりますして前のページ現在のページになります。 この記事では、私は十分な 3 つの異なるページ遷移を柔軟な方法でこの手法を実装する方法について説明します — ページのスライド、3 D のようなフリップと 2D カールをページします。

グーテンベルクに戻る

このコラムの前回の記事で (msdn.microsoft.com/magazine/hh205757)、いくつかの非常に単純な改ページ ロジックという名前の EmmaReader、ジェーン オースティンの小説「エマ」(1815) を読むことができますので、いわゆる Windows の電話 7 のプログラムでを示した。 有名なプロジェクト ・ グーテンベルクの Web サイトからダウンロードした、テキスト ファイル、プログラムを使用 (でgutenberg.org) にはなります利用可能な 3万人以上のパブリック ドメインの書籍。

改ページ調整の時間のかなりの量を必要とすることができます重要なプロセスです。 ページ番号をその理由は、EmmaReader のみオンデマンド本を通じて、ユーザーの進捗状況として付け。 ページを作成した後、プログラムは、本のページを開始する位置を示す情報と終了を格納します。 この情報は、本を下位のユーザー ページにそれを使用できます。

この資料のコードのジョージ ・ エリオットの小説、「ミドルマーチ」(一八七四年) を選択し、、ダウンロード可能な Windows の電話 7 プロジェクト MiddlemarchReader と呼ばれます。 このプログラムは、一歩 Windows 電話 7 のプロジェクト ・ グーテンベルク テキスト ファイルに基づいて汎用の電子書籍リーダーを表します。 現在のベストセラーのこの電子書籍リーダーを使用することはできませんが、それは確かに、英語文学の古典を探索できるようになります。

プロジェクトグーテンベルクのプレーン テキスト ファイル各段落は複数の連続 72 文字の行に分割します。 段落を空白行で区切られます。 これは、プロジェクト ・ グーテンベルクの改ページ調整とプレゼンテーションこれら別の行を 1 行の段落に連結する必要があります形式を希望する任意のプログラムを意味します。 MiddlemarchReader では、PageProvider クラスの GenerateParagraphs メソッドで発生します。 本を読み込むたびに呼び出されます、段落、簡単な汎用リスト String 型のオブジェクトに格納します。

この段落の連結ロジックには、少なくとも 2 つの場合を離れて: 段落全体をインデントすると、各行が個別にラップされますのでプログラムこれらの行連結はありません。 散文ではなく、詩の本が含まれる場合、逆の問題が発生する: プログラムが誤ってこれらの行を連結します。 これらの問題は、人間の介入なしかなり困難である、実際のテキストの意味を調べることなくが避けることは不可能です。

新しい章

ほとんどの書籍の章にも分かれているし、特定の章にジャンプすることができます、e ブック リーダーの重要な機能です。 EmmaReader、私は章では、無視しますが、PageProvider クラス MiddlemarchReader の各チャプターの開始位置を決定する GenerateChapters メソッドが含まれます。 一般に、プロジェクト ・ グーテンベルク ファイル章本に応じての 3 つまたは 4 つの空白行を区切ります。 「ミドルマーチは、「3 つの空白行、章タイトルを示す行の前に。 GenerateChapters この機能、特定の段落を確認するのにはそれぞれの章を開始使用します。

本章に分けて、電子書籍リーダーの改ページ調整問題を軽減できます。 たとえば、読者が本の終わりの近くであるし、フォント サイズを変更することを決定します。 (EmmaReader も MiddlemarchReader もこの機能があるが私は理論的には話しています。章部門なし全体予約にポイントが必要があります。 しかし、本の章に分割されている場合は、現在のモジュールだけがする必要があります。 行が挿入または削除することが機械式の日に戻る、本の章に分割、違和まったく同じ方法でしました。

MiddlemarchReader プログラム、書籍に関する情報は分離ストレージに保存します。 この情報の永続化の主なクラスは、AppSettings と呼ばれます。 AppSettings ChapterInfo オブジェクトだ (それぞれの章に 1 つ) のコレクションが含まれています、BookInfo オブジェクトを参照します。 これらの ChapterInfo オブジェクトには、章のタイトルとその章の PageInfo オブジェクトのコレクションが含まれます。 PageInfo オブジェクトは、各ページの各段落の文字列の一覧を参照に、文字列の CharacterIndex 内のインデックス、ParagraphIndex に基づいて開始位置を示します。 章の最初の PageInfo オブジェクトは、前に説明した GenerateChapters メソッドで作成されます。 後続の PageInfo オブジェクトを作成され、章のユーザーの読み取りに徐々 に、章改ページ調整されるように蓄積します。 また BookInfo で保存、チャプター インデックス、章内のページのインデックスを特定のユーザーの現在のページです。

「ミドルマーチ」86 章ですが「ミドルマーチ」部門に 8 つの「本」と素材、先頭と末尾、ファイルのタイトルなど、110 の章 PageProvider の GenerateChapters メソッドにロジックを検索。 電話番号の書式設定、小説は、約 1,800 ページまたは章ごとの約 20 のページです。

図 1MainPage.xaml で、コンテンツ ・ パネルを示しています。 単一セル グリッドを占有している 2 つのコントロールが、その時に 1 つだけ表示されます。 両方のコントロールには、MiddlemarchPageProvider をリソースとして定義された型のオブジェクトへのバインドが含まれます。 BookViewer コントロールを説明します。 ListBox の章では、スクロール可能な一覧が表示され、プログラムの ApplicationBar のボタンにそれを呼び出します。 図 2MiddlemarchReader、について中間にスクロールにチャプターの一覧を示します。

図 1MainPage.xaml のコンテンツ パネル

<phone:PhoneApplicationPage.Resources>
  <local:MiddlemarchPageProvider x:Key="pageProvider" />
</phone:PhoneApplicationPage.Resources>
...
<Grid x:Name="ContentPanel" Grid.Row="1">
  <local:BookViewer x:Name="bookViewer"
                    PageProvider="{StaticResource pageProvider}"
                    PageChanged="OnBookViewerPageChanged">
    <local:BookViewer.PageTransition>
      <local:SlideTransition />
    </local:BookViewer.PageTransition>
  </local:BookViewer>

  <ListBox Name="chaptersListBox"
           Visibility="Collapsed"
           Background="{StaticResource PhoneBackgroundBrush}"
           ItemsSource="{Binding Source={StaticResource pageProvider}, 
                                 Path=Book.Chapters}"
           FontSize="{StaticResource PhoneFontSizeLarge}"
           SelectionChanged="OnChaptersListBoxSelectionChanged">
    <ListBox.ItemTemplate>
      <DataTemplate>
        <Grid Margin="0 2">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
          </Grid.ColumnDefinitions>
                            
          <TextBlock Grid.Column="0"
                     Text="&#x2022;"
                     Margin="0 0 3 0" />
                        
          <TextBlock Grid.Column="1" 
                     Text="{Binding Title}"
                     TextWrapping="Wrap" />
        </Grid>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Grid>

私はこれらの見出しとタイトルをすべて大文字に表示する選択していないかが、元のファイルとプログラムで盲目的にそれらを 3 つ以上の空白行で先行する行のみに基づくプル表示方法です。

The Scrollable List of Chapters

図 2章のスクロール可能なリスト

戻るページ問題

ページ番号を EmmaReader のような MiddlemarchReader に応じて、本を読み取りますが下位ページのページ情報が保存されますと付け。 そこの問題無し。 MiddlemarchReader は、ユーザーが特定のチャプターにジャンプすることもできます。 問題ありませんが、いずれか、各チャプターの開始位置は、すでにプログラムを知っているので。

ただし、ユーザーは、章の先頭にジャンプ、後方、最後、前の章のページを決定とします。 前の章がまだ改されていない場合は、その全体の章は、最後のページを表示するには改ページ調整する必要があります。 段落不足している可能性があります。 または本と章によって非常に長いことができます。 それは問題です。

MiddlemarchReader 要求時にページ番号を付けが、それは実際には少し越えて行きます。 とき、ユーザーが 1 つのページ、次ページ、前のページを読んでいる前方または後方のページングの準備ができています。 通常、これら追加のページを取得するには、問題ではありません。 しかし、ユーザーは、章の先頭にジャンプするとき、プログラム何をすべきか? プログラムは、後方にページングの可能性のために準備するには、前の章の最後のページを得るか? それは無駄: ほとんどの場合、ユーザーは、そうなぜわざわざページされますか?

明らかに、それは少しトリッキーを取得します。 私はすでに PageProvider クラスが元のプロジェクト ・ グーテンベルク ファイルを解析し、段落、章に分けて責任ですが、改ページ調整の責任も言及しました。 参照される MiddlemarchPageProvider クラス図 1小型です。 PageProvider から派生し、PageProvider によって処理できるように単に「ミドルマーチ」ファイルをアクセスします。

私は PageProvider の内部に、可能な限りの知識を少しとしてに、BookViewer コントロールを望んでいた。 その理由は、BookViewer の PageProvider プロパティのタイプ IPageProvider (PageProvider によって実装されるインターフェイス) とのように図 3

図 3IPageProvider フェース

public interface IPageProvider
{
  void SetPageSize(Size size);

  void SetFont(FontFamily fontFamily, double FontSize);

  FrameworkElement GetPage(int chapterIndex, int pageIndex);

  FrameworkElement QueryLastPage(int chapterIndex, out int pageIndex);

  FrameworkElement GetLastPage(int chapterIndex, out int pageIndex);
}

これから、そのしくみについて説明します。各ページのサイズの PageProvider BookViewer オブジェクトを通知し、フォントとフォント サイズの TextBlock 要素の作成のために、ページを構成します。 ほとんどの場合、BookViewer は、GetPage を呼び出すことによってページを取得します。 GetPage が null を返す場合、章、ページ インデックス範囲のうち、そのページが存在しません。 これは、章の終わりを超えてを行って次の章に進む必要があると BookViewer を示しています。 図 4MiddlemarchReader、章の先頭に表示されます。 (「ミドルマーチ」の各章金石文、いくつかのジョージ ・ エリオットが彼女自身を記述で始まります。)

The BookViewer Control Displaying a Page

図 4BookViewer コントロールは、ページの表示

BookViewer 章の先頭に移動すると、IPageProvider には、前の章の最後のページを取得するには、2 つのメソッドを定義します。 QueryLastPage、高速な方法です。 既に前の章改ページ調整されている、その最後のページが利用可能の場合は、メソッドはページを返し、ページ インデックスを設定します。 ページが使用できない場合は、QueryLastPage は null を返します。 これは、ユーザー、章の先頭に移動すると、前のページを取得する BookViewer を呼び出すメソッドです。

QueryLastPage null ユーザーしページを返しますは、行方不明のページに戻る場合は、BookViewer には GetLastPage の呼び出しが、ことができません。 必要に応じて、GetLastPage のちょうど最後のページを取得するには、全体の章改ページします。

理由は、章の先頭に、ユーザーを移動した後、前の章では、2 番目のスレッドの実行のページ次のように設定ですか? おそらく 2 番目のスレッドは戻るページにユーザーを決定する時間で完了します。 確かに説得力のあるソリューションですが、改ページ ロジックは少し StackPanel と TextBlock オブジェクトを作成しているプライマリ スレッドによってこれらを使用することはできませんので再構築する必要があります。

BookViewer 遷移

私は、一般的には、前述のとおり BookViewer は 3 つのページを一度にジャグリングです。 コントロールが UserControl から派生し、図 5XAML ファイルを示します。 PageHost0、pageHost1、pageHost2 の名前を持つ 3 つの Border 要素は、PageProvider から取得したページの親として使用されます。 (これらのページはページで段落を TextBlock 実際に StackPanel 要素です)。白い背景と、わずかなマージン ホワイト ページの境界線の周りの他の 3 つの境界線要素 pageContainer で始まる名前を提供します。 これらはまたページ遷移を操作要素です。 GestureListener (Silverlight を Windows 電話ツールキット、CodePlex でからダウンロードで利用可能なbit.ly/cB8hxu) タッチ入力を提供します。

図 5BookViewer の XAML ファイル

<UserControl x:Class="MiddlemarchReader.BookViewer"
             xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=
               Microsoft.Phone.Controls.Toolkit">

  <UserControl.Resources>
    <Style x:Key="pageContainerStyle" TargetType="Border">
      <Setter Property="BorderBrush" Value="Black" />
      <Setter Property="BorderThickness" Value="1" />
      <Setter Property="Background" Value="White" />
    </Style>

    <Style x:Key="pageHostStyle" TargetType="Border">
      <Setter Property="Margin" Value="12, 6" />
      <Setter Property="CacheMode" Value="BitmapCache" />
    </Style>
  </UserControl.Resources>

  <Grid x:Name="LayoutRoot">
    <toolkit:GestureService.GestureListener>
      <toolkit:GestureListener GestureBegin="OnGestureListenerGestureBegin"
                               GestureCompleted=
                                 "OnGestureListenerGestureCompleted"
                               Tap="OnGestureListenerTap"
                               Flick="OnGestureListenerFlick" 
                               DragStarted="OnGestureListenerDragStarted"
                               DragDelta="OnGestureListenerDragDelta"
                               DragCompleted="OnGestureListenerDragCompleted" />
    </toolkit:GestureService.GestureListener>

    <Border Name="pageContainer0" Style="{StaticResource pageContainerStyle}">
      <Border Name="pageHost0" Style="{StaticResource pageHostStyle}" />
    </Border>

    <Border Name="pageContainer1" Style="{StaticResource pageContainerStyle}">
      <Border Name="pageHost1" Style="{StaticResource pageHostStyle}" />
    </Border>

    <Border Name="pageContainer2" Style="{StaticResource pageContainerStyle}">
      <Border Name="pageHost2" Style="{StaticResource pageHostStyle}" />
    </Border>
  </Grid>
</UserControl>

BookViewer が作成されると、pageHosts という名前の配列に 3 つの pageHost オブジェクトを格納します。 PageHostBaseIndex という名前のフィールドは 0 に設定されます。 ユーザー ページの本を pageHostBaseIndex に行く 1 日に 2、0 をバックアップします。 ユーザーのページの下位、pageHostBaseIndex は、2、1 をして戻る 0 から 0 を行きます。 いつでも、pageHosts [pageHostBaseIndex] 現在のページです。 次のページです。

pageHosts[(pageHostBaseIndex + 1) % 3]

前のページです。

pageHosts[(pageHostBaseIndex + 2) % 3]

ページのホストでは、特定のページを設定するとそれを交換するまでこの方式では、それがまま。 プログラム ページから 1 ページ別のホストに転送する必要はありません。

このしくみは非常に単純なページ遷移できます: pageHosts [pageHostBaseIndex] の上の他の 2 つはちょうど Canvas.SetZIndex を使用します。 しかし、おそらくページの間を移行する方法はありません。 それはむしろ当たり障りのないもやや危険であり。 ユーザー可能性があります誤って先の代わりに、2 つのページに移動し、ないもそれを実現、タッチ入力の不正確さです。 このためより劇的なページ遷移が望ましいです。

BookViewer 型 PageTransition に示すように、抽象クラスの PageTransition プロパティを定義することで柔軟にページ効果についてを実現図 6

図 6PageTransition クラス

public abstract class PageTransition : DependencyObject
{
  public static readonly DependencyProperty FractionalBaseIndexProperty =
    DependencyProperty.Register("FractionalBaseIndex",
      typeof(double),
      typeof(PageTransition),
      new PropertyMetadata(-1.0, OnTransitionChanged));

  public double FractionalBaseIndex
  {
    set { SetValue(FractionalBaseIndexProperty, value); }
    get { return (double)GetValue(FractionalBaseIndexProperty); }
  }

  public virtual double AnimationDuration 
  {
    get { return 1000; }
  }

  static void OnTransitionChanged(DependencyObject obj, 
                                  DependencyPropertyChangedEventArgs args)
  {
    (obj as PageTransition).OnTransitionChanged(args);
  }

  void OnTransitionChanged(DependencyPropertyChangedEventArgs args)
  {
    double fraction = (3 + this.FractionalBaseIndex) % 1;
    int baseIndex = (int)(3 + this.FractionalBaseIndex - fraction) % 3;
    ShowPageTransition(baseIndex, fraction);
  }

  public abstract void Attach(Panel containerPanel,
                              FrameworkElement pageContainer0,
                              FrameworkElement pageContainer1,
                              FrameworkElement pageContainer2);

  public abstract void Detach();

  protected abstract void ShowPageTransition(int baseIndex, double fraction);
}

PageTransition は、BookViewer タッチ入力に基づいて設定を FractionalBaseIndex という名前の単一の依存関係プロパティを定義します。 ユーザー、画面をタップすると、FractionalBaseIndex、pageHostBaseIndex から DoubleAnimation pageHostBaseIndex プラス 1 に増やします。 アニメーションは、ユーザー、画面をフリックする場合もトリガーされます。 FractionalBaseIndex 画面に沿って指を「手動で」に設定されているユーザー ドラッグの距離に基づいて場合ユーザー全体を画面幅の割合として彼指をドラッグしていますいます。

PageTransition クラスは、整数 baseIndex と、派生クラスの利益のために FractionalBaseIndex を分離します。 ShowPageTransition 方法では、特定の遷移の特性は、派生クラスによって実装されます。 既定値は SlideTransition に示すように、です図 7はスライドを前後のページです。 図 8ビューを下落されて、ページが表示されます。 クラス TranslateTransform オブジェクトはページのコンテナーに Attach の呼び出し中につき、デタッチ中に削除されます。

図 7SlideTransition クラス

public class SlideTransition : PageTransition
{
  FrameworkElement[] pageContainers = new FrameworkElement[3];
  TranslateTransform[] translateTransforms = new TranslateTransform[3];

  public override double AnimationDuration
  {
    get { return 500; }
  }

  public override void Attach(Panel containerPanel,
                              FrameworkElement pageContainer0, 
                              FrameworkElement pageContainer1, 
                              FrameworkElement pageContainer2)
  {
    pageContainers[0] = pageContainer0;
    pageContainers[1] = pageContainer1;
    pageContainers[2] = pageContainer2;

    for (int i = 0; i < 3; i++)
    {
      translateTransforms[i] = new TranslateTransform();
      pageContainers[i].RenderTransform = translateTransforms[i];
    }
  }

  public override void Detach()
  {
    foreach (FrameworkElement pageContainer in pageContainers)
      pageContainer.RenderTransform = null;
  }

  protected override void ShowPageTransition(int baseIndex, double fraction)
  {
    int nextIndex = (baseIndex + 1) % 3;
    int prevIndex = (baseIndex + 2) % 3;

    translateTransforms[baseIndex].X = -fraction * 
      pageContainers[prevIndex].ActualWidth;
    translateTransforms[nextIndex].X = translateTransforms[baseIndex].X + 
      pageContainers[baseIndex].ActualWidth;
    translateTransforms[prevIndex].X = translateTransforms[baseIndex].X – 
      pageContainers[prevIndex].ActualWidth;
  }
}

A Page Sliding into View

図 8スライド ビュー ページ

他の 2 つのページ遷移アプリケーション バー メニューから選択することができます: FlipTransition SlideTransition には、ような PlaneProjection 変換の 3 D の外観の使用以外は。 右上を返した場合、CurlTransition は、ページを表示します。 (最初に、右下コーナーからは、カールに CurlTransition コードを書いたし、水平方向の座標を単に調整することによって変換することができた。 あなたは右カールに戻る curlFromTop 定数を false に設定して再コンパイルする変更できます。)

MiddlemarchReader 章の先頭にジャンプするには、ユーザーができるので、実際のページ番号は、プログラムの先頭に表示可能なありません。 代わりに、プログラム テキストの長さにベースの割合が表示されます。

改ページ調整の恐怖

実際の使用では、MiddlemarchReader を見て、多くの実際の電子書籍リーダーのように感じるし始めています。 しかし、それは、その改ページ ロジックのパフォーマンスの低下から苦しんでいます。 確かに、いくつかのページに行く、段落、章を始まるため、プログラムを Windows の電話 7 デバイスで実行されている場合、最初の章の「ミドルマーチ」最初ページは少し遅れた。 バックアップ後に新しい段落を時々 ジャンプ秒ほど必要し、私はまだ十分にユーザーが選択したフォントやフォント サイズ改ページのこれらの機能が必要なためにプログラムを導入する勇敢されていないページング。

改ページを高速化する方法はあるか。 おそらく。 間違いなくこのような改善は、エレガントな巧妙な私は予想よりも難しいだりましょう。

Charles Petzold 長年寄稿編集者である MSDN Magazine*.*彼の最近の本は、「プログラミング Windows 電話 7」(マイクロソフト プレス、2010) は無料のダウンロードとして利用可能なbit.ly/cpebookpdf.