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

前回のエントリーで、WPF を学習するのに「プログラミング Windows 第6版」という書籍を紹介しました。この書籍は、Windows ストア アプリを中心にしている関係で、WinRT XAML という WPF XAML のサブセットを使った説明となっています。したがって、WPF XAML とは色々な面で異なる箇所があります。この異なる差異を乗り越えて WPF を学習するために、サンプルを移植しながら解説を試みるのが今回のテーマとなります。目標は、プログラミング Windows 第6版 の上巻をカバーすることで、下巻は自分で 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章 ページとナビゲーション
  14. プログラミング Windows 第6版 下巻に向けて

利用するサンプルは、以下で公開されています。

2014/12に追記
XAML の詳細を学習するには、C# を使用した Windows ストア アプリ開発概要ジャンプ スタートという動画を参考にするのも良いかもしれません。日本語の字幕はありませんがあり、書籍を持っていない方でも基本的な XAMLの使い方を学習するのに役に立つと考えられます。

今回の記事は、私がプログラミング Windows 第6版 の Windows 8.1 対応作業を実施した時に考えていた目的でもあります。WPF XAML を学習する書籍などで、最新環境(.NET Framework 4.5x)に対応した日本語の書籍がほぼ無い状況だったことから、Windows プログラミングを学ぶ上での王道とも言える書籍が WinRT XAML に対応したことが理由です(個人的には、時代の変遷を感じています)。せっかくなので、WPF XAML の学習に利用できたらと考えていました。

第1章 マークアップとコード

1.1(P3) 最初のプロジェクト

WPF アプリケーションを作成するには、Visual Studio 2013 を起動してから、[新しいプロジェクト]-[Visual C#]-[Windows デスクトップ]-[WPF アプリケーション] を選択します。
NewProjectWizard
※プロジェクト テンプレートには、「WPF アプリケーション」の他に「WPF カスタム コントロール ライブラリ」「WPF ユーザー コントロール ライブラリ」があります。

書籍では、Hello プロジェクトを作成し、プロジェクトの構造とコード、および XAML の説明が続きますので、WPF で作成した場合の基本的な要素を説明します。最初に、ソリューション エクスプローラーの状態を示します。
Ch01 Solution Explorer
Windows ストア アプリとの違いは、「Assetsフォルダー」、「Common フォルダー」、「Hello_Temporarykey.pfx」および「Package.appxmanifest」が無いことになります。また、参照設定の内容が大きく異なっています。具体的には、WPF では次の3 種類のアセンブリが参照設定されています。

  • PresentationCore.dll
  • PresentationFramework.dll
  • WindowsBase.dll

これは、Windows Forms アプリケーションが「System.Windows.Forms.dll」と「System.Drawing.dll」へ参照を持つことと同じで、WPF にとって必要なものだと理解すれば良いでしょう。

それでは、MainWindow.xaml.cs(WinRT XAMLでは MainPage.xaml.csです) のコードを次に示します。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Hello
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

 

WinRT XAML との大きな違いは、MainWindow クラスが Window クラスを継承しているところになります。WinRT XAML では、Window クラスを提供しておらず、Page クラスを使用するのが普通だからです。一方で、WPF ではナビゲーションを使用する場合のみに Page クラスを使用するだけで(具体的に第11章で説明する予定です)、基本になるウィンドウとして Window クラスを使用します。名前空間は、WinRT と違って System で始まっています。たとえば、System.Windows、System.Windows.Controls などになります。また、MainWindow クラスに partial キーワードが付与されている点に注意してください。これは、MainWindow.xaml がビルド時にパーシャル クラスである MainWindowのコードを生成し、生成したコードの中に InitializeComponent メソッドが含まれていることを意味するからです。

今度は、MainWindow.xaml を示します。

 <Window x:Class="Hello.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        Title="Hello" Height="350" Width="525" WindowState="Maximized">
    <Grid Background="Black">
        <TextBlock Text="Hello, Windows 8!"
                   FontFamily="Times New Roman"
                   FontSize="96"
                   FontStyle='Italic'
                   Foreground='Yellow'
                   HorizontalAlignment='Center'
                   VerticalAlignment='Center' />
    </Grid>
</Window>

 

ルート要素が、WinRT XAML と異なり Window になっています。また、WindowsState プロパティ(属性)に「Maximized(最大化)」が設定されている点にもご注意ください。これは、Windows ストア アプリが常に全画面であるのに対して、WPF は任意のウィンドウ サイズを設定できることから、意図的にフルスクリーンに設定しています。これ以外には、Grid 要素の Background属性を静的リソースから「Black」に変更しています。これは、WPF は WinRT XAML と違って標準でスタイル シートを組み込まないためです。次に XAML 構文で WinRT XAML との大きな違いは、名前空間の宣言方法になります(MainWindow.xaml には示していません)。

  • WinRT XAMLでは、「xmlns:local=”using Hello” 」
  • WPF XAML では、「xmlns:local=”clr-namespace:Hello”」
    より正確には、「xmlns:プレフィックス=”clr-namespace:使用する名前空間;assembly=アセンブリ名”」と指定します。

上記で示したように、自分で定義したクラスなどを使用する場合の名前空間の指定方法に大きな違いがあります。今度は、Windows Forms と比較したものを示します。

  • Form クラスに相当するのが、Window クラス
    Controls コレクション に相当するプロパティを Window クラスは持ちません。
    Text プロパティに相当するのが、Title プロパティになります。
  • Label クラスに相当するのが、TextBlock クラス
  • Grid クラスに相当するものは、Windows Forms にはありません。
    Controls コレクションに相当するプロパティとして Children があります。

次に、App.xaml のプロパティを示します。
Ch01 App.xaml Property

注目して欲しいのが、ビルド アクションが「ApplicationDefinition」になっていることです。次に、App.xaml.cs を示します。

 using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace Hello
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
    }
}

 

