プログラミング Windows 第6版 第3章 WPF編


この記事では、「プログラミング Windows 第6版」を使って WPF XAML の学習を支援することを目的にしています。この目的から、書籍と併せて読まれることをお勧めします。

第3章 基本的なイベント処理

3.1(P75) タップ イベント

この節では、タッチ、マウス、ペンによるイベントに基本的な事項を説明しています。特に、タッチ系のイベントに関して WinRT XAML と WPF XAML では異なるので注意が必要です。

  • タッチ、マウス、ペンによる入力は、名前が Pointer で始まる 8 つのイベントにまとめられています(書籍より引用)。
    WPF XAML では、Mouse で始まる 10 個のイベントになっており、Pointer で始まるイベントはありません。
  • 複数の指を使った入力は、名前が Manipulation で始まる 5 つのイベントにまとまれています(書籍より引用)。
    WPF XAML でも基本は同じで Manipulation で始まるイベントになっており、Win RT XAML の 5つに加えて、ManipulationboundaryFeedback イベントが追加されています。
  • キーボード入力は、WinRT XAML も WPF XAML も同じで名前が Key で始まる 2つのイベントにまとめられています。
  • この他に、Tapped、DoubleTapped、RightTapped、Holding というより高度なイベントがあります(書籍より引用)。
    WPF XAML では、Tapped イベントはサポートされていないので注意が必要です。

WPF XAML のタッチに関するイベントは、名前が Touch で始まる 5 つのイベントにまとめられています(WinRT XAML は、名前が Touch で始まるイベントはありません)。ここで、WinRT XAML と WPF XAML におけるタッチ サポートの違いを理解しておくことが重要です。

  • 複数の指を使ったジェスチャー操作は、WinRT XAML と WPF XAML は 名前が Manipulation で始まるイベントになっています。
  • WinRT XAML でタッチの低レベル操作を扱う時は、名前が Pointer で始まるイベントを使用します(タッチ、マウスで共通です)。
  • WPF XAML でタッチ固有の処理を行う時は、名前が Touch で始まるイベントを使用します。
  • WPF XAML でタッチとマウスを区別しないでポインターに関する操作を行う時は、名前が Mouse で始まるイベントを使用します。
  • タップなどの Windows のタッチ操作を直接的に処理できるのは、WinRT XAML となります。つまり、イベントが用意されているという意味になります。
    WPF XAML では、名前が Touch で始まるイベントを使って自分でタップなどの操作を実現する必要があります。もちろん、タップなどのタッチ 操作を判定するコンポーネントが市販されていれば、そのようなコンポーネントを使っても良いでしょう。

Windows OS は、OS 側の機能として タッチ 入力をマウス入力と見做して処理できるようになっています。これを、タッチ操作として処理するには Windows Touch API を使用する必要があります。この Windows Touch サポートを盛り込んだのが、WPF XAML におけるタッチ サポートになります。一方で、WinRT XAML は Windows のタッチ操作を提示しているようにタッチ操作を前提に用意された環境だと言えます。
余談になりますが、Windows Touch API サポートは Windows 7 からになります。Windows Vista では、テーブル型のマルチタッチ コンピューターの Surface のための独自拡張になっていました。もちろん、Silverlight もタッチ サポートが入りましたが、名前が Touch で始まるイベントのみのサポートになっており、Windows Phone 向けの Silverlight で DoubleTap のサポートが追加されています。

タッチの違いが理解できれば、TapTextBlock サンプルをどのように書き換えれば良いかが理解できたことでしょう(もちろん、第1章などで説明した、Page をWindows へ、組み込みスタイルの変更なども必要です)。それでは、MainWindow.xaml の抜粋を示します。

<Grid>

    <TextBlock Name="txtblk"
               Text="Touch Text!"
               FontSize="96"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               TouchDown="txtblk_TouchDown" />
</Grid>

 

