かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPF4.5入門 その21 「WPFのコンセプトと重要な機能つまみ食い」

過去記事

少し間が空いてしまいました。ちょっとJavaに浮気してましたがWPFは、まだまだ続きます!ここまでレイアウトコントロールについて詳しく見てきましたが少し飽きてきたので、ちょっと横道にそれてWPFのコンセプトと、個人的に重要だと思ってる機能について、少し紹介したいと思います。



WPFのコンセプト

WPFのコンセプトはMSDNによると「UI、メディア、およびドキュメントを組み込んだ Windows スマート クライアントの豊富なユーザー エクスペリエンスを構築するための、統一されたプログラミング モデルを開発者に提供すること」です。また、ASP.NETなどで取り入れられたテンプレートを使った柔軟なレイアウトの構築や、CSSのように見た目の定義を共通化する方法などのWebアプリケーションを開発する上での優れた仕組みなども取り込まれています。
私の感想として、WPFは当時のGUIを開発するためのプラットフォームのいい点や反省点をマイクロソフトが本気で検討して実装しなおしたテクノロジだと思います。その結果、その後のUIを開発するためのプラットフォームはSilverlightやWindows PhoneやWindows ストア アプリでもWPFと同じXAMLとC#による開発が主になっています。WPFを学習するということは、マイクロソフトの提供するプラットフォーム上でのUIの開発をするうえでWPFのノウハウを活用できるという点でもおすすめです。(細かい差異はたくさんありますが…)
このようなコンセプトを実現するためのキーとなる部分として、以下の3つをここで簡単に紹介します。

  • コンテンツモデルとデータテンプレート
  • スタイル
  • データバインディング

ここでは、こんなことが出来るんだという雰囲気をつかんでもらうことを目的としています。詳細な説明は、後半で行うのでそちらを参照してください。

コンテンツモデル

WPFでは、単一の要素を表示するコントロールとしてContentControlというものが定義されています。このコントロールは、ButtonやLabelなどの多くのコントロールの親クラスです。ContentControlにはContentという名前のobject型のプロパティが定義されていて、そこに設定されたクラスの型に応じて表示方法が切り替わります。表示ロジックは以下のようになっています。

  • UIElement型(ButtonやRectangleなどのコントロール)の場合はそのまま表示する。
  • ContentTemplateにデータテンプレートが設定されている場合は、それを使って表示する。
  • Contentプロパティに設定された型に対してデータテンプレートが定義されている場合は、それを使って表示する。
  • UIElement型へ変換するTypeConverterがある場合は、それを使ってUIElement型に変換して表示する。
  • string型へ変換するTypeConverterがある場合は、それを使って文字列に変換してTextBlockのTextプロパティに設定して表示する。
  • ToStringメソッドの呼び出し結果をTextBlockのTextプロパティに設定して表示する。

ここで特に重要なのは、Contentプロパティにコントロールが設定された場合は、そのまま表示できることと、その他のオブジェクトが設定された場合はデータテンプレートという仕組みを使って表示されることです。このシンプルな仕組みを理解することがWPFでデータを画面に表示する際にとても重要になります。

UIElementを表示する例

Buttonコントロールを使ってContentプロパティの表示を確認してみます。まずは、コントロールを設定した場合です。XAMLを以下に示します。

<Button HorizontalAlignment="Left" VerticalAlignment="Top">
    <StackPanel Orientation="Horizontal" Margin="5">
        <TextBlock Text="button" />
        <Image Source="btn.png" Stretch="None" />
        <TextBlock Text="button" />
    </StackPanel>
</Button>

これは、コードでも同じように記述できます。

var panel = new StackPanel
{
    Orientation = Orientation.Horizontal,
    Margin = new Thickness(5)
};
panel.Children.Add(new TextBlock { Text = "button" });
panel.Children.Add(new Image 
{ 
    Source = new BitmapImage(new Uri("btn.png", UriKind.Relative)),
    Stretch = Stretch.None
});
panel.Children.Add(new TextBlock { Text = "button" });
 
var b = new Button
{
    HorizontalAlignment = HorizontalAlignment.Left,
    VerticalAlignment = VerticalAlignment.Top,
    Content = panel
};

このボタンを表示すると以下のようになります。

文字列の表示

ToStringメソッドの呼び出し結果を表示できるので、当然文字列はそのまま表示できます。

<Button Content="文字列を設定" />

上記のXAMLで以下のようなボタンが表示されます。

オブジェクトの表示

任意のクラスを設定した場合の例を示します。例えば以下のようなAnimalクラスがあるとします。

using System.Windows.Media.Imaging;
 
public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }
    public BitmapImage Picture { get; set; }
}

このクラスをButtonのContentに設定して表示してみます。コードは以下のようになります(buttonObjectという名前のButtonクラスの変数があると仮定)。

// オブジェクトを作成
var anthem = new Animal
{
    Name = "アンセム",
    Age = 9,
    Picture = new BitmapImage(new Uri("/anthem.png", UriKind.Relative))
};
 
// ボタンに設定
this.buttonObject.Content = anthem;

このボタンの表示は以下のようになります。

