プログラミング Windows 第6版 下巻に向けて

プログラミング Windows 第6版」という書籍を使用して、WPF の学習を補助するという目的で、上巻を WPF に置き換えて説明するというエントリーを以下のように記述しました。

  1. XAML とは何か
  2. 第1章 マークアップとコード
  3. 第2章 XAML 構文
  4. 第3章 基本的なイベント処理
  5. 第4章 パネルを使った表示
  6. 第5章 コントロールとのやりとり
  7. 第6章 WinRT と MVVM
  8. 第7章 非同期性
  9. 第8章 アプリ バーとポップアップ
  10. 第9章 アニメーション
  11. 第10章 座標変換
  12. 第11章 3つのテンプレート
  13. 第12章 ページとナビゲーション

そして、第1章の記事に書いた目標は、「下巻は自分で WPF に置き換えて学習ができるようになることです」ということでした。今回は、下巻に自分で取り組む場合の簡単な概要を示します。

  1. 第14章 ビットマップ
    WritableBitmap クラスを使用して、ビットマップ オブジェクトを作成する方法を説明しています。Windows 8.1 の話題では、RenderTargetBitmap を説明しています。
    WPF XAML でも WritableBitmap クラスを同様に使用することができます。そして、WritableBitmap クラスは、Render メソッドをサポートしていますから、Visual を継承する UIElement や FrameworkElement というビジュアルをビットマップ化することができます。WinRT XAML では、Render メソッドがサポートされていなかったために、Windows 8.1 より RenderTargetBitmap クラスが追加されたのです。つまり、WPF XAML で出来ていた機能が WinRT XAML で削除された、また追加されたということになります。この章は、自分でビットマップ オブジェクトを作成する場合に役立ちます。
  2. 第15章 ネイティブ機能へのアクセス
    この章では、Win32 API などへのアクセス方法を説明しています。というのは、WinRT XAML の環境では厳しく制限されている分野になるからです。
    WPF XAML は、デスクトップ アプリですから Windows Forms と同じように Win32 API や COM にアクセスすることができます。注意点としては、ウィンドウ ハンドル を使用する Win32 API の使用には制限があるという点になります。この点は、XAML とは何かでも解説しました。
  3. 第16章 リッチテキスト
    この章では、WinRT XAML の RichTextBlock と RitchEditBox コントロールを説明しています。
    WPF XAML では、RitchTextBox コントロールに置き換えると同じ機能を実現することができます。リッチ テキストを学習するには、最適な章になっています。
  4. 第17章 共有と印刷
    この章では、WinRT XAML における印刷と Windows 8 以降の機能である共有機能を説明しています。
    WPF XAML では、System.Printing 名前空間などに印刷に必要な機能がありますので、これに置き換えて学習する必要があります。もちろん、共有機能はありません。印刷については、「印刷および印刷システムの管理」に説明があります。
  5. 第18章 センサーとGPS
    この章では、WinRT XAML におけるセンサーや位置情報の使い方を説明しています。
    WPF XAML では、位置情報は  System.Device.Location 名前空間で用意されており、センサー API に関しては COM として用意されています。もちろん、Windows 7 までの OS では位置情報を検出するデバイスやプロバイダーが標準で含まれていない点に注意が必要です。しかし、センサーや位置情報を扱う基本的な考え方は、WinRT XAML と同じですから、本章は WPF XAML にも役立つことでしょう。
  6. 第19章 ペン入力
    この章では、WinRT XAML におけるペン入力を説明しています。
    WPF XAML では、「デジタル インク」で説明していますが、書籍の説明も考え方が同じですから役立つことでしょう。

第14章から第19章までの内容を考えれば、上巻で XAML に関する基本的な考え方が学習できていれば、上記に記載した点に注意しながら読み進めれば、ほとんどの内容が WPF XAML にも使えることが理解できることでしょう。重要なことは、XAML という UI 技術に関する基本的な知識を身に付けることにあります。そうすれば、後は .NET Framework のライブラリや C#/VB といった言語知識を使うことで応用することができることでしょう。