TouchDown イベントに書き換えていますが、もちろん TouchUp イベントでも構いません。要は、どのタイミングでコードを実行するかという違いだからです。それでは、MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    Random rand = new Random();
    byte[] rgb = new byte[3];

    public MainWindow()
    {
        InitializeComponent();
    }

    private void txtblk_TouchDown(object sender, TouchEventArgs e)
    {
        rand.NextBytes(rgb);
        Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
        txtblk.Foreground = new SolidColorBrush(clr);
    }
}

 

3.2(P79) ルーティング イベントの処理

この節では、ルーティング イベントを説明しています。ルーティング イベントは、WPF XAML でも同じ機能となります。一方で Windows Forms には、無い考え方になります。あえて Windows Forms に当てはめるのであれば、たとえば KeyDown イベントに対する PreviewKeyDown イベントが全てのイベントでサポートされていると考えることができます。ルーティング イベントは、別名としてバブル イベントと呼ばれています。何故、バブルかと言えば、XAML 定義の中でイベントが発生したら タグを親に向かって連鎖的にイベントを発生させることができるからです(つまり、泡が広がるのと同じです)。それでは、RoutedEvents0 プロジェクトの Mainwindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    Random rand = new Random();
    byte[] rgb = new byte[3];

    public MainWindow()
    {
        InitializeComponent();
    }

    private void TextBlock_TouchDown(object sender, TouchEventArgs e)
    {
        TextBlock txtblk = sender as TextBlock;
        rand.NextBytes(rgb);
        Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
        txtblk.Foreground = new SolidColorBrush(clr);
    }
}

 

TextBlock_TouchDown イベントを呼び出す MainWindow.xaml の抜粋を示します。

<Window x:Class="RoutedEvents0.MainWindow"
        ...
        FontSize="48">
    <Grid>
        <TextBlock Text="Left / Top"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top"
                   TouchDown="TextBlock_TouchDown" />
        ...
        <TextBlock Text="Right / Bottom"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Bottom"
                   TouchDown="TextBlock_TouchDown" />
    </Grid>
</Window>

 

実行結果は、同じように TextBlock をタッチすることで色が変化するようになります。
RoutedEvents0

次の RoutedEvents1 プロジェクトでは、ルーティング イベントを理解するために TextBlock ではなく、Grid にイベントを設定します。

<Grid TouchDown="Grid_TouchDown">
    <TextBlock Text="Left / Top"
               HorizontalAlignment="Left"
               VerticalAlignment="Top" />
    ...

    <TextBlock Text="Right / Bottom"
               HorizontalAlignment="Right"
               VerticalAlignment="Bottom" />

</Grid>

そして、RoutedEvents1 プロジェクトの MainWindow.xaml.cs の抜粋を示します。

private void Grid_TouchDown(object sender, TouchEventArgs e)
{
    if (e.OriginalSource is TextBlock)
    {
        TextBlock txtblk = e.OriginalSource as TextBlock;
        rand.NextBytes(rgb);
        Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
        txtblk.Foreground = new SolidColorBrush(clr);
    }
}

イベント ハンドラーのコードは、WinRT XAML と同じだということがわかります。

今度は、XAML でイベントを定義しない RoutedEvents2 プロジェクトの MainWindow.xaml の抜粋を示します。

<Window x:Class="RoutedEvents2.MainWindow"
        ...
        FontSize="48">
    <Grid>
        <TextBlock Text="Left / Top"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top" />
        ...
        <TextBlock Text="Right / Bottom"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Bottom" />

    </Grid>
</Window>

 

イベントの定義を示すために、MainWindow.xaml.cs の抜粋を示します。

protected override void OnTouchDown(TouchEventArgs e)
{
    if (e.OriginalSource is TextBlock)
    {
        TextBlock txtblk = e.OriginalSource as TextBlock;
        rand.NextBytes(rgb);
        Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
        txtblk.Foreground = new SolidColorBrush(clr);
    }
    base.OnTouchDown(e);
}

 

WinRT XAML と同じように、OnTouchDown イベントをオーバーライドすることでイベントを処理しています。