ToStringメソッドの結果が表示されていることが確認できます。これに、ButtonコントロールのContentTemplateプロパティにデータテンプレートを設定してみます。XAMLを以下に示します。

<Button Name="buttonObject" HorizontalAlignment="Left" VerticalAlignment="Top">
    <Button.ContentTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Name}" />
                <TextBlock Text="{Binding Age, StringFormat={}{0}歳}" />
                <Image Source="{Binding Picture}" Stretch="None" />
            </StackPanel>
        </DataTemplate>
    </Button.ContentTemplate>
</Button>

テンプレートを設定すると、ToStringメソッドの結果だったものが以下のような表示になります。

データテンプレートを使うと、データの表示をとても柔軟に制御できることが感じていただけると思います。

スタイル

WPFでは、スタイルという仕組みを使うことでコントロールのプロパティの設定を複数のコントロールで共通化することが出来ます。こうすることで、アプリケーション全体で統一した見た目を定義することが簡単に出来るようになります。スタイルを設定するには、コントロールのStyleプロパティにStyleを設定します。Styleは、Setterというオブジェクトを使って、どのプロパティにどんな値を設定するか指定できます。
スタイルを使って、背景色を黒、前景色を白に設定したXAMLを以下に示します。

<Button Content="スタイルの例">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Black" />
            <Setter Property="Foreground" Value="White" />
        </Style>
    </Button.Style>
</Button>

このボタンを表示すると以下のようになります。

この方法だと、1つのボタンに直接スタイルを設定しているのであまり意味はありませんが、以下のようにリソースとしてスタイルの定義を外だしにすることで複数ボタンに共通のスタイルを設定することが出来ます。XAMLを以下に示します。

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <!-- スタイルをリソースに定義する -->
        <Style TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Black" />
            <Setter Property="Foreground" Value="White" />
        </Style>
    </Window.Resources>
    <StackPanel>
        <!-- 画面にボタンを複数置く -->
        <Button Content="スタイルの例1" />
        <Button Content="スタイルの例2" />
        <Button Content="スタイルの例3" />
        <Button Content="スタイルの例4" />
        <Button Content="スタイルの例5" />
    </StackPanel>
</Window>

先ほどButtonのStyleプロパティに直接設定されていたStyleの定義を外に移動させています。このWindowを表示すると以下のようになります。

複数のコントロールにスタイルが適用されていることが確認できます。

データバインディング

最後に、データバインディングの紹介をします。データバインディングは、コントロールのプロパティと任意のオブジェクトのプロパティを同期するための仕組みです。値の同期には、双方向と一方向が指定できます。

ソースの指定方法は、いろいろな方法がありますが一番よく使うのがコントロールのDataContextプロパティにソースとなるオブジェクトを設定する方法です。DataContextプロパティは、デフォルトで親の値を継承するためWindowのDataContextにソースとなるオブジェクトを設定するだけでWindow内のコントロールすべてのバインディングのソースとして設定できます。

バインディングの記述方法

バインディングは、XAMLで“{”と“}”で括った記法で書くのが一般的です。この記法はマークアップ拡張といわれるXAMLの記法です。ButtonコントロールのContentプロパティにDataContextに設定されているオブジェクトのFullNameプロパティの値をバインドするXAMLの例を以下に示します。

<Button Content="{Binding FullName}" />

Buttonを置いているWindowのDataContextに以下のようにオブジェクトを設定するとButtonのContentとバインドされます。

// DataContextにFullNameプロパティを持ったオブジェクトを設定
this.DataContext = new { FullName = "大田 一希" };

Windowを表示させると、以下のようにバインドできていることが確認できます。


今回は、ButtonコントロールのContentプロパティとバインドしましたがTextBoxコントロールのような、ユーザーの入力を受け取るコントロールとバインドすると、入力値をオブジェクトに反映することが出来ます。このように、宣言的に見た目と裏のデータを対応付け出来るため綺麗にデータと表示を分離することができます。コンテンツモデルとデータテンプレートの箇所のコード例でもバインディングを利用していましたが、データテンプレートと組み合わせることで自由自在にデータを画面に表示させることが出来るようになります。

まとめ

ここではWPFのコンセプトと、そのコンセプトを実現するために重要だと思うコンテンツモデル・データテンプレート・スタイル・データバインディングについて簡単に紹介しました。コンテンツモデルでは、単純なデータの表示から複雑なものの表示までが、とてもシンプルに実現できることを示しました。データテンプレートでは、オブジェクトを表示するための柔軟な仕組みがあることを示しました。スタイルでは、コントロールのプロパティを共通に定義することで、一貫した見た目のアプリケーションを簡単に作れることを示しました。データバインディングでは、コントロールのプロパティと、オブジェクトのプロパティを強力に結びつけることを示しました。


これらの機能は、WPFの膨大な機能のほんの一握りにすぎませんが、これだけで従来のデスクトップアプリケーションの開発では困難だった表現や、データと見た目を綺麗に分離したアプリケーションの開発が簡単に実現できることを感じ取って頂けたと思います。ここから先は、引き続きWPFの各機能の詳細について説明していきます。