WPF 入門 第 1 回愈々 WPF の時代 WPF の習得を始めよう WPF(Windows Presentation Foundation) は.NET Framework に含まれるプレゼンテーション層技術 (GUI 開発ライブラリ ) で有る WPF はバージョン 3.0 以降の.NET Framework に標準搭載されて居る 其れより前の GUI 開発ライブラリで有る Windows フォームが 単に Win32 API をマネージコードでラップした物で有るのに対して WPF はマネージコードで新たに実装された GUI 開発ライブラリで有り 豊かなユーザー体験を提供する先進的な GUI 開発基盤で有る ( 詳細後述 ).NET Framework が 3.0 3.5 4 とバージョンアップし WPF は既に 3 世代目を迎える 此れ迄 Windows フォームからの移行が中々進まなかった WPF だが 標準で提供される機能も増え Visual Studio に依る開発サポートも充実し 実用するのに必要充分な環境が漸く整った 愈々 WPF の時代が遣って来たと謂っても過言では無いだろう 其処で 本稿を通じて 是非共 WPF を学んで欲しい 今回は WPF の概要に付いて説明する WPF の特徴と利点 グラフィックスハードウェア WPF は コアの部分にグラフィックスハードウェアを活用したベクターベースのレンダリングエンジンを採用して居る ベクターベースで有る為 UI 要素にスムーズな拡大 縮小 / 回転を掛ける事が出来る 亦 ハードウェアアクセラレーションに依り CPU への負担を最小限に抑えて居る コントロール メディア 文書の統合 WPF は ボタンやリストボックス等のコントロール ラスター画像やベクターグラフィックス 頂点メッシュを用いた 3D 描画 動画等のメディア リッチテキスト等の整形済み文書に対して 統一的な開発機能を提供する 夫々に別個のプログラミングモデルを覚える必要が無い丈では無く 例えば ボタンの中に動画を表示すると謂った組み合わせも簡単に行える UI カスタマイズの柔軟性 既存の GUI 開発ライブラリでは ボタン等の UI 要素をカスタマイズするにしても サイズや背景色の変更程度の機能しか持って居ない物も多い 此れに対して WPF では 例えば任意の形状のボタンを作成したり 背景に動画を流したり 動的に回転や拡大 縮小を行ったりと謂った非常に柔軟なカスタマイズが可能で有る 観た目 ( 外観デザイン ) とロジックの分離 WPF では XAML(Extensible Application Markup Language) と呼ばれる XML 形式の宣言的言語を用いてユーザーインターフェイスを記述する 観た目 ( 外観デザイン ) に関する部分を XAML 言語 -1-
で記述し ロジックを C# 言語等を用いて記述する事で アプリケーションの観た目に関する部分をロジックから完全に切り離す構造に成って居る 此れは ビジュアルデザイナーとロジック開発者との協業を意識した物で有る プログラミングモデル WPF では 以下の様なプログラミングモデルに基づいた開発を行う事に成る XAML コード + 分離コード ツリー構造の UI 要素 データバインディング プログラミングモデルに関して 1 つ特筆す可き事が有る クロスプラットフォームな RIA(Rich Internet Application) 開発環境で有る Silverlight も WPF と同じプログラミングモデルに基づいて居ると謂う点だ WPF と Silverlight では 流石にソースコードの丸々コピーで動くと謂う事は無いが プログラミングモデルにさえ慣れ親しんで了えば相互の移植は簡単で有る 其れでは 此等の詳細付いて説明して行く事にする XAML コード + 分離コード 前述した通り WPF では XAML 言語と C# 等のプログラミング言語を用いてアプリケーションを開発する XAML 言語で記述した観た目の部分に加えて ロジックが必要な場合には Figure 1 に示す様に C# 等で記述する ( 分離コード と呼ぶ ) Figure 1: XAML コード +(C# 等の ) 分離コード XAML コードと 其れに付随する (C# 等の ) 分離コードは ビルド時に 1 つのクラスに合成され ( 部分クラスと謂う機能を用いて居る ) コンパイルされる 例として Page1 と謂う名前のビューを作った場合 ビルド時の流れは Figure 2 の様に成る -2-
Figure 2: XAML コード +(C# 等の ) 分離コードのビルドの流れ 図中に登場する BAML は Binary Application Markup Language と呼ばれる物で ファイルサイズの縮小と読み込み負荷の軽減を目的として XAML コードをバイナリ化した物で有る BAML コードはリソースとして実行可能ファイル (.exe ファイル ) に埋め込まれる 亦 中間生成物の.g.cs ファイルは XAML コード内で定義した UI 要素 ( ボタンやテキストボックス等 ) を (C# 等の ) 分離コード側から参照する為のコードや BAML コードを読み込む為のコードを含んで居る 此の様な XAML コードと (C# 等の ) 分離コードを分ける事には以下の利点が有る 1. ビジュアルデザイナー向けのツールを用いた外観デザインが容易 2.UI 要素の階層が深く成った場合に ( 一般的なプログラミング言語よりも ) 階層構造を把握し易い 3.Web 開発で一般的に用いられて居る HTML+JavaScript に似た感覚でアプリケーションを開発可能 4.C# の様なプログラミング言語では書けない 或いは 書き難い記述が容易 1 と 2 は XML 形式を用いる事に依る利点で有る XML のコードはツールでの解析が容易で有り 亦 階層的なデータ構造を取り扱うのに向いて居る 3 に関して Web 開発に於ける HTML+JavaScript の流行は 観た目とロジックの分離が容易で有る と謂う利点が支持されたと謂う側面が有る WPF の XAML+ 分離コード (C# 等 ) と謂う構成は此の流れを汲む物だ 4 に関しては 詳細は後述する事に成るが 依存関係プロパティやデータバインディングと呼ばれる機能が此れに該当する 続いて ツリー構造の UI 要素を説明する ツリー構造の UI 要素 Figure 3 に模式的に表す様に WPF の UI 要素はツリー構造に依り構築される -3-
Figure 3: ツリー構造の UI 要素 此の例では Canvas 要素を入れ子にして居るが WPF ではボタン等のコントロールの中身も入れ子にする事が出来る 例えば ボタンの中に動画を配置したり コンボボックスの中にボタンを並べたりする事も可能だ 此の様な WPF のツリー構造の UI 要素では 要素の親子関係に依って以下の様な事が起こる 平行移動や回転等の変形は 親要素からの相対位置に基づいて 子要素諸共行われる 添付プロパティと謂う仕組みを用いて 要素自身ではなく 親要素が使う情報を持てる FontSize 等 一部のプロパティは親要素から値を継承する ( 包含継承 ) 先ず Figure 4 に平行移動の例を示す 亦 Figure 4 の平行移動に加えて 回転も掛けた物を Figure 5 に示す ( 簡便性の為 回転に関係の無いプロパティは省略 ) 孰れも 位置指定 (Canvas.Left 属性や Canvas.Top 属性等 ) は親要素からの相対指定と成って居る 亦 回転は子要素共々行われて居る事が解る Figure 4: UI 要素の平行移動 -4-
Figure 5: UI 要素の回転 此処で Figure 4 の例に注目して欲しい 入れ子に成った Canvas 要素や Rectangle 要素が Canvas.Top 属性と Canvas.Left 属性を持って居る事に気付くだろう 此等の属性値は 要素自身ではなく 1 段上の親に当たる Canvas 要素が使用する 此の親 Canvas 要素は 座標を指定して任意の位置に子要素を配置する為のコンテナだが 其の子要素の座標指定は子要素に Canvas.Left 属性と Canvas.Top 属性を付与する事で行う 此の様な 自分自身ではなく 親要素が使用する値を子要素に付与する為の仕組みを 添付プロパティ (Attached Property) と呼ぶ 通常の意味での ( 詰まり.NET のクラスの ) プロパティでは 此の仕組みを素直に実現する事は出来ない 勿論 総ての UI 要素が Left プロパティ及び Top プロパティを持つ事でも可能では有るが 然うして了うと Canvas 要素を利用しない場合に無駄なデータを持つ事に成って了う 其処で WPF では 添付プロパティや後述するデータバインディングを実現する為の機能として 依存関係プロパティ (Dependency Property) と謂う仕組みを持って居る 依存関係プロパティの詳細に付いては次回以降で説明する事に成るが 簡単に謂うと 一種の辞書構造を用いて値を保持する仕組みで有る 扨て 最後に 値の包含継承に付いて説明して置く HTML コードを書いた事が有るなら解るだろうが HTML では親要素に書いた属性値が子要素に継承される事が有る 例えば body 要素に対して style 属性や font 属性でフォントサイズを指定すると body 要素内の総ての要素のフォントサイズが変化する WPF でも此の様な仕組みを採用して居り FontSize プロパティ等 一部のプロパティ値を ( プロパティ定義時の指定次第で ) 親要素から継承する様に成って居る オブジェクト指向に於ける 継承 ( クラスの継承 ) と区別する為に 此れを 包含継承 と呼ぶ データバインディング WPF では ビュー ( データの表示 ) とモデル ( データの不整合が無いかの検証や データベース等へのデータの永続化 ) を分離する為の仕組みとして データバインディングと謂う機能を提供して居る データバインディングでは ビュー側には 此処に此のデータを表示し度い と謂う様な目印丈を入れ 実際のデータは外部から与える事に成る -5-
Figure 6: データ バインディングの例 Figure 6 に例示する様に XAML コード内で 属性値に Binding と謂う記述を行う 実際に表示し度いデータは DataContext と謂うプロパティを介して渡す事に成る 例えば 上記の例の場合 DataContext プロパティに渡された Point 型データの X プロパティと Y プロパティの値を Button 要素の Content 属性に結び付ける 値の結び付けはリフレクションを用いて行われて居り DataContext には任意の型を渡す事が出来る 此の仕組みを旨く利用すれば ビューとモデルの接点は DataContext プロパティの只 1 点而巳と成り 疎結合が実現出来る 亦 ビューの内部には一切ロジックを持つ必要が無い ( 即ち ビューの分離コード内にはロジック用のコードを一切書かない ) 詳細は次回以降の説明と成るが モデル側で データの更新通知や ユーザーから入力されたデータの妥当性検証を行う仕組みが提供されて居る (INotifyPropertyChanged インターフェイスや依存関係プロパティと謂う仕組み等を用いる ) Figure 7 にデータの更新通知の例を示す 此の例では X や Y の値はテキストボックスとスライダコントロールで共有されて居て 片方の値が変更されると即座に他方に値の変更が通知され ビューに変更が反映される 亦 X と Y の値の変化と連動して X+Y の値の変化も即座にビューに反映される Figure 7: データの更新通知の例 -6-
此処で WPF の UI 要素を幾つか紹介して置く ( 詳細は次回以降説明する ) WPF の UI 要素には 大まかに分類すると Table 1 の様な物が有る 種類説明 UI 要素の例 コントロール コンテナ シェイプ ボタンやリストボックス等のコントロール類 子要素の配置を決める為に用いる ベクターグラフィックスを描画する為の要素 Button TextBox ComboBox StackPanel WrapPanel DockPanel Canvas Grid Rectangle Ellipse Path メディア静止画や動画等 Image MediaElement ドキュメント文書整形 RichTextBox Table 1: WPF の UI 要素の分類 標準コントロール WPF では 標準で様々なコントロールが用意されて居る Figure 8 に幾つか代表的な物を示す ウィンドウ左側に有るのは 上から順に Menu Button CheckBox ComboBox RadioButton Slider ListBox StatusBar コントロールで有る 亦.NET Framework 4 で新たに追加されたコントロールも幾つか有り Figure 8 の右側に有る Calendar コントロールも其の 1 つだ Figure 8: 標準コントロールの例 其の他にも.NET Framework 4 で 待望の DataGrid コントロールが追加された WPF ではデータバインディングとデータテンプレートの仕組みが強力で ListView と謂うコントロールを用いる事で柔軟なデータ表示が可能だった 併し 開発の仕易さと謂う観点から謂うと ListView コントロールは少し面倒で Windows フォームに於ける DataGrid コントロールの様なドラッグ & ドロップ開発の容易なコントロールが待ち望まれて居た 其の DataGrid コントロールが WPF にも追加された訳で有る Figure 9 に DataGrid コントロールの利用例を示す 此処では SQL Server にサンプルとして付いて来る Northwind データベースの Customers テーブルの内容を DataGrid コントロールで表示して居る -7-
Figure 9:DataGrid コントロールの例 WPF Toolkit 標準コントロール以外にも WPF Toolkit と謂う形で幾つかのコントロールが提供されて居る WPF Toolkit は開発途中のコントロールを一早く開発者が試せる様に マイクロソフトが CodePlex と謂うサイトに於いてオープンソースとして提供して居る物で有る WPF Toolkit に含まれて居るコントロールは 安定した物から順に.NET の標準ライブラリに取り入れられて行く 前述の DataGrid コントロールや Calendar コントロールも 元々は WPF Toolkit の一部として提供されて居た物が.NET Framework 4 で標準コントロールに採用された物だ 現在 WPF Toolkit として而巳提供されて居るコントロールには グラフ表示の為のチャートコントロール等が有る コンテナ WPF では UI 要素の配置を柔軟に制御する為に 幾つかのコンテナコントロールが用意されて居る 標準では 以下の様なコンテナが有る StackPanel: 要素を縦又は横に並べて配置する WrapPanel:HTML のインライン要素と同じ様に 画面の幅に合わせて自動的に折り返す DockPanel:UI 要素を上下左右に貼り付ける形で配置する Grid:UI 要素を格子状に配置する Canvas:UI 要素を 座標や幅 高さの値を明示的に指定して配置する シェイプ 本稿の冒頭で説明した様に WPF ではコアの部分にベクターベースのグラフィックエンジンを採用して居る コントロールがベクターベースで描画されて居るのは勿論 プリミティブなベクターグラフィ -8-
ックスを表示する為の UI 要素も提供されて居る 例えば 長方形を描画する Rectangle 要素や 楕円を描画する Ellipse 要素等が有る メディア WPF では静止画を表示する為の Image コントロールや 動画や音声を再生する為の MediaElement コントロールが標準で提供されて居る 此等は 他の UI 要素と統合されて居て 混在させて使ったり 他の UI 要素の中に表示したりと謂った事も簡単に実現出来る 亦 WPF が標準で提供する変形機能を用いて 動画を拡大 縮小 回転すると謂った事も可能だ ドキュメント WPF は Figure 10 に示す様な文書整形機能も標準で備えて居る Figure 10:WPF の文書整形機能 次回からは XAML に付いて基礎から詳しく説明する予定だ -9-
第 2 回 WPF と XAML の関係 ~ XAML の基礎を学ぶ 前回説明した様に XAML(Extensible Application Markup Language) と呼ばれる宣言的言語が WPF アプリケーション開発の中核と成って居る 勿論 (XAML を使わない )C# や Visual Basic 等のプログラミング言語丈を用いた WPF アプリケーション開発も可能では有るが XAML の利用には様々な利点が有り 実質的には WPF アプリケーション開発に於いて XAML に関する知識が必須で有ると謂える 其処で 今回から数回に亘り XAML に関する解説を行って行く 今回は 先ず WPF と XAML の関係性に付いてと XAML の基本的な部分 ( 特に WPF に依らない XAML 固有の部分 ) に付いて解説する 亦 本稿の最後では XAML 構文に付いて纏める WPF と XAML の関係性 本題に入る前に 簡単に XAML の実態に付いて触れて置こう XAML=CLR オブジェクトのインスタンス生成 此れ迄 WPF アプリケーションの視覚的な部分は XAML を使って記述する と説明して来た 併し XAML は WPF の為丈の物か? と謂うと 然うではない 実の処 XAML と謂うのは CLR に於けるオブジェクトのインスタンス ( 以降 CLR オブジェクト ) を生成する為のマークアップ言語 で有る 従って WPF 以外にも 例えば WF(Windows Workflow Foundation) でも ワークフローの記述に XAML を利用して居る CLR オブジェクトと XAML コードの双方向変換 C#/VB コードに依りユーザーが定義した型は XAML 形式でシリアライズ / デシリアライズする事が可能で有る 詰まり C#/VB コードに依る CLR オブジェクトのインスタンス生成 は XAML コード として保存出来る 例えば 以下の C#/VB コードは ユーザー定義の Point クラス (CLR オブジェクト ) を XAML コードにシリアライズするサンプルプログラムで有る Visual Basic Imports System.IO Imports System.Windows.Markup Namespace Sample Public Class Point Public Property X As Integer Public Property Y As Integer End Class End Namespace Module Program Sub Main() Dim x = New Sample.Point With.X = 1,.Y = 2 Using stream = File.Open("test.xaml", FileMode.Create) -10-
XamlWriter.Save(x, stream) End Using End Sub End Module Visual C# using System.IO; using System.Windows.Markup; namespace Sample public class Point public int X get; set; public int Y get; set; class Program static void Main(string[] args) var x = new Sample.Point X = 1, Y = 2 ; using (var stream = File.Open("test.xaml", FileMode.Create)) XamlWriter.Save(x, stream); List 1: ユーザー定義型のインスタンスを XAML コードとして保存するサンプルコード コンソールアプリケーションとして作成する ビルドするには PresentationFramework アセンブリ WindowsBase アセンブリ System.Xml への参照を追加する必要が有る 此のコードを実行した結果として得られる XAML コードは 以下の様な物に成る <Point X="1" Y="2" xmlns="clr-namespace:sample;assembly=samplelibrary" /> List 2: 上記のコードに於ける Point クラスのインスタンスを XAML コード化した結果 SampleLibrary の部分には 実際には Point クラスが定義されて居るアセンブリ名が入る 逆に XAML コードから CLR(Common Language Runtime) オブジェクトにデシリアライズするには XamlReader.Load メソッドを用いる XAML を使わない WPF アプリケーション 亦 XAML で書ける物は 原理的に C#/VB 等の任意の.NET 言語丈で書く事も出来る WPF の場合も例外ではなく 余り一般的ではないが C#/VB 等の.NET 言語而巳で WPF アプリケーションを作成可能で有る -11-
例えば Visual Studio の [ 新しいプロジェクト ] ダイアログで WPF アプリケーションを作成した後 最初からプロジェクトに含まれて居る App.xaml ファイルや MainWindow.xaml ファイル等を一度総て削除して MainWindow.cs/MainWindow.vb ファイルを新規作成し 其のファイルに以下の様な C#/VB コードを記述して観て欲しい Visual Basic Imports System.Windows Imports System.Windows.Controls Public Class MainWindow Inherits Window Public Sub New() Dim panel = New StackPanel Dim menuctrl = New Menu menuctrl.items.add(new MenuItem With.Header = " メニュー ") panel.children.add(menuctrl) panel.children.add(new Button With.Content = " ボタン ") panel.children.add(new CheckBox With.Content = " チェックボックス ") Dim comboctrl = New ComboBox With.SelectedIndex = 0 comboctrl.items.add(" コンボボックス ") panel.children.add(comboctrl) panel.children.add(new RadioButton With.Content = " ラジオボタン ") panel.children.add(new Slider()) Dim listctrl = New ListBox listctrl.items.add(" リストボックス項目 1") listctrl.items.add(" リストボックス項目 2") panel.children.add(listctrl) Dim gridctrl = New Grid gridctrl.children.add(panel) Me.Content = gridctrl End Sub <STAThread()> Shared Sub Main() Dim app = New Application() app.run(new MainWindow With.Title = " サンプル ",.Width = 300,.Height = 300) End Sub End Class Visual C# using System; using System.Windows; using System.Windows.Controls; public class MainWindow : Window -12-
public MainWindow() this.content = new Grid Children = new StackPanel Children = new Menu Items = new MenuItem Header = " メニュー ", new Button Content = " ボタン ", new CheckBox Content = " チェックボックス ", new ComboBox Items = " コンボボックス ", SelectedIndex = 0, new RadioButton Content = " ラジオボタン ", new Slider(), new ListBox Items = " リストボックス項目 1", " リストボックス項目 2", ; [STAThread] static void Main(string[] args) var app = new Application(); app.run(new MainWindow Title = " サンプル ", Width = 300, Height = 300, ); List 3:XAML を使わない WPF アプリケーションの例 上記のコードは問題なくビルド可能で 実行すると Figure 1 の様なウィンドウが起動する -13-
一般的な WPF アプリケーション 前置きが長く成ったが 一般的な WPF アプリケーションの場合を観てみよう Visual Studio の [ 新しいプロジェクト ] で WPF アプリケーション テンプレートを使って WPF アプリケーションプロジェクトを作成すると Figure 2 の様な状態が得られる ( 此の例では プロジェクト名は SampleWpfApplication として居る ) Figure 2:Visual Studio で WPF アプリケーションプロジェクトを作成した直後の状態 此処で WPF に不慣れな人は以下の 2 点を疑問に思うかもしれない (1)Main メソッドがない (2) 何処にも定義が見当たらない InitializeComponent と謂うメソッドを呼び出して居る 前回説明した様に WPF では XAML コードから BAML コードと C# コードを生成して居る (VB のプロジェクトテンプレートを使った場合には 当然 BAML コードと VB コードが生成される ) Main や InitializeComponent 等のメソッドは XAML コードから自動生成された C# コードの中で定義されて居る 此の XAML コードからの BAML コード及び C# コードの生成は Visual Studio( 正確には付属のビルドツールで有る MSBuild) が担って居る Figure 3 に示す様に XAML ファイルのプロパティを表示すると [ ビルドアクション ] が Page に成って居る事が解る -14-
MainWindow.xaml ファイルに対する Page ビルドアクションの中間生成物と成る BAML コード及び C# コードは プロジェクトフォルダの下の obj フォルダの中に 夫々 MainWindow.baml 及び MainWindow.g.cs と謂うファイル名で出力されて居る 一度ファイルの中身を観てみるのも良いだろう 同様に App.xaml ファイルに対しても App.baml 及び App.g.cs と謂うファイルが生成されて居る筈だ 生成された.g.cs ファイル中に書かれて居るコードは 要約すると以下の様な物で有る Main メソッド内で App クラスのインスタンスを生成し 其の Run メソッドを実行する ( 起動時に表示されるウィンドウは App.xaml ファイルで <Application> タグの StartupUri 属性に設定された MainWindow.xaml ファイルの物 ) MainWindow.xaml ファイル内で Name 属性付きで書かれた UI 要素を MainWindow クラスのメンバ変数として定義 ( WPF ウィンドウ上に UI 要素を配置した場合 ) MainWindow クラスの InitializeComponent メソッド内で BAML コードをロードする 其れでは XAML の基礎に付いて説明して行く XAML の基礎 此処では 特に WPF に依存しない インスタンス生成用のマークアップ言語 としての XAML を中心に説明する XAML の XML 要素 / 属性と 対応する CLR オブジェクト 詳細に付いて話す前に先ず XAML コードと 其れと粗等価の CLR オブジェクトを生成する C#/VB -15-
コードに付いて幾つかの例を挙げる事で 大まかなイメージを掴んで貰い度い List 4~List 10 に其の例を示す ユーザー定義クラスのインスタンス生成例 List 4 は (XAML コードに於ける <Point> 要素から ) ユーザー定義クラス Point のインスタンス (CLR オブジェクト ) が生成される例で有る XAML <Point X="1" Y="2" xmlns="clr-namespace:sample;assembly=samplelibrary" /> Visual Basic Dim obj = New Sample.Point With.X = 1,.Y = 2 Visual C# var obj = new Sample.Point X = 1, Y = 2 ; List 4: ユーザー定義クラスのインスタンス生成例 大まかに謂うと 以下の様な対応関係に成る XML 名前空間 (xmlns 属性 ) = CLR 名前空間 + アセンブリ情報 XML 要素名 = CLR クラス名 XML 属性 = CLR オブジェクトのプロパティ値設定 ( 属性構文 ) System.Uri クラスのインスタンス生成例 次の List 5 は (XAML コードに於ける <Uri> 要素から )Uri クラス (System 名前空間 ) のインスタンス (=CLR オブジェクト ) が生成される例で有る XAML <Uri xmlns="clr-namespace:system;assembly=system"> http://www.atmarkit.co.jp/</uri> Visual Basic Dim converter = New System.UriTypeConverter() Dim obj = converter.convertfrom("http://www.atmarkit.co.jp/") ' System.Uri クラス (=<Uri> 要素 ) には ' <TypeConverter(GetType(UriTypeConverter))> 属性が付いて居る Visual C# var converter = new System.UriTypeConverter(); var obj = converter.convertfrom("http://www.atmarkit.co.jp/"); -16-
// System.Uri クラス (=<Uri> 要素 ) には // [TypeConverter(typeof(UriTypeConverter))] 属性が付いて居る List 5: System.Uri クラスのインスタンス生成例 XAML コード中の各要素の文字列は CLR クラスに付与された TypeConverter 属性の値に基づいて変換処理が行われる 例えば List 5 の XAML コードに有る <Uri> 要素の文字列は Uri クラスに付与された TypeConverter 属性の UriTypeConverter に基づき C# や VB コードに於ける Uri クラスのインスタンス (CLR オブジェクト ) に変換される System.Windows.Controls.Button クラスのインスタンス生成例 List 6 は (XAML コードに於ける <Button> 要素から )Button クラス (System.Windows.Controls 名前空間 ) のインスタンス (CLR オブジェクト ) が生成される例で有る XAML <Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" FontSize="20" Background="Blue"> ボタン </Button> Visual Basic ' Button クラス ( の親クラスで有る ContentControl クラス ) には ' [ContentProperty("Content")] 属性が付いて居る Dim obj = New Button With.Content = " ボタン ",.FontSize = 20,.Background = New SolidColorBrush(Colors.Blue) ' BrushConverter.ConvertFrom("Blue") の結果 ' Background プロパティの型で有る Brush クラスには ' <TypeConverter(GetType(BrushConverter))>] 属性が付いて居る Visual C# var obj = new Button Content = " ボタン ", // Button クラス ( の親クラスで有る ContentControl クラス ) には // [ContentProperty("Content")] 属性が付いて居る FontSize = 20, Background = new SolidColorBrush(Colors.Blue), // BrushConverter.ConvertFrom("Blue") の結果 // Background プロパティの型で有る Brush クラスには // [TypeConverter(typeof(BrushConverter))] 属性が付いて居る ; List 6: System.Windows.Controls.Button クラスのインスタンス生成例 List 6 を見ると解る様に WPF 関連のクラスに関しては XML 名前空間の宣言に逐一 CLR 名前空 -17-
間 + アセンブリ情報 を記述する必要はなく WPF 専用の XML 名前空間 http://schemas.microsoft.com/winfx/2006/xaml/presentation が用意されて居るので 代わりに此れを宣言すれば良い 亦 CLR クラスに ContentProperty 属性を付ける事で 其の属性に指定されたプロパティは <Button Content=" ボタン " /> の様に属性ではなく <Button> ボタン </Button> の様に要素のテキストセレクションとして記述出来る 複雑なプロパティ値を持つインスタンス生成例 次の List 7 も (XAML コードに於ける <Button> 要素から )Button クラス (System.Windows.Controls 名前空間 ) のインスタンス (CLR オブジェクト ) が生成される例で有る 此の例では 先程の List 6 の場合よりも複雑なプロパティを持って居る XAML <Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <Button.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <LinearGradientBrush.GradientStops> <GradientStop Color="White" Offset="0" /> <GradientStop Color="Blue" Offset="1" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Button.Background> ボタン </Button> Visual Basic Dim obj = New Button With.Content = " ボタン ",.Background = New LinearGradientBrush With.StartPoint = New Point With.X = 0,.Y = 0,.EndPoint = New Point With.X = 1,.Y = 1,.GradientStops = New GradientStopCollection From New GradientStop With.Color = Colors.White,.Offset = 0, New GradientStop With.Color = Colors.Blue,.Offset = 1-18-
Visual C# var obj = new Button Content = " ボタン ", Background = new LinearGradientBrush StartPoint = new Point X = 0, Y = 0, EndPoint = new Point X = 1, Y = 1, GradientStops = new GradientStop Color = Colors.White, Offset = 0, new GradientStop Color = Colors.Blue, Offset = 1, ; List 7: 複雑なプロパティ値を持つインスタンス生成例 文字列からの型変換では生成し難い様な複雑なプロパティ値を指定し度い場合 XML 属性ではなく XML 要素の形でプロパティ値を代入する事も可能で有る ( 此れはプロパティ要素構文と呼ばれる List 7 の XAML コードでは <Button.Background> 要素等が其の例 ) マークアップ拡張を使ったプロパティ値設定の例 List 8 は (XAML コードに於ける <TextBlock> 要素から )TextBlock クラス (System.Windows.Controls 名前空間 ) のインスタンス (CLR オブジェクト ) が生成される例で有る XAML <TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Foreground="x:Static Member=SystemColors.HighlightTextBrush" Background="x:Static Member=SystemColors.HighlightBrush" Text="text" /> Visual Basic Dim obj = New TextBlock With.Text = "text",.foreground = SystemColors.HighlightTextBrush,.Background = SystemColors.HighlightBrush ' 実際には 以下の様なクラスのインスタンスが作られた上で ' 其のインスタンスの ProvideValue メソッドが呼ばれた結果 ' 静的メンバが取得される ' 'Dim extension As New StaticExtension With ' '.Member = "SystemColors.HighlightBrush" ' -19-
Visual C# var obj = new TextBlock Text = "text", Foreground = SystemColors.HighlightTextBrush, Background = SystemColors.HighlightBrush, ; // 実際には 以下の様なクラスのインスタンスが作られた上で // 其のインスタンスの ProvideValue メソッドが呼ばれた結果 // 静的メンバが取得される // // var extension = new StaticExtension // // Member = "SystemColors.HighlightBrush" // ; List 8: マークアップ拡張を使ったプロパティ値設定の例 属性構文を使って複雑なインスタンスを生成する為に マークアップ拡張と呼ばれる機能も提供されて居る XML 属性の値として で囲った記述を行うと 一度 MarkupExtension クラスを継承したクラス (List 8 の例では StaticExtension クラス ) のインスタンスが生成され 其のインスタンスの ProvideValue メソッドの戻り値がプロパティの値として設定される List 8 の例では 何等かのクラスの静的プロパティを読み出す為のマークアップ拡張で有る x:static を利用して居る リストのインスタンス生成例 List 9 は ( XAML コードに於ける <StackPanel> 要素から ) StackPanel クラス (System.Windows.Controls 名前空間 ) のインスタンス (CLR オブジェクト ) が生成される例で有る XAML <StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <Button Content=" ボタン 1" /> <Button Content=" ボタン 2" /> <Button Content=" ボタン 3" /> </StackPanel> Visual Basic Dim obj As New StackPanel obj.children.add(new Button With.Content = " ボタン 1") obj.children.add(new Button With.Content = " ボタン 2") obj.children.add(new Button With.Content = " ボタン 3") ' StackPanel クラス ( の親クラスで有る Panel クラス ) には ' [ContentProperty("Children")] 属性が付いて居る Visual C# var obj = new StackPanel Children = // StackPanel クラス ( の親クラスで有る Panel クラス ) には // [ContentProperty("Children")] 属性が付いて居る -20-
new Button Content = " ボタン 1", new Button Content = " ボタン 2", new Button Content = " ボタン 3", ; List 9: リストのインスタンス生成例 StackPanel クラスの Children プロパティの型は UIElementCollection クラス (System.Windows.Controls 名前空間 ) だが 此の様な IList インターフェイスを実装するクラスや配列の場合 <UIElementCollection> と謂う様なリストを表すタグを 1 段省略出来る 実際に List 9 では <UIElementCollection> タグが省略され 複数の <Button> 要素が <StackPanel> タグの中に直接記述されて居る 添付プロパティを持つインスタンス生成例 List 10 は (XAML コードに於ける <Canvas> 要素から )Canvas クラス (System.Windows.Controls 名前空間 ) のインスタンス (CLR オブジェクト ) が生成される例で有る XAML <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="100" Height="100"> <Button Content=" ボタン " Canvas.Left="10" Canvas.Top="50" /> </Canvas> Visual Basic Dim button = New Button With.Content = " ボタン " Canvas.SetLeft(button, 10) Canvas.SetTop(button, 50) Dim obj As New Canvas With.Width = 100,.Height = 100 obj.children.add(button) ' Canvas クラス ( の親クラスで有る Panel クラス ) には ' [ContentProperty("Children")] 属性が付いて居る Visual C# var button = new Button Content = " ボタン " ; Canvas.SetLeft(button, 10); Canvas.SetTop(button, 50); var obj = new Canvas Width = 100, Height = 100, -21-
Children = button // Canvas クラス ( の親クラスで有る Panel クラス ) には // [ContentProperty("Children")] 属性が付いて居る ; List 10: 添付プロパティを持つインスタンス生成例 添付プロパティ ( 前回説明した様に 自分自身ではなく 親要素で用いる値を保持する為の機構 ) は クラス名.Set プロパティ名 と謂う名前の静的メソッド呼び出しとして解釈される List 10 の赤字の部分が 添付プロパティで有る 以上 XAML コードの XML 要素と 其れに対応する CLR オブジェクトの例を示した 分離コードの利用例 此処迄は XAML 単体で書ける仕様だったが 下記の記述例 (C#/VB) では MSBuild を通すのが必須に成る List 11 に其の例を示す XAML <Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:class="sample.mygrid"> <Button x:name="button1" Content=" ボタン " Click="button1_Click" /> </Grid> Visual Basic Namespace Sample Class MyGrid Inherits Grid Private Sub button1_click(byval sender As System.Object, _ ByVal e As System.Windows.RoutedEventArgs) MessageBox.Show(" ボタンが押されました ") End Sub End Class End Namespace Visual C# using System.Windows.Controls; using System.Windows; namespace Sample public partial class MyGrid : Grid -22-
public MyGrid() InitializeComponent(); void button1_click(object sender, System.Windows.RoutedEventArgs e) MessageBox.Show(" ボタンが押されました "); List 11: 分離コードの利用例 http://schemas.microsoft.com/winfx/2006/xaml と謂う XML 名前空間は (WPF に限らない )XAML 全般に使う特別なスキーマを定義する x:class 属性や x:name 属性も其の一種で x:class 属性には分離コードで実装するクラス名を x:name 属性にはフィールド名を指定する 亦 プロパティと同様に イベントに対しても Click="button1_Click" と謂う様な XML 属性を使った記述が可能で有る 其処で記述したメソッド名のイベントハンドラは 分離コード内で定義する必要が有る List 10 及び List 11 の組み合わせた C#/VB 而巳のコード List 11 の例の MyGrid クラスと粗同等の物を C#/VB 丈で記述すると List 12 の様に成る Visual Basic Namespace Sample Class MyGrid1 Inherits Grid Friend button1 As Button Public Sub New() InitializeComponent() button1 = New Button With.Content = " ボタン " AddHandler button1.click, AddressOf button1_click End Sub Private Sub button1_click(byval sender As System.Object, _ ByVal e As System.Windows.RoutedEventArgs) MessageBox.Show(" ボタンが押されました ") End Sub End Class End Namespace Visual C# using System.Windows.Controls; -23-
using System.Windows; namespace Sample public partial class MyGrid : Grid internal Button button1; public MyGrid() button1 = new Button Content = " ボタン " ; button1.click += button1_click; void button1_click(object sender, System.Windows.RoutedEventArgs e) MessageBox.Show(" ボタンが押されました "); List 12: List 10 及び List 11 の組み合わせと粗同等のコード 分離コードを用いた WPF アプリケーション作成に関して 詳細は次回以降で説明する 最後に 此れ迄の例で出て来た構文に付いて纏めて置く XAML 構文のまとめ XML 名前空間 XML 名前空間には 以下の様に CLR 名前空間とアセンブリ名に関する情報を書く xmlns:ns="clr-namespace:clr 名前空間名 ;assembly= アセンブリ名 " 以後 <ns: クラス名 > と謂う様な XML 要素を書く事で 対応する CLR オブジェクトのインスタンスを生成出来る ( 当然乍クラス名は 記述したアセンブリに含まれる CLR 名前空間のクラスでなければ成らない ) 但し WPF の場合には関連するクラスが様々な名前空間に亘って定義されて居るが 此等を逐一 XML 名前空間に書く必要は無く 以下の様な WPF 専用の XML 名前空間を 1 つ書く丈で構わない xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 亦 XAML 固有の機能を使う為に 通常は以下の XML 名前空間も併せて記述する xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" -24-
属性構文 XAML コード中の XML 属性は CLR オブジェクトのプロパティ若しくはイベントへの値の代入として解釈される ( 属性構文 (attribute syntax) と呼ぶ ) プロパティの場合 XML 属性値 ( 文字列 ) から所望の型への変換は 以下の手順で行われる 1. 先ず CLR オブジェクトのプロパティに TypeConverter 属性が付いて居るか何うかを調べ 付いて居る場合は此の属性情報に基づいた型コンバータを使って ConvertFrom メソッドに依り値を生成する 2. プロパティの次に プロパティの型に対して TypeConverter 属性が付いて居るか何うかを調べ 同様に此の情報に基づいて値を生成する イベントの場合には 分離コード内に其のイベントハンドラと成るメソッドが定義されて居る必要が有る プロパティ要素構文 文字列からの変換が困難で有る様な複雑なインスタンスを生成する場合は XML 属性の代わりに 子要素としてプロパティ値を設定する事も出来る ( プロパティ要素構文 (property element syntax) と呼ぶ ) プロパティ要素構文では < 型名. プロパティ名 > と謂う形の XML 要素を記述する 属性構文とプロパティ要素構文とで得られる結果は同じで 例えば List 13 の 2 つの Button 要素は同じ構造のインスタンスを生成する ( 値を文字列丈で書ける場合には属性構文 然うでない場合にプロパティ要素構文の利用が推奨される ) <Button Background="Blue" /> <Button> <Button.Background>Blue</Button.Background > </Button> List 13: 属性構文 ( 上 ) とプロパティ要素構文 ( 下 ) マークアップ拡張 プロパティ要素構文に加えて 属性構文を用いて複雑なインスタンスを生成する手段として マークアップ拡張 (markup extension) と呼ばれる機能が有る 詳細は次回以降で解説する事に成るが データバインディングを行う為の Binding マークアップ拡張が其の代表例だ (List 14) <StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="100"> <Slider Name="slider" /> <TextBox Text="Binding ElementName=slider, Path=Value" /> </StackPanel> List 14: Binding マークアップ拡張 データバインディングに依り スライダコントロールの値 (Value プロパティの値 ) がテキストボックスに反映される -25-
マークアップ拡張では XML 属性中に で囲った記述を書くと 対応するマークアップ拡張クラスのインスタンス生成と 其の ProvideValue メソッド呼び出しを通してプロパティの値が設定される List 14 の場合 List 15 に示す様な Binding クラスのインスタンスが生成され Binding.ProvideValue メソッド内でデータバインディングの処理が行われる Visual Basic Dim binding = New System.Windows.Data.Binding With.ElementName = "slider",.path = new PropertyPath("Value") Visual C# var binding = new System.Windows.Data.Binding ElementName = "slider", Path = new PropertyPath("Value"), ; List 15: List 14 の XAML コードに対応する Binding クラスのインスタンスを生成するコード マークアップ拡張クラスは自作する事も出来る マークアップ拡張クラスは MarkupExtension クラス (System.Windows.Markup 名前空間 ) を継承する必要が有る 名前の末尾に Extension が付く場合 XAML コード中では Extension の部分を省略出来る ( 例えば ArrayExtension は Array と記述出来る 猶 一般的なクラスの命名規則としては Extension を付ける Binding クラス (System.Windows.Data 名前空間 ) は此の例には当て嵌らない ) コンテンツプロパティと XML 要素の省略 XAML には XAML コードの可読性を向上させる為 XML 要素の省略機構が幾つか存在する 其の 1 つがコンテンツプロパティ (content property) で クラスに対して ContentProperty 属性が付いて居る場合 其の情報に基づいて XML 要素の省略が可能に成る 例えば Button クラスの場合 親クラスで有る ContentControl クラスに [ContentProperty("Content")] 属性が付いて居る為 <Button.Content> 要素を省略出来る 即ち List 16 に示す 2 つの <Button> 要素は同じ意味に成る <Button> <Button.Content> ボタン </Button.Content> </Button> <Button> ボタン </Button> List 16: コンテンツプロパティに於ける XML 要素の省略 -26-
コレクション型の省略 プロパティ要素構文で プロパティの値がコレクションの場合 コレクションに相当する XML 要素を省略する事が出来る 例えば StackPanel クラス等の Children プロパティ ( コンテンツプロパティに設定されて居る ) の型は UIElementCollection と謂うコレクションクラスで有る為 省略可能で有る List 17 に示す 2 つの <StackPanel> 要素は同じ意味に成る <StackPanel> <StackPanel.Children> <UIElementCollection> <Button Content=" ボタン 1" /> <Button Content=" ボタン 2" /> </UIElementCollection> </StackPanel.Children> </StackPanel> <StackPanel> <Button Content=" ボタン 1" /> <Button Content=" ボタン 2" /> </StackPanel> List 17: コレクション型の省略 ( 上 : 省略無し 下 : 省略有り ) 省略可能と成るのは以下の孰れかの場合で有る IList インターフェイス ( ジェネリック版 / 非ジェネリック版共に ) を実装するクラス IDictionary インターフェイス ( ジェネリック版 / 非ジェネリック版共に ) を実装するクラス ( 此の場合 子要素に x:key 属性が必須と成る ) 配列 今 1 つ IAddChild インターフェイスに依るコレクション型要素の省略も可能で有るが 過去の互換性の為丈に有り 新規に利用する事は無いだろう 添付プロパティ 繰り返しに成るが XAML には 自分自身ではなく親要素で用いる値を保持する為の機構として添付プロパティ (attached property) と謂う機能が存在する 例えば <Button Canvas.Left="10" /> と謂う様に XML 属性名に 型名. 添付プロパティ名 を指定する 此の様な添付プロパティの値の設定は 型名.Set 添付プロパティ名 と謂う名前の静的メソッド呼び出しとして解釈される 前記の Canvas.Left の例であれば Canvas.SetLeft(button, 10) と謂う様なメソッド呼び出しに成る 因みに WPF の内部的には 次回以降で説明する依存関係プロパティ (dependency property) と謂う物を用いて添付プロパティを実装して居る Canvas.SetLeft メソッドの例で謂うと 内部的には List 18 に示す様な実装に成って居る Visual Basic Public Class Canvas Public Shared ReadOnly LeftProperty As DependencyProperty = ' 詳細は省略 -27-
Public Shared Sub SetLeft(ByVal element As DependencyObject, ByVal value As Double) element.setvalue(leftproperty, value) End Sub End Class Visual C# public class Canvas public static readonly DependencyProperty LeftProperty = // 詳細は省略 public static void SetLeft(DependecyObject element, double value) element.setvalue(leftproperty, value); List 18: Canvas.SetLeft メソッドの実装 添付プロパティと同様に イベントに関しても親子間でのイベントの伝搬を行うルーティングイベント (routed event) と謂う機能が有り 添付プロパティと同様に 型名. イベント名 =" イベントハンドラ " と謂う書き方が出来る ルーティングイベントに付いては次回以降で説明を行う XML 形式 XAML の記述には XML 形式を用いる為 XML 形式を使う上での注意点が其の儘 XAML にも当て嵌まる < > & " 等の文字は其の儘では利用出来ない 夫々 < > & "e; と謂う記述に置き換える必要が有る 或いは CDATA セクションを用いる ( <![CDATA[ と ]]> で囲む ) 空白文字は無視される 空白文字に意味を持たせる為には XML 要素に xml:space="preserve" と謂う属性を追加する 参考 :XAML コード単体で UI( ユーザーインターフェイス ) 表示 最後に 余談に成るが XAML コード単体で (C# 等の分離コードを一切書かず MSBuild に依るコンパイルも行わず )UI を表示させる方法に触れて置く Loose XAML 分離コードを必要としない ( 或いは XAML コード中に <x:code> 要素に依りインラインで C#/VB のプログラミングコードを埋め込んで居ない )XAML コードで有れば コンパイルせずに其の儘 IE (Internet Explorer) 等のブラウザ上で表示する事が可能で有る 此の C#/VB 等のプログラミングコードを含まない XAML コードを Loose XAML と呼ぶ Loose XAML の表示は.NET Framework 同梱のブラウザプラグインに依る物で有り.NET Framework 3.0 のインストールされた Windows 上でなら IE 而巳 3.5 以上なら IE に加えて Firefox での表示が可能だ -28-
Loose XAML のサンプル Silverlight WPF ではなく Silverlight を用いた技術に成るが Gestalt と謂うライブラリを使う事で HTML コード中に XAML コードを埋め込んで表示出来る Silverlight ベースで有る為 WPF とは利用出来る UI 要素等に差が有るが <Button> 要素等の基本的な物は其の儘利用出来る部分も多い 以下の例では 上述の Loose XAML と粗同じ (<Menu> 要素と <StatusBar> 要素が無い以外は全く同じ )XAML コードを HTML コード中に埋め込んで居る Gestalt を使った HTML コードへの XAML コード埋め込みサンプル -29-
第 3 回 XAML コードから生成されるプログラム コードを理解する 前回は主に WPF に依らない XAML の一般的な仕組みに付いて説明を行った 今回からは WPF 固有の機能に踏み込んで説明して行く 先ず WPF の内部的な挙動の理解を深めて貰う為 XAML コードから自動生成される中間生成物のプログラムコード (C#/VB) に付いて説明を行う 亦 プログラムの起点と成る Application クラス (System.Windows 名前空間 ) に関する説明も行う (Application クラスと並んで WPF アプリケーションの基礎と成る Window クラスに関しては コントロールの一種と看做せる為 次回以降 コントロールに関する回にて説明を行う ) 更に データバインディング等の高度な機能を実現する為の要と成る 依存関係プロパティ と ルーティングイベント に関する説明を行う XAML コードから自動生成される中間生成物のプログラムコード 前回からの再掲に成るが 標準的な WPF アプリケーションの作りを今一度観てみよう Visual Studio のテンプレートから WPF アプリケーションプロジェクトを作成すると Figure 1 に示す様な状態が得られる筈だ Figure 1: Visual Studio で WPF アプリケーションプロジェクト (C#) を作成した直後の状態 前回は概要而巳の説明だったが 今回は今少し踏み込んで XAML コードから自動生成される中間生成物のプログラムコード (.g.cs/.g.vb ファイル等 詳しくは第 1 回を参照 ) に付いて説明して行く MainWindow.xaml ファイルから生成されるプログラムコード MainWindow.xaml ファイルの分離コードで有る MainWindow.xaml.cs/MainWindow.xaml.vb ファイル内に生成される MainWindow クラスは 名前通り アプリケーション起動時に最初に開かれるメイ -30-
ンウィンドウで 其の実体は Window クラス (System.Windows 名前空間 ) を継承したクラスで有る MainWindow クラスはパーシャルクラスと成って居て MainWindow.xaml ファイルから自動生成されるプログラムコードと合わせて 1 つのクラスと成る Visual Studio のテンプレートから生成された儘の MainWindow.xaml ファイル (+ 其の分離コード ) をビルド ( コンパイル ) すると List 1 に示す様なプログラムコードが (obj フォルダ内に MainWindow.g.cs/MainWindow.g.vb 等の名前で ) 自動生成される ( 見易さを優先して 属性やコメント等を一部削って居る ) Visual Basic Imports System.Windows Imports System.Windows.Markup Partial Public Class MainWindow Inherits Window Implements IComponentConnector Private _contentloaded As Boolean Public Sub InitializeComponent() Implements IComponentConnector.InitializeComponent If _contentloaded Then Return End If _contentloaded = true Dim resourcelocater As System.Uri = New System.Uri( _ "/WpfApplication1;component/mainwindow.xaml", _ System.UriKind.Relative) System.Windows.Application.LoadComponent( Me, resourcelocater) End Sub Sub System_Windows_Markup_IComponentConnector_Connect( _ ByVal connectionid As Integer, ByVal target As Object) _ Implements IComponentConnector.Connect Me._contentLoaded = true End Sub End Class Visual C# using System.Windows; using System.Windows.Markup; namespace SampleWpfApplication public partial class MainWindow : Window, IComponentConnector -31-
private bool _contentloaded; public void InitializeComponent() if (_contentloaded) return; _contentloaded = true; System.Uri resourcelocater = new System.Uri( "/SampleWpfApplication;component/mainwindow.xaml", System.UriKind.Relative); System.Windows.Application.LoadComponent( this, resourcelocater); void IComponentConnector.Connect(int connectionid, object target) this._contentloaded = true; List 1: MainWindow.xaml ファイルから自動生成される中間生成物のプログラムコード 上記のコードを観ると解る様に Visual Studio テンプレートからの生成時の状態では XAML コードのロード以外には特に何も行って居ない 此処で XAML コードに記述を追加すると 自動生成されるプログラムコードに以下の様な変化が生じる XAML コード中で UI 要素に Name 属性を付けると 同名のフィールドが追加され IComponentConnector.Connect メソッド中での初期化が行われる XAML コード中でイベントハンドラの登録を行うと IComponentConnector.Connect メソッド中でイベントハンドラの追加が行われる <x:code> 要素を追加すると 要素内の記述が其の儘自動生成されたクラス内部にコピーされる 例えば MainWindow.xaml ファイルを List 2 の様に書き換えると 自動生成されるプログラムコードに List 3 に示す様なコードが追加される <Window x:class="samplewpfapplication.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"> <x:code><![cdata[ Private Sub Button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) MessageBox.Show("test") End Sub ]]> </x:code> -32-
<StackPanel> <TextBlock Name="text1" Text="Sample" /> <Button Name="button1" Click="Button_Click" Content="OK" /> </StackPanel> </Window> <Window x:class="samplewpfapplication.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"> <x:code><![cdata[ private void Button_Click(object sender, RoutedEventArgs e) MessageBox.Show("test"); ]]> </x:code> <StackPanel> <TextBlock Name="text1" Text="Sample" /> <Button Name="button1" Click="Button_Click" Content="OK" /> </StackPanel> </Window> List 2: Name 属性や Click 属性を含む様に MainWindow.xaml ファイルを書き換え (XAML) Visual Basic Friend WithEvents text1 As System.Windows.Controls.TextBlock Friend WithEvents button1 As System.Windows.Controls.Button Sub System_Windows_Markup_IComponentConnector_Connect(ByVal connectionid As Integer, _ ByVal target As Object) Implements IComponentConnector.Connect If (connectionid = 1) Then Me.text1 = CType(target,System.Windows.Controls.TextBlock) Return End If If (connectionid = 2) Then Me.button1 = CType(target,System.Windows.Controls.Button) AddHandler Me.button1.Click, _ New System.Windows.RoutedEventHandler( _ AddressOf Me.Button_Click) Return End If Me._contentLoaded = true End Sub Private Sub Button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) MessageBox.Show("test") End Sub Visual C# -33-
internal System.Windows.Controls.TextBlock text1; internal System.Windows.Controls.Button button1; void IComponentConnector.Connect(int connectionid, object target) switch (connectionid) case 1: this.text1 = ((System.Windows.Controls.TextBlock)(target)); return; case 2: this.button1 = ((System.Windows.Controls.Button)(target)); this.button1.click += new System.Windows.RoutedEventHandler(this.Button_Click); return; this._contentloaded = true; private void Button_Click(object sender, RoutedEventArgs e) MessageBox.Show("test"); List 3: List 2 から自動生成される中間生成物のプログラムコード ( 部分的に抜粋 ) XAML コードに記述した <x:code> 要素は HTML で謂う処の <script> タグに依るスクリプトコードの埋め込みと同じ感覚で利用出来るが 実際の処 WPF アプリケーション開発では余り利用されない WPF の場合 プログラムコードは分離コードの中に記述するのが一般的で有る 亦 XAML コード中の属性構文で指定した値がプロパティの場合は (InitializeComponent メソッド内で呼び出されて居る )LoadComponent メソッド内で自動的に読み出して貰えるが ( 即ち プログラムコードの自動生成に頼らない XAML 自体の仕様 ) 其の値がイベントの場合には 上記の様なプログラムコードの追加が必要と成る 続いて App.xaml ファイルから生成されるプログラムコードに付いて説明する App.xaml ファイルから生成されるプログラムコード 此処から 次に App.xaml ファイル ( VB では Application.xaml ファイル ) を観てみよう App.xaml ファイルの分離コードで有る App.xaml.cs/App.xaml.vb ファイル内に生成される App クラス ( VB では Application クラス ) は アプリケーション自身を定義する物で 実体は.Application クラス (System.Windows 名前空間 ) を継承したクラスで有る MainWindow クラスと同様に こちらもパーシャルクラスと成って居る App.xaml ファイルから自動生成される中間生成物のプログラムコードは MainWindow.xaml ファイルの物とは少し異なる Visual Studio 上でファイルのプロパティを開いて見比べて貰うと Figure 2 に示す様に MainWindow.xaml ファイルと App.xaml ファイルで [ ビルドアクション ] が異なる事が解る -34-
Figure 2: ビルドアクション の差 ( 左 :MainWindow.xaml 右 :App.xaml) [ ビルドアクション ] が ApplicationDefinition の場合 自動生成されるプログラムコード (obj フォルダ内に作成される App.g.cs/Application.g.vb ファイル ) は List 4 に示す様な物に成る ( 先程と同様に 説明に不要な属性やコメントは削除して居る ) Visual Basic Imports System.Windows Partial Public Class Application Inherits System.Windows.Application Public Sub InitializeComponent() Me.StartupUri = New System.Uri( _ "MainWindow.xaml", System.UriKind.Relative) End Sub Public Shared Sub Main() Dim app As Application = New Application() app.initializecomponent app.run End Sub End Class Visual C# using System.Windows; namespace SampleWpfApplication public partial class App : Application public void InitializeComponent() this.startupuri = new System.Uri( "MainWindow.xaml", System.UriKind.Relative); public static void Main() SampleWpfApplication.App app = -35-
new SampleWpfApplication.App(); app.initializecomponent(); app.run(); List 4: [ ビルドアクション ] が ApplicationDefinition の場合に自動生成される中間生成物のプログラムコード アプリケーションのエントリ ポイント (Main メソッド ) は此処で定義されて居る Visual Studio テンプレート生成時の状態では XAML コードの内容総てがプログラムコード化されて居り XAML コードを実行時にロードする処理が含まれて居ないが <Application.Resources> 要素の中身を (App.xaml ファイルに ) 追加したりすれば MainWindow.xaml ファイルの場合と同様に LoadComponent メソッドの呼び出しが追加される System.Windows.Application クラス Application クラス (System.Windows 名前空間 ) は アプリケーションの起点と成るクラスで 主に以下の様なイベントを持って居る Startup: アプリケーションの起動時 (Run メソッド呼び出し直後 ) に発生する Exit: アプリケーションの終了直前に発生する Activated: アプリケーションがアクティブに成った瞬間 ( 他のアプリケーションからフォーカスが戻った瞬間 ) に発生する Deactivated: 他のアプリケーションにフォーカスが移った瞬間に発生する SessionEnding: ユーザーのログオフや オペレーティング システムのシャットダウンに依ってセッションが終了するときに発生する コマンドライン引数 WPF アプリケーションでは XAML コードから自動生成される中間生成物のプログラムコード中に Main メソッドが隠されて居る為 コマンドライン引数を直接 受け取れない コマンドライン引数を利用し度い場合には Startup イベントのイベントハンドラ内で Environment クラス (System 名前空間 ) の GetCommandLineArgs メソッドを通して受け取る ( 参考 :.NET TIPS: コマンドライン引数を取得するには? ) アプリケーション オブジェクトの参照 一般に ウィンドウをまたいでアプリケーション全体で利用する値等は Application クラス ( を継承した自前のアプリケーション クラス ) の中に定義する ( 但し グローバル変数が非推奨とされるのと同様に アプリケーション全体から参照される値は可能な限り避けるべきで有る ) ウィンドウ ( 例えば Visual Studio のテンプレートで生成される MainWindow クラス等 ) からは Application.Current 静的プロパティを通して現在実行中のアプリケーション オブジェクトを取得出来る為 各ウィンドウがアプリケーション オブジェクトの参照を持つ必要はない 扨て 次のページからは 依存関係プロパティ ( 及び 添付プロパティ ) に付いて説明する -36-
依存関係プロパティ 此処迄にも名前丈はたびたび出てきて居るが WPF では 依存関係プロパティ (dependency property) と謂う 通常のプロパティ ( 区別の為に WPF の文脈に於いては CLR プロパティ と呼ぶ事が有る ) とは異なる 値の保持機構 を持って居る 依存関係プロパティは以下の様な用途を想定して作られて居る (Figure 3) 包含継承 : 親要素で設定した値を其の儘継承して使う為の機構 リソース : 1 カ所で定義したオブジェクトを複数カ所から参照する為の リソースの定義 / 参照機構 スタイル : HTML で謂う処の CSS の様なスタイル設定機構 データバインディング : モデルとビューの間等 異なるオブジェクト間で値を結び付ける ( 一方での値の変更を他方に通知し 即座に反映させる ) 機構 Figure 3: 依存関係プロパティの用途 此等は 孰れも 他の要素の値に依存してプロパティの値を決定する機構 と謂える 依存関係プロパティと謂う名前は此処から来て居る リソース スタイル 及び データバインディングに関する詳細は次回以降で説明する 今回は 依存関係プロパティの定義及び利用方法に付いて説明して行く ( 包含継承の説明を含む ) 依存関係プロパティの定義 先ず List 5 に 依存関係プロパティを定義するクラスの最低限の実装例を示す Visual Basic Public Class Point Inherits DependencyObject -37-
Public Property X() As Integer Get Return CType(GetValue(XProperty), Integer) End Get Set(ByVal value As Integer) SetValue(XProperty, value) End Set End Property Public Shared ReadOnly XProperty As DependencyProperty = _ DependencyProperty.Register( _ "X", GetType(Integer), GetType(Point), New UIPropertyMetadata(0)) End Class Visual C# public class Point : DependencyObject public int X get return (int)getvalue(xproperty); set SetValue(XProperty, value); public static readonly DependencyProperty XProperty = DependencyProperty.Register( "X", typeof(int), typeof(point), new UIPropertyMetadata(0)); List 5: 依存関係プロパティを定義する最低限のクラス定義 依存関係プロパティの定義には DependencyProperty クラス (System.Windows 名前空間 ) を用いる 但し DependencyProperty クラスのインスタンス自身は プロパティ其の物と謂うよりは辞書のキーの様な物で 実際に値を保持するのは DependencyObject クラス (System.Windows 名前空間 )( の子クラス ) に成る 依存関係プロパティは以下の様にして定義する (Figure 4) ( 辞書のキーとして使われる ) 依存関係プロパティの静的フィールド名は プロパティ名 +Property とする DependencyProperty.Register メソッドを用いて WPF のフレームワークに登録する DependencyProperty.Register メソッドの引数には プロパティ名 プロパティの型 プロパティを定義する型 及び メタデータ ( 後述 ) を与える -38-
Figure 4: 依存関係プロパティの定義 (C#) 実際の値の読み書きは DependencyObject クラスの SetValue/GetValue メソッドを通して行う 亦 内部的に SetValue/GetValue メソッドを呼び出す丈の CLR プロパティ ( 此の例の場合 X プロパティ ) も定義しておく 但し 此の CLR プロパティ内では SetValue/GetValue メソッド呼び出し以外の処理を行っては成らない 依存関係プロパティは 必ずしも此の CLR プロパティを通して呼ばれる訳ではなく データバインディング等を行うと SetValue/GetValue メソッドが直接呼び出される場合が有る為 CLR プロパティ内に記述した処理は行われない事が有る 添付プロパティの場合 依存関係プロパティの仕組みは 添付プロパティを実現する為にも利用される 依存関係プロパティを添付プロパティとして利用し度い場合には List 6 に示す様に Register メソッドの代わりに RegisterAttached メソッドを用いた登録を行う Visual Basic Public Class MyCanvas Public Shared Function GetX(ByVal obj As DependencyObject) As Integer Return CType(obj.GetValue(XProperty), Integer) End Function Public Shared Sub SetX(ByVal obj As DependencyObject, ByVal value As Integer) obj.setvalue(xproperty, value) End Function Public Shared ReadOnly XProperty As DependencyProperty = _ DependencyProperty.RegisterAttached( _ "X", GetType(Integer), GetType(MyCanvas), New UIPropertyMetadata(0)) End Class Visual C# public class MyCanvas public static int GetX(DependencyObject obj) return (int)obj.getvalue(xproperty); public static void SetX(DependencyObject obj, int value) -39-
obj.setvalue(xproperty, value); public static readonly DependencyProperty XProperty = DependencyProperty.RegisterAttached( "X", typeof(int), typeof(mycanvas), new UIPropertyMetadata(0)); List 6: 添付プロパティとして利用する場合の依存関係プロパティの登録 亦 添付プロパティの場合には CLR プロパティの代わりに Set プロパティ名 Get プロパティ名 ( 上記のコードの例では GetX SetX ) と謂う名前の静的メソッドを定義しておく 通常の依存関係プロパティの場合と同様 此等の Set/Get メソッドの内部では DependencyObject.SetValue/DependencyObject.GetValue メソッド呼び出し以外の処理を行っては成らない オーナー クラスの追加 他のクラスで定義された既存の依存関係プロパティを自作のクラスでも利用し度い場合 DependencyProperty.AddOwner メソッドを用いる事で WPF に登録情報を追加出来る 例えば 標準の TextBox クラスで定義されいてる Text 依存関係プロパティを 自作の MyControl クラスでも利用し度い場合 List 7 に示す様な記述を行う Visual Basic Public Class MyControl Public Shared TextProperty As DependencyProperty = TextBox.TextProperty.AddOwner(GetType(MyControl)) End Class Visual C# using System.Windows; using System.Windows.Controls; public partial class MyControl : UserControl public static DependencyProperty TextProperty = TextBox.TextProperty.AddOwner(typeof(MyControl)); List 7: 依存関係プロパティのオーナー クラスの追加 此の仕組みは例えば ComboBox クラス (System.Windows.Controls 名前空間 ) 等の IsSelected 依存関係プロパティ等で利用されて居る ComboBox クラスの子要素と成る ComboBoxItem クラスに対して IsSelected 依存関係プロパティを設定する場合 本来なら <ComboBoxItem ComboBox.IsSelected="true"/> と謂う様に 添付プロパティとして設定する必要が有るが IsSelected 依存関係プロパティはオーナー クラスの追加の仕組みを使って ComboBoxItem クラス側にも定義されていて <ComboBoxItem IsSelected="true"/> と書く事が出来る -40-
依存関係プロパティの意義 WPF が CLR プロパティではなく わざわざ依存関係プロパティの様な仕組みを利用する意義は 以下の様な点に有る (1) パフォーマンス 依存関係プロパティは一種の辞書構造に成って居るが 此れはデータバインディングのパフォーマンスの向上や 添付プロパティの実現の為に利用されて居る CLR プロパティを用いてデータバインディングの様な仕組みを実現する為には リフレクションに頼る事に成る 一般に リフレクションの利用は著しくパフォーマンスを低下させる事が有る プロパティの値を辞書的に持てば リフレクションの利用を避ける事ができ パフォーマンスの向上が見込める 亦 辞書的に値を持つ事で 添付プロパティ ( 要素自身ではなく 親要素で利用する値を保持する機構 ) も実現可能で有る (2) メタデータの保持 依存関係プロパティは 次節で説明する様なメタデータを持つ事が出来る メタデータ メタデータは PropertyMetadata クラス (System.Windows 名前空間 ) 若しくは 其の子クラスのインスタンスとして定義する 利用場面に応じて以下の 3 つのうちの孰れかを用いる ( 孰れも System.Windows 名前空間に所属するクラス ) PropertyMetadata クラス UIPropertyMetadata クラス FrameworkPropertyMetadata クラス PropertyMetadata クラスは WPF に限らず幅広い用途で利用する事を想定した物で メタデータとして最低限の情報を持って居る PropertyMetadata クラスの持って居るプロパティのうち 主要な物を以下に示す DefaultValue: 依存関係プロパティのデフォルト値 CoerceValueCallback: ( 此のプロパティは ) 依存関係プロパティの値を変更する前に呼び出され 値が適切か何うかを判定し 適切でなければ適切な値に修正する為のコールバック メソッド 此の様な挙動を 値の強制 (coercion) と呼ぶ PropertyChangedCallback: ( 此のプロパティは ) 依存関係プロパティの値が変更された際に呼び出されるコールバック メソッド UIPropertyMetadata クラスは UI 要素向けのメタデータで PropertyMetadata クラスの持つ情報に加えて アニメーションの可否を表す IsAnimationProhibited プロパティを持つ FrameworkPropertyMetadata クラスは WPF 用のメタデータで UIPropertyMetadata クラスの持つ情報に加えて WPF 固有の豊富な機能向けの情報を持って居る FrameworkPropertyMetadata クラスのプロパティのうち 主要な物を以下に示す -41-
Inherits: 値の包含継承が行われるか何うか IsDataBindingAllowed: データバインディングに対応して居るか何うか BindsTwoWayByDefault: データバインディングの既定の挙動が双方向で有る事を示す 値の優先順位 依存関係プロパティを利用する際 包含継承やスタイル等を併用すると 複数の個所から値が設定される事に成る 此の場合 値の設定元には優先順位が有り 順位の高い物が依存関係プロパティの値に反映される 優先順位は以下の通りで有る ( 上に行くほど優先度が高い ) 1.PropertyMetadata クラスの CoerceValueCallback プロパティに設定したコールバック メソッドに依る値の強制 2. アニメーションに依る動的な値の更新 3. ローカル値 ( 要素に対して直接設定した値 ) 4.TemplatedParent( テンプレート親 要素が XAML のコントロール テンプレート機能を用いて作られた場合に設定される 要素の作成元 ) からの包含継承 5. スタイル内のトリガー ( マウス オーバー等のイベントをきっかけとして起きる値の変化 ) 6.(XAML の ) コントロール テンプレート内のトリガー 7. スタイル中で設定される値 8. テーマ スタイル ( アプリケーション全体に適用されるスタイル ) 中で設定される値 9. 親要素からの包含継承 10. メタデータで設定された規定値 最後に ルーティングイベントに付いて解説する ルーティングイベント 依存関係プロパティと同様に WPF では ルーティングイベント (routed event) と呼ばれる特殊なイベント通知機構を持って居る ( プロパティの場合と同様に 通常のイベントを CLR イベント と呼んで区別する ) ルーティングイベントは依存関係プロパティのイベント版と謂える CLR イベントとの差は 発生したイベントが要素ツリーをたどって ルーティング される処に有る ルーティングの方針には以下の 3 種類が有る 直接 (Direct): イベント発生源と成る要素自身のイベントハンドラ而巳が呼び出される トンネル (Tunnel): 要素ツリーのルートからイベント発生源と成る要素に向かって 要素ツリーを掘り進む様にイベントハンドラが呼び出される バブル (Bubble): イベント発生源と成る要素からルートに向かって 要素ツリーをたどり乍 浮かび上がる様にイベントハンドラが呼び出される ルーティングイベントの定義 List 8 にルーティングイベントの定義例を示す Visual Basic Public Class MyControl -42-
Public Shared ReadOnly ClickEvent As RoutedEvent = _ EventManager.RegisterRoutedEvent( _ "Click", RoutingStrategy.Bubble, _ GetType(RoutedEventHandler), GetType(MyControl)) Public Custom Event Click As RoutedEventHandler AddHandler(ByVal value As RoutedEventHandler) MyBase.AddHandler(ClickEvent, value) End AddHandler RemoveHandler(ByVal value As RoutedEventHandler) MyBase.RemoveHandler(ClickEvent, value) End RemoveHandler RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs) MyBase.RaiseEvent(e) End RaiseEvent End Event End Class Visual C# public partial class MyControl : UserControl public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent( "Click", RoutingStrategy.Bubble, typeof(routedeventhandler), typeof(mycontrol)); public event RoutedEventHandler Click add AddHandler(ClickEvent, value); remove RemoveHandler(ClickEvent, value); List 8: ルーティングイベントの定義 依存関係プロパティと同様に RoutedEvent クラス (System.Windows 名前空間 ) のインスタンス自体は辞書のキー (+ メタデータ ) の様な物で有り 実際にイベントハンドラを保持するのは UIElement クラス (System.Windows 名前空間 )( の子クラス ) のインスタンスで有る ルーティングイベントを格納する静的フィールドの名前は イベント名 +Event とする ルーティングイベントも WPF への登録が必要で こちらは EventManager クラス (System.Windows 名前空間 ) の RegisterRoutedEvent メソッドを用いる 引数は イベント名 ルーティング方針 イベントハンドラの型 及び イベント発生源と成る型で有る UIElement クラスのインスタンスへのイベントハンドラの追加 / 削除は UIElement.AddHandler/RemoveHandler メソッドを用いて行う 依存関係プロパティの定義の際に対応する CLR プロパティを定義したのと同様に ルーティングイベントの場合にも対応する CLR イベントを定義しておく ( 上記の例では Click イベントが該当 ) -43-
ルーティングイベントの利用例 ルーティングイベントを利用する事で UI 要素で発生したイベントを 其の親要素で一括して処理出来る 例えば <ListBox> 要素の様な コレクションとして子要素を持つ様な物で 子要素で発生したイベントを <ListBox> 要素自身で一括して処理し度い場合等に便利で有る List 9 に其の一例を挙げる 本連載でまだ説明して居ないデータテンプレート等の機能を使って居るが 詳細は次回以降で説明して行く 此処でのポイントは <ListBox> 要素で Button.Click イベントを拾い 子要素として追加した <Button> 要素の Click イベントを一括処理して居る部分で有る <Button> 要素 1 つ 1 つにイベントハンドラを追加しなくても <ListBox> 要素側の 1 度きりの追加でイベントの処理が可能に成って居る XAML <UserControl x:class="mycontrol" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <ListBox Name="list" Button.Click="Button_Click"> <ListBox.ItemTemplate> <DataTemplate> <Button Content="Binding" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </UserControl> Visual Basic Public Class MyControl Public Sub New() InitializeComponent() Me.list.ItemsSource = "A", "B", "C" End Sub Private Sub Button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Dim button As Button = e.originalsource MessageBox.Show(button.DataContext.ToString()) End Sub End Class Visual C# using System.Windows; using System.Windows.Controls; public partial class MyControl : UserControl -44-
public MyControl() InitializeComponent(); this.list.itemssource = new[] "A", "B", "C", ; private void Button_Click(object sender, RoutedEventArgs e) var button = e.originalsource as Button; MessageBox.Show(button.DataContext.ToString()); List 9: ルーティングイベントの利用例 次回はスタイルやテンプレート等の仕組みに付いて説明を行っていく http://www.atmarkit.co.jp/ait/subtop/features/dotnet/app/introwpf_index.html -45-