今度は、イベント引数の OriginalSource のオブジェクトによって、色を変える RoutedEvents3 プロジェクトの MainWindow.xaml.cs の抜粋を示します。

protected override void OnTouchDown(TouchEventArgs e)
{
    rand.NextBytes(rgb);
    Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
    SolidColorBrush brush = new SolidColorBrush(clr);

    if (e.OriginalSource is TextBlock)
        (e.OriginalSource as TextBlock).Foreground = brush;

    else if (e.OriginalSource is Grid)
        (e.OriginalSource as Grid).Background = brush;

    base.OnTouchDown(e);
}

 

また、RoutedEvents3 プロジェクトの MainWindow.xaml の抜粋を示します。

<Grid Background="White">
    <TextBlock Text="Left / Top"
               HorizontalAlignment="Left"
               VerticalAlignment="Top" />
    ...
    <TextBlock Text="Right / Bottom"
               HorizontalAlignment="Right"
               VerticalAlignment="Bottom" />

</Grid>

ここで注意して欲しいのが、Grid 要素の Background プロパティを「White」に指定している点です。コードでは、OriginalSource によって TextBlock か Grid かを識別して背景色を変更していますが、Grid の Background プロパティを指定しないと「Background=”{x:Null}”」を指定したことと同義になり、Grid コントロールが不可視となり、イベントを発生させることができなくなります。これは、XAML を使った UI 技術の特徴にもなります。WinRT XAML でこのことが問題にならないのは、Background に組み込みのスタイルを指定しているからです。 

RoutedEvents3 から、Grid の背景色と TextBlock の背景色をイベントによってランダムに設定するようにした RoutedEvents4 の MainWindow.xaml の抜粋を示します。

<Grid Name="contentGrid">
    <TextBlock Text="Left / Top"
               HorizontalAlignment="Left"
               VerticalAlignment="Top"
               TouchDown="TextBlock_TouchDown" />
    ...
    <TextBlock Text="Right / Bottom"
               HorizontalAlignment="Right"
               VerticalAlignment="Bottom"
               TouchDown="TextBlock_TouchDown" />
</Grid>

 

そして、MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    Random rand = new Random();
    byte[] rgb = new byte[3];

    public MainWindow()
    {
        InitializeComponent();
    }

    private void TextBlock_TouchDown(object sender, TouchEventArgs e)
    {
        TextBlock txtblk = sender as TextBlock;
        txtblk.Foreground = GetRandomBrush();
    }

    protected override void OnTouchDown(TouchEventArgs e)
    {
        contentGrid.Background = GetRandomBrush();
        base.OnTouchDown(e);
    }

    Brush GetRandomBrush()
    {
        rand.NextBytes(rgb);
        Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
        return new SolidColorBrush(clr);
    }
}

RoutedEvents4 プロジェクトでは、RoutedEvents3 プロジェクトと異なり Grid 要素の Background プロパティを指定していません。それでも、RoutedEvents3 プロジェクトと同じように動作する理由は、OnTouchDown イベントにあります。このイベントで、無条件に Grid の背景色を変更しているから問題なく動作します。このことは、RoutedEvents3 プロジェクトの説明と合わせると、次に示す特徴を表しています。

  • TouchEventArgs.OriginalSource には、背景色が Null な Grid オブジェクトが含まれない。
  • OnTouchDown イベントは、Window オブジェクトをオーバーライドしているので必ず発生する。

次のサンプルでは、バブル イベントという性格を持つルーティング イベントを停止するかどうかを試します。では、RoutredEvents5 プロジェクトの MainWindow.xaml.cs  の抜粋を示します。

private void TextBlock_TouchDown(object sender, TouchEventArgs e)
{
    TextBlock txtblk = sender as TextBlock;
    txtblk.Foreground = GetRandomBrush();
    e.Handled = true;
}

イベント引数が持つ Handled プロパティを設定しているだけで、Handled プロパティの説明は次の節で紹介しています。

3.3(P85) Handled 設定の上書き