App クラスは、Application クラスを継承するだけで、コード自体は何も記述されていません。実は、App クラスが WPF アプリケーションのエントリー ポイントになっています。一般的にエントリー ポイントであれば、Main メソッドが存在します。実は、App.xamlのビルド アクションが「ApplicationDefinition」になっているのは、ビルド時にMainメソッドを自動生成するようになっているためなのです。エントリー ポイントで何らかの処理を行うのであれば、ビルド アクションを「Page」に変更して、自分で Main メソッドを定義する必要があります。これは、第1章の最後で説明します。Hello プロジェクトの実行結果を示します。
Hello
ウィンドウの形式を除けば、WinRT XAML と同じ結果が得られました。ここまでの説明で、WinRT XAML との大きな違いを以下の点に集約できると思います。

  • Window クラスを基本とする
  • 名前空間の記述方法が異なる
  • ウィンドウ サイズは、自分で設定する
  • 参照するアセンブリが異なる

今度は、Windows Forms の Form クラスと WPF のWindow クラス のサイズ関係の違いを次に示します。

  Form Window 説明
Width 幅(Size) 描画時の推奨値 描画時に指定する幅で、描画後には変化しません。
Height 高さ(Size) 描画時の推奨値 描画時に指定する幅で、描画後には変化しません。
ActualWidth 無し 実際の幅 描画された大きさです。
ActualHeight 無し 実際の高さ 描画された大きさです。

さらに、Window クラスには「SizeToContent」プロパティがあり、これを「WidthAndHeight」に設定することでWindow 内部に配置したコントロールに応じてウィンドウ サイズを決定することができるようになっています。また、WPF において Windows Forms と決定的に異なるのは座標の取り扱いにあります。

  • Windows Forms は、ピクセル座標を扱います。
  • WPFは、論理座標(1/96 DPI) を扱います。

たとえば、Width や Height プロパティには「Auto」という値を設定することもできます。これは、設定されたコントロールが持つコンテンツに応じて描画時の推奨サイズを決定してくださいという値になります。つまり、WPF では Width や Height を使ってレンダリング エンジンに対して描画して欲しいサイズを伝えることはできますが、最終的に描画されるサイズはコントロールが配置されたパネル コントロールなどによって変化することを意味します。よって、最終的に描画されたサイズを取得するには ActualWidth やActualHeight プロパティを使用するしかないことになります。これは、Windows Forms などの GDI 技術と大きく違う点になりますし、論理座標を使うことで 高DPI などに対応するスケーリングのメリットを受けられることに他なりません。