WPF に限りませんが、XAML 系 UI 技術の特徴として説明していない概念として、ビジュアル ツリー(VisualTree)があります。ビジュアル ツリーとは、XAML などで定義されたオブジェクトのメモリ構造であるオブジェクト グラフのことです。XAML は、XML で記述しますから、DOM(ドキュメント オブジェクト モデル)と説明した方が理解し易いかも知れません。つまり、ビジュアル ツリーを検索することで目的のオブジェクト(コントロールなど)を実行時に取得することができます。たとえば、第12章でコントロール テンプレートを説明していますが、コントロール テンプレートの各要素に名前を付与したとしても、プログラム内から名前で呼び出すことはできません。コントロール テンプレートには、次に示す特徴があります。

  1. テンプレート定義内の名前には、テンプレート オブジェクトからしかアクセスできません。
    定義時点では、テンプレート オブジェクトであってコントロールには適用されていません。
  2. コントロールがインスタンス化されてから、テンプレートが適用されます。
    FindName メソッド(FrameworkElementで定義)を使って、名前でテンプレート内の要素へアクセスできません。
    コントロールにテンプレートが適用されると、コントロールの子要素としてテンプレートで定義されたオブジェクトがビジュアル ツリーに追加されます。
  3. ビジュアル ツリーを使って、コントロールの子要素としてテンプレートで定義されたオブジェクトへアクセスができます。

ビジュアル ツリーへアクセスするには、VisualTreeHelper クラスを使用します。VisualTreeHelper クラスを使いやすくするための拡張メソッドなどが、WPF Toolkit では VisualTreeExtensions クラスとして定義されています。必要に応じて VisualTreeHelper クラスを使用すれば、より柔軟にオブジェクトへアクセスできるようになることでしょう。

今度は、第11章 3つのテンプレート の中で MSDN マガジンの記事である「テンプレートを使用した WPF コントロールのカスタマイズ」より引用した SpringLoadedScrollBar サンプルでは実行結果だけを示しました。

このサンプルと書籍の SplingLoadedSlider サンプルでは、大きな違いとして Orientaion プロパティの Vertical のテンプレートが無い点があります。これは、WinRT XAML の Slider コントロール テンプレートにおいて、横向きのテンプレートは HorizontalTemplate であり、縦向きのテンプレートは VerticalTemplate という名前を持っており、同じ名前でテンプレートを上書きするという手法で実現しています。WPF XAML の ScrollBar コントロール テンプレートの場合は、横向きや縦向きのテンプレートを名前では区別していないので、WinRT XAML とは別のアプローチを使用しないと、同じようなテンプレートを作成することができません。縦向きのテンプレートも作成したので、その XAML の抜粋を示します。

 <Window ... >
    <Window.Resources>
        <Style x:Key="springScroll" TargetType="{x:Type ScrollBar}">
            <Style.Resources>
                <!-- Make all the RepeatButtons non-focusable -->
                <Style TargetType="{x:Type RepeatButton}">
                    <Setter Property="Focusable" Value="False" />
                    <Setter Property="IsTabStop" Value="False" />
                </Style>
                <!-- Define a template for the buttons on each end -->
                <ControlTemplate x:Key="templateArrow"
                                 TargetType="{x:Type RepeatButton}">
                    ...
                </ControlTemplate>
                <!-- Define a template for the central buttons -->
                <ControlTemplate x:Key="templateSpring" 
                                 TargetType="{x:Type RepeatButton}">
                    ...
                </ControlTemplate>
            </Style.Resources>
            <Setter Property="Width" Value="50" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ScrollBar}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="50" />
                                <RowDefinition Height="1*" />
                                <RowDefinition Height="50" />
                            </Grid.RowDefinitions>
                            <!-- Line-top button on left side -->
                            <RepeatButton Grid.Row="0" 
                                          Command="ScrollBar.LineLeftCommand"
                                          Foreground="{TemplateBinding Foreground}"
                                          Template="{StaticResource templateArrow}"
                                          LayoutTransform="0 1 1 0 0 0" />
                            <!-- Named track occupies most of the ScrollBar -->
                            <Track Grid.Row="1" Name="PART_Track" IsDirectionReversed="True" Orientation="Vertical" >
                                <Track.DecreaseRepeatButton>
                                    <RepeatButton Command="ScrollBar.PageUpCommand" Width="Auto"
                                                  Foreground="{TemplateBinding Foreground}"
                                                  Template="{StaticResource templateSpring}"
                                                  LayoutTransform="0 -1 1 0 0 0"/>
                                </Track.DecreaseRepeatButton>
                                <Track.IncreaseRepeatButton>
                                    <RepeatButton Command="ScrollBar.PageDownCommand" Width="Auto"
                                                  Foreground="{TemplateBinding Foreground}"
                                                  Template="{StaticResource templateSpring}"
                                                  LayoutTransform="0 -1 1 0 0 0"/>
                                </Track.IncreaseRepeatButton>
                                <Track.Thumb>
                                    <Thumb Background="{TemplateBinding Foreground}" />
                                </Track.Thumb>
                            </Track>
                            <!-- Line-bottom button on right side -->
                            <RepeatButton Grid.Row="2" 
                                          Command="ScrollBar.LineRightCommand"
                                          Foreground="{TemplateBinding Foreground}"
                                          Template="{StaticResource templateArrow}" 
                                          LayoutTransform="0 -1 1 0 0 0" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Orientation" Value="Horizontal">
                    <Setter Property="Width" Value="Auto" />
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ScrollBar}">
                                ... <!-- サンプルの定義と同じ -->
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>

    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <ScrollBar Style='{StaticResource springScroll}'
                   Grid.Row='0'
                   Orientation='Horizontal' Margin='48' 
                   Minimum='-50' Maximum='50' SmallChange='1' LargeChange='10'
                   Foreground='SteelBlue' Height='50' />
        <ScrollBar Style='{StaticResource springScroll}'
                   Grid.Row='1' 
                   Orientation='Vertical' Margin='48' HorizontalAlignment='Left'
                   Minimum='0' Maximum='50' SmallChange='1' LargeChange='10'
                   Foreground='HotPink' Height='Auto' />
    </Grid>