この節で最初に説明する RoutedEvents6 プロジェクトでは、Grid要素の背景色を変更するイベントと TextBlock の色を変更するイベントを定義しています。この MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
  Random rand = new Random();
  byte[] rgb = new byte[3];

  public MainWindow()
  {
      InitializeComponent();

      this.AddHandler(UIElement.TouchDownEvent,
                      new EventHandler(OnWindowTouchDown), true);
  }

  private void TextBlock_TouchDown(object sender, TouchEventArgs e)
  {
      TextBlock txtblk = sender as TextBlock;
      txtblk.Foreground = GetRandomBrush();
      e.Handled = true;
  }

  private void OnWindowTouchDown(object sender, TouchEventArgs e)
  {
      contentGrid.Background = GetRandomBrush();
  }

  Brush GetRandomBrush()
  {
      rand.NextBytes(rgb);
      Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
      return new SolidColorBrush(clr);
  }

}

コンストラクターで行っているイベント ハンドラーの登録は、WinRT XAML と同じように AddHandler メソッドを使用しています。AddHandler メソッドを使用している理由は、本書を読んで確認してください。

3.4(P87) 入力、位置合わせ、背景

この節では、RoutedEventns3 と比較して TextBlock がどのように配置されるかを ToutedEvents7 プロジェクトを使って説明しています。それでは、MainWindow.xaml の抜粋を示します。

<Window x:Class="RoutedEvents7.MainWindow"
        ...
        FontSize="48">
    <Grid Background="White">
        >TextBlock Text="Hello, Windows 8!"
                   Foreground="Red" />

    </Grid>
</Window>

そして、MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    Random rand = new Random();
    byte[] rgb = new byte[3];

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnTouchDown(TouchEventArgs e)
    {
        rand.NextBytes(rgb);
        Color clr = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
        SolidColorBrush brush = new SolidColorBrush(clr);

        if (e.OriginalSource is TextBlock)
            (e.OriginalSource as TextBlock).Foreground = brush;

        else if (e.OriginalSource is Grid)
            (e.OriginalSource as Grid).Background = brush;
        base.OnTouchDown(e);
    }
}

もちろん。RoutedEvents3 プロジェクトと同じように Grid 要素の Background プロパティを設定しています。実行結果を示します。
RoutedEvents7

自分で実行して、ウィンドウをタップしてみるとわかりますが、ウィンドウのどこをタップしても TextBlock の色が変化されます。つまり、TextBlock の表示位置などの指定していないので、ウィンドウ一杯の領域が TextBlock と扱われて、OnTouchDown イベント ハンドラーの Grid を判定するロジックが動作しないことを確認できます。この問題を解決するには、書籍に記述されているように TextBlock の表示位置や表示サイズを指定して、Grid の背景色に何かの色(たとえば、Whiteなど)を指定するか、「Transparent」を指定することになります(Transparent と Null は異なりますので、注意してください)。

3.5(P90) サイズと向きの変更

この節では、Windows ストア アプリが画面の向きやサイズ変更にどのように対応しているかをサンプルを使って説明しています。最初に説明しているのは、WhatSize プロジェクトを使って、画面サイズを確認することです。それでは、WhatSize プロジェクトの MainWindow.xaml の抜粋を示します。

<Window x:Class="WhatSize.MainWindow"
        ...
        SizeChanged="Window_SizeChanged">
    <Grid>
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Top">
            ↤ <Run x:Name="widthText" /> pixels ↦
        </TextBlock>

        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   TextAlignment="Center">
            ↥
            <LineBreak />
            <Run x:Name="heightText" /> pixels
            <LineBreak />
            ↧
        </TextBlock>
    </Grid>
</Window>

 

続いて、MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        widthText.Text = e.NewSize.Width.ToString();
        heightText.Text = e.NewSize.Height.ToString();
    }
}

 

WinRT XAML と同じように SizeChanged イベントを処理することで、画面のサイズを取得することができます。
WhatSize