この節の最後では、ビルドした結果の生成物の違いを少しだけ説明します。WPF XAMLでは、ビルド結果として XAML が BAML という埋め込みリソースとして作成されます。BAMLは、XAMLをビルドして作成されたバイナリーになり、起動時間の短縮などを目的としています。一方で、WinRT XAML では、ビルド結果として XAML が XBF というパッケージ コンテンツ(Windows 8.1)になります。XBFは、BAML と同じ目的でWindows ストア アプリの起動時間などの短縮を目的としています。

1.2(P10) グラフィカルなプロジェクト:XAML

HelloImage プロジェクトの MainWindow.xaml の抜粋を以下に示します。

 <Grid Background="Black">
    <Image Source="https://www.charlespetzold.com/pw6/PetzoldJersey.jpg" />
</Grid>

 

XAML そのものは、WinRT XAML と同じになります。もちろん、すでに説明したように Page クラスを Window クラスに置き換えています。逆に、Windows Forms の PictureBox クラスが Image クラスに置き換わります。さらに、プロパティの構造も大きく異なります。たとえば、PictureBox では ImageLocation とImage プロパティがありますが、Image は Source プロパティのみになります。また、SizeMode プロパティに相当するものとして、Stretch プロパティがあります。Stretch プロパティは、書籍に説明がありますので内容をご確認ください。HelloImage の実行結果を次に示します。
HelloImage

次にローカル リソースである画像を使ったHelloLocalImage プロジェクトを説明します。 WPF でプロジェクト内に含める画像などのアセットは、2 種類の扱い方があります。1 つは、プロジェクトのリソースとする方法と、もう1つは WinRT XAML で説明しているコンテンツにする方法です。WPF では、コンテンツとは、ローカルに配置したファイルへアクセスすることと同じ意味になりますので、ここではリソースに設定しているプロパティを示します。
Ch01 LocalImage Property
プロジェクトに画像を配置して、ビルド アクションを「Resource」に設定するだけになります。こうすることで、出力するアセンブリ内に画像が埋め込まれることになります。この時の、MainWindow.xaml の抜粋を示します。

 <Grid Background="Black">
    <Image Source="Images/Greeting.png"
           Stretch="None" />
</Grid>

Source プロパティに URL を指定した時と同じように、埋め込みリソースを記述することもできます。リソースなどを指定する URI の詳細については、リソース(WPF)を参照してください。

1.3(P14) テキストのアレンジ

この節には、WrappedText、OverlappedStackedText、InternatinalHelloWorld プロジェクトがありますが、すでに説明した内容(Page を Windowへ、組み込みのスタイルを個別指定へ)で読み替えることで問題なく WPF に置き換えることができます。例として、実行結果だけを示します。
WrappedTextOverlappedStackedTextInternationalHelloWorld

1.4(P23) メディアを使ったプロジェクト

この節には、HelloAudio、HelloVideo プロジェクトがありますが、すでに説明した内容(Page を Windowへ、組み込みのスタイルを個別指定へ)で読み替えることで問題なく WPF に置き換えることができます。WinRT XAML の MediaElement クラスには、Windows 8.1 よりAreTransportControlsEnabled プロパティが追加されています。このプロパティは、WPF XAML はサポートしていませんので注意してください。

Windows Forms では、ビデオやオーディオを統合するには工夫をする必要があります。一方で、XAML 環境では MediaElement クラスを使うことで、アプリケーションに統合することが簡単にできます。

1.5(P25) コードを使ったプロジェクト

XAML で UI を定義することと同じことが、コードだけでも実現することができます。最初に HelloCode プロジェクトの MainWindow.xaml.cs(MainPage.xaml.cs)の抜粋を示します。

 public MainWindow()
{
    InitializeComponent();

    TextBlock txtblk = new TextBlock();
    txtblk.Text = "Hello, Windows 8!";
    txtblk.FontFamily = new FontFamily("Times New Roman");
    txtblk.FontSize = 96;
    txtblk.FontStyle = FontStyles.Italic; // FontStyle.Italic を変更
    txtblk.Foreground = new SolidColorBrush(Colors.Yellow);
    txtblk.HorizontalAlignment = HorizontalAlignment.Center;
    txtblk.VerticalAlignment = VerticalAlignment.Center;

    contentGrid.Children.Add(txtblk);

}

 