</Window>

この XAML のポイントを、次に示します。

  • ControlTenmplate 単独ではなく、Style として定義。
  • Template プロパティに縦向きの ControlTemplate を定義。
    要素を LayoutTransform で座標変換(縦向きになるように)。
    Track 要素に IsDirectionReversed 属性と Orientation 属性を指定。
  • Style.Triggers プロパティで、Triger 要素を定義して、Orientation プロパティが Horizontal の時のテンプレートを定義。
    このようにした理由は、ScrollBar コントロール テンプレートが、Style.Triggers で Horizontal を定義していたので同じにしました。

それでは、実行結果を示します。
SpringLoadedScrollBar2

書籍と同じように、縦向きのコントロール テンプレートが定義できていることを確認することができます。書籍の SplingLoadedSlider サンプルでは、スライダーのバネの左側と右側で色を変えていますが、示した XAML を理解できれば、同じように色を変更することもできることでしょう。このように Style の Triggers プロパティを使用すれば、コントロールのプロパティに応じたコントロール テンプレートに切り替えることもできるようになります。この機能は、WinRT XAML の Style クラスにはありません。つまり、Triggers プロパティを WinRT XAML はサポートしていないのです。ですから、WinRT XAML の Slider コントロールでは、縦向きと横向きのテンプレートに名前を付けて、プロパティによって切り替えるようになっているとも言えます。これなども、WPF XAML と WinRT XAML のアプローチの違いであると考えることができます。この一連の記事の最初に説明しましたが、WPF XAML はフルセットの高機能型であり、WinRT XAML は Silverlight などのサブセットのフィードバックなどから、よりコンパクトでありながら、使いやすさの面で改良されたものであることから、このようなアプローチの違いがあるのです。このような違いは、ドキュメントやコントロール テンプレートを自分で調べれば、すぐに見つけられることでしょう。そのために必要な情報は、書籍と今回の一連の記事の中で説明してきているからです。

それでは、プログラミング Windows 第6版 を使った WPF の説明は、今回で終了となります。是非、WPF や Windows ストア アプリを作ってみてください。

追記
WPF がリリースされたのは、Windows Vista に含まれる .NET Framework 3.0 になります。その後に、Windows 7 に含まれる .NET Framework 3.5 で機能強化が行われています。また、このタイミングで .NET Framework SDK と Windows SDK の統合が行われています。たとえば、LocBaml ツールのサンプルに記載されているサンプルが Windows SDK に含まれています。これ以外のサンプルも、Windows SDK に含まれています。WPF などのサンプルが含まれた Windows SDK は、Microsoft Windows SDK for Windows 7 and .NET Framework 3.5 SP1となります。Microsoft Windows SDK for Windows 7 and .NET Framework 4 には、.NET Framework のサンプルが含まれていないのでご注意ください。以前の Windows SDKをダウンロードするには、Windows SDK Archive のページを参照してください。
また、センサーなどを使用する場合は Window API Code Pack の使用をご検討ください。