書籍では、続いて、画面の向きが変化したかどうかを ScalableInternationalHelloWorld プロジェクトを使って説明しています。この時に使用しているクラスが、DisplayProperties (Windows 8.1では、DisplayInformation) になりますが、このクラスは Windows Runtime 固有であり、WPF などのデスクトップ アプリから使用することはできません。

それでは、WPF などのデスクトップ向けのアプリでタブレット PC 固有の画面の向きに対応するにはどうしたら良いでしょうか。これには、いつかの手段を考えることができます。

  • システム イベントの DisplaySettingsChanged イベントで処理する(もしくは、WM_DISPLAYCHANGED メッセージを ウィンドウ プロシージャで処理します)。
  • Screen クラスの PrimaryScreent プロパティを使って、Bounds プロパティ経由で縦横を比較して画面の向きを処理する。

これらの説明が、「Detecting Screen Orientation and Screen Rotation in Tablet PC Applications」という ドキュメントに記載されていますので、必要に応じて処理する必要があります。よって、 ScalableInternationalHelloWorld プロジェクトはサンプルに含まれていません。

Windows ストア アプリでは、画面の向きやスケーリングを完全にサポートしています。一方で、デスクトップ上のアプリは WPFのみがスケーリングのに対応し、画面の向きなどの対応はアプリケーションに任せるという考え方になっている点にご注意ください。

3.6(P95) Run でのバインディング

この節で最初に説明している TextBlock 内に「<Run Text=”{Binding ElementName=window, Path=ActualWidth}” />」という記述方法を WPF XAML で行うと、Visual Studio 2013 の IDE は警告を表示します。警告を無視して実行すると、実行時に初回例外が発生します。この動きは、WinRT XAML とは違いますが、Run 要素を使わないバインディングでは正常に動作します。
====ここから追記====
書籍では「<Run Text=”{Binding ElementName=page, Path=ActualWidth}” />」という記述をすると、ゼロが表示されると記載されています。そして、Petzold が Blog でうまく動くと記載しています。この件を調べた結果、WinRT XAML では ActualWidth、ActualHeight プロパティをバインディングした場合に発生する現象でした。WPF XAML では、 ActualWidth、ActualHeight プロパティをバインディングする場合は明示的に「, Mode=OneWay」を記載しないと、実行時に初回例外が発生します。もちろん、Width などに値を明示的に指定してバインディングした場合は、この問題は WinRT XAML と WPF XAML でも発生しません。どうも、 ActualWidth、ActualHeight プロパティをバインディングする場合に注意しないといけない問題だと思われます。emoacht さん、フィードバックを有難うございました。
==== ここまで ======

<Window x:Class="WhatSizeBinding.MainWindow"
        ...
        x:Name="window"
        FontSize="36">
    <Grid>
        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Top"
                   Text="{Binding ElementName=window, Path=ActualWidth}" />

        <TextBlock HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   TextAlignment="Center"
                   Text="{Binding ElementName=window, Path=ActualHeight}" />
    </Grid>
</Window>

 

実行結果も書籍と同じようになります。なぜ、Run 要素にバインディングができないかの理由は書籍を読めば答えがわかるようになっています。ここでは、WinRT XAML と WPF XAML の挙動の違いを説明するだけにします。

3.7(P98) タイマーとアニメーション

この節では、DispatcherTimer クラスを使った説明になります。DispatcherTimer クラスは、XAML という UI 技術を使った場合に使える共通のタイマー クラスとなっています。一方 Windows Forms では、System.Windows.Forms.Timer クラスが用意されていて、デザイナーにツールボックスからドラッグ&ドロップして使用することができますが、WPF を初めとする XAML 系の UI 技術ではデザイナー サポートは用意されていません。つまり、タイマーを使用したい場合は、自分でコードを記述する必要がある点にご注意ください。

最初に DigitalClock プロジェクトの MainWindow.xaml の抜粋を示します。

<Grid>
    <TextBlock Name="txtblk"
               FontFamily="Lucida Console"
               FontSize="120"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />

</Grid>

 

xaml は WinRT と同じであり、分離コードも同じになります。次にMainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += OnTimerTick;
        timer.Start();
    }

    void OnTimerTick(object sender, object e)
    {
        txtblk.Text = DateTime.Now.ToString("h:mm:ss tt");
    }
}

もちろん、実行した結果も同じになります。
DigitalClock

次のサンプルは、CompositionTarget.Rendering イベント ハンドラーを利用した ExpandingText プロジェクトになります。MainWindow.xaml の抜粋を示します。

<Grid>
    <TextBlock Name="txtblk"
               Text="Hello, Windows 8!"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />

</Grid>

ExpandingText プロジェクトは、WPF でも同じコードを使用できますので、MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        CompositionTarget.Rendering += OnCompositionTargetRendering;
    }

    void OnCompositionTargetRendering(object sender, object args)
    {
        RenderingEventArgs renderArgs = args as RenderingEventArgs;
        double t = (0.25 * renderArgs.RenderingTime.TotalSeconds) % 1;
        double scale = t < 0.5 ? 2 * t : 2 - 2 * t;
        txtblk.FontSize = 1 + scale * 143;
    }
}

このコードで WinRT XAML と同じように フォント サイズが変化するアニメーションになります。

今度は、色をアニメーション化する ManualBrushAnimation プロジェクトの MainWindow.xaml の抜粋を示します。

<Grid Name="contentGrid">
    <TextBlock Name="txtblk"
               Text="Hello, Windows 8!"
               FontFamily="Times New Roman"
               FontSize="96"
               FontWeight="Bold"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />

</Grid>

色に基づいてアニメーションするコードも同じになります。MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        CompositionTarget.Rendering += OnCompositionTargetRendering;
    }

    void OnCompositionTargetRendering(object sender, object args)
    {
        RenderingEventArgs renderingArgs = args as RenderingEventArgs;
        double t = (0.25 * renderingArgs.RenderingTime.TotalSeconds) % 1;
        t = t < 0.5 ? 2 * t : 2 - 2 * t;

        // Background
        byte gray = (byte)(255 * t);
        Color clr = Color.FromArgb(255, gray, gray, gray);
        contentGrid.Background = new SolidColorBrush(clr);

        // Foreground
        gray = (byte)(255 - gray);
        clr = Color.FromArgb(255, gray, gray, gray);
        txtblk.Foreground = new SolidColorBrush(clr);
    }
}

今度は、 SolidColorBrush をコードではなく XAML で定義した ManualColorAnimation プロジェクトになります。MainWindow.xaml の抜粋を示します。

<Grid>
    <Grid.Background>
        <SolidColorBrush x:Name="gridBrush" />
    </Grid.Background>

    <TextBlock Text="Hello, Windows 8!"
               FontFamily="Times New Roman"
               FontSize="96"
               FontWeight="Bold"
               HorizontalAlignment="Center"
               VerticalAlignment="Center">
        <TextBlock.Foreground>
            <SolidColorBrush x:Name="txtblkBrush" />
        </TextBlock.Foreground>
    </TextBlock>
</Grid>

この XAML を使用する MainWindow.xaml.cs の抜粋を示します。

void OnCompositionTargetRendering(object sender, object args)
{
    RenderingEventArgs renderingArgs = args as RenderingEventArgs;
    double t = (0.25 * renderingArgs.RenderingTime.TotalSeconds) % 1;
    t = t < 0.5 ? 2 * t : 2 - 2 * t;

    // Background
    byte gray = (byte)(255 * t);
    gridBrush.Color = Color.FromArgb(255, gray, gray, gray);

    // Foreground
    gray = (byte)(255 - gray);
    txtblkBrush.Color = Color.FromArgb(255, gray, gray, gray);
}

もちろん、WPF でも WinRT XAML と同じように動作します。

最後に扱うサンプルは、虹色に変化するアニメーションである RainbowEight プロジェクトになります。最初に、MainWindow.xaml の抜粋を示します。