WinRT XAML との違いは、FontStyle プロパティに設定して値を変更したことです。WinRT XAMLでは、FontStyle列挙になるりますが、WPF では FontStyles 列挙となります。Windows Forms では、Font.Style プロパティを使用しますが、WPF では Font プロパティではなく、FontFamily、FontSize、FontStyle プロパティが用意されており、しかも、System.Drawing.FontStyle 列挙ではなく、System.Windows.FontStyles 列挙となります。これは、WPF が System.Drawing 名前空間を使用するのではなく、WPF 独自の名前空間を使用しているからです。クラス名や列挙名だけを比べると同じ名前が WPF にありますが、実態は別の名前空間で定義されていることがありますので、ご注意ください。

1.6(P28) グラフィカルなプロジェクト:コード

この節には、HelloImageCode、HelloLocalImageCode プロジェクトがあります。基本的には、すでに説明した内容(Page を Windowへ、組み込みのスタイルを個別指定へ)で読み替えることで問題なく WPF に置き換えることができます。しかしながら、HelloLocalImageCode プロジェクトでは、イメージへのパスを書き換える必要がありますので、MainWindow.xaml.cs の抜粋を示します。

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

        // パスを変更 ms-appx:///Images/Greeting.png
        image.Source = new BitmapImage(new Uri("Images/Greeting.png", UriKind.Relative));
    }
}

 

これは、WinRT XAML と WPF XAML におけるリソースの取り扱い方による違いになります。WinRT XAML プロジェクトでのイメージはビルド アクションがコンテンツに指定されており、アプリ パッケージ内のリソースとなっていることから、「ms-appx:///」プレフィックスを使用しています。これに対して、WPF XAML プロジェクトではHelloLocalImage プロジェクトと同じように埋め込みリソースにしています。このため、XAML に記述するとのと同じパス表記をすることができています。

1.7(P31) ページですらない

WinRT XAMLでは、Page クラスが基本ですが、WPF では Window クラスを基本にするという説明をすでにしました。このことをコードのみでページを作って説明するというのが、この節の主題になっています。ここでは、App.xaml.cs を示します。

 using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace StrippedDownHello
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        [STAThreadAttribute()]
        public static void Main(string[] args)
        {
            var app = new App();
            TextBlock txtblk = new TextBlock
            {
                Text = "Stripped-Down Windows 8",
                FontFamily = new FontFamily("Lucida sans Typewriter"),
                FontSize = 96,
                Foreground = new SolidColorBrush(Colors.Red),
                HorizontalAlignment = HorizontalAlignment.Center,
                VerticalAlignment = VerticalAlignment.Center
            };
            var window = new Window
            {
                Title = "StrippedDownHello",
                WindowState = WindowState.Maximized,
                Content = txtblk
            };

            app.Run(window);
        }
    }
}

 

すでに説明してきた考え方をコードで示しています。このコードは、次のようなことを行っています。

  • App クラスのインスタンスを作成。
  • TextBlock クラスのインスタンスを作成。
  • Window クラスのインスタンスを作成し、Content プロパティに TextBlock のインスタンスを設定。
  • App クラスのRun メソッドに Window クラスのインスタンスを引数として渡す。

こうすることで、UI 定義の XAML がないアプリケーションを作成することができます。今度は、このようなプロジェクトの作成手順を示します。

  • 新しいプロジェクトで、WPF アプリケーション テンプレートを選択してプロジェクトを作成します。
  • MainWindow.xaml を削除します。
  • App.xaml のプロパティでビルド アクションを「ApplicationDefinition」から「Page」に変更します。
  • App.xaml を開いて、StartupUri プロパティを削除します。
  • App.xaml.cs へ Main メソッドの定義と必要なコードを記述します。

このようにすることで、アプリケーションのエントリー ポイントである Main メソッドをビルド タスクに任せることなく自分で定義できるようになります。この方法を使えば、Window を表示する前に何らかの処理を行うことができるようになります。

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

ch01.zip