<Grid>
    <TextBlock Name="txtblk"
               Text="8"
               FontFamily="CooperBlack"
               FontSize="1"
               HorizontalAlignment="Center">
        <TextBlock.Foreground>
            <LinearGradientBrush x:Name="gradientBrush">
                <GradientStop Offset="0.00" Color="Red" />
                <GradientStop Offset="0.14" Color="Orange" />
                <GradientStop Offset="0.28" Color="Yellow" />
                <GradientStop Offset="0.43" Color="Green" />
                <GradientStop Offset="0.57" Color="Blue" />
                <GradientStop Offset="0.71" Color="Indigo" />
                <GradientStop Offset="0.86" Color="Violet" />
                <GradientStop Offset="1.00" Color="Red" />
                <GradientStop Offset="1.14" Color="Orange" />
                <GradientStop Offset="1.28" Color="Yellow" />
                <GradientStop Offset="1.43" Color="Green" />
                <GradientStop Offset="1.57" Color="Blue" />
                <GradientStop Offset="1.71" Color="Indigo" />
                <GradientStop Offset="1.86" Color="Violet" />
                <GradientStop Offset="2.00" Color="Red" />
            </LinearGradientBrush>
        </TextBlock.Foreground>
    </TextBlock>
</Grid>

WinRT XAML との違いは、何もありません。TextBlock 要素に LinearGridentBrush を設定していることも同じです(このブラシが、虹色に変化する雰囲気を醸し出します)。それでは、MainWindow.xaml.cs の抜粋を示します。

public partial class MainWindow : Window
{
    double txtblkBaseSize;  // ie, for 1-pixel FontSize

    public MainWindow()
    {
        InitializeComponent();

        Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        //txtblkBaseSize = txtblk.ActualHeight; // ActualHeight が異なるため
        txtblkBaseSize = txtblk.DesiredSize.Height;
        CompositionTarget.Rendering += OnCompositionTargetRendering;
    }

    void OnCompositionTargetRendering(object sender, object args)
    {
        // Set FontSize as large as it can be
        txtblk.FontSize = this.ActualHeight / txtblkBaseSize;

        // Calculate t from 0 to 1 repetitively
        RenderingEventArgs renderingArgs = args as RenderingEventArgs;
        double t = (0.25 * renderingArgs.RenderingTime.TotalSeconds) % 1;

        // Loop through GradientStop objects
        for (int index = 0; index < gradientBrush.GradientStops.Count; index++)
            gradientBrush.GradientStops[index].Offset = index / 7.0 - t;
    }

}

コメントを記述していることから違いを理解できると思いますが、WPF XAML では、このようなケースでは ActualHeight を使用するのは望ましくありません。このため、DesiredSize プロパティを使用して描画しようとするサイズを取得しています。DesiredSize プロパティは、レイアウトを描画する時の必要な描画サイズを計算した値になります。WinRT XAML では、ActualHeight プロパティで問題ないことから、WPF で ConpositionTarget.Rendering イベントで オブジェクトの描画サイズにアクセスするには、DesiredSize を使用すると覚えていただければ結構です。実行結果を示します。
RainbowEight

ここまで説明してた違いを意識しながら、第3章を読むことで WPF にも書籍の内容を応用することができるようになることでしょう。

ch03.zip

Comments (2)

  1. emoacht より:

    「3.6(P95) Run でのバインディング」について、自分の.NET4.5のWPFアプリではRunのTextプロパティにバインディングは普通にできました(Mode=OneWay指定が必要ですが)。

    原著者のペゾルドさんもWindowsストアアプリでのことだと思いますが、後から試したらできるようになっていたと書いておられます。http://www.charlespetzold.com/…/A-Data-Binding-to-the-Text-Property-of-Run.html

  2. shozoa より:

    emoacht さん、ご指摘くださって有難うございます。確認した後に、追記させていただきます。

    今後も気が付いたことがあれば、ご指摘ください。

Skip to main content