Open棟梁Project - マイクロソフト系技術情報 Wiki
System.Object
http://msdn.microsoft.com/ja-jp/library/system.object.aspx
これに対し、アンマネージ コンポーネント(milcore.dll)はだけ。
http://msdn.microsoft.com/ja-jp/library/system.windows.threading.dispatcherobject.aspx
DispatcherObject?は、派生したCLRオブジェクトに、STAオブジェクトとしての動作を実装する基本抽象クラスである。
通常、WPFアプリケーションは、
の2つのスレッドを使用して実行される。
「UIスレッド」と「レンダリング スレッド」の関係は、
このメソッドに指定されたデリゲートは、同期実行されるため、バック グラウンド スレッドが実行結果を表示する「UI変更処理」に利用することが多い。
http://msdn.microsoft.com/ja-jp/library/system.windows.dependencyobject.aspx
DependencyObject?は、派生したCLRオブジェクトに「WPFプロパティ システム」を実装する。
このためWPFの開発元は、このXAMLのマークアップによる宣言的プロパティによる制御の範囲を拡大するために、「WPFプロパティ システム」の「依存関係プロパティ」が必要であると判断した。
http://msdn.microsoft.com/ja-jp/library/system.windows.media.visual.aspx
Visualは、WPF の中心的な機能である描画をサポートする基本抽象クラスである。
Visualは、マネージ コンポーネントとアンマネージ コンポーネントの2つのサブシステムの接続ポイントであり、
これによって、描画内容をレンダリングする。
┬Visual ├UIElement │└FrameworkElement ├ContainerVisual │├DrawingVisual │├HostVisual └Viewport3DVisual
として表される。
なお、
http://msdn.microsoft.com/ja-jp/library/system.windows.uielement.aspx
UIElementは、WPFコア レベル実装の基本クラス
UIElementは、WPFコア レベル実装の基本クラスで、下記を目的とする。
http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.aspx
UIElementに、「レイアウト」、「スタイル」を中心とした機能を追加する基本クラス。
FrameworkElement?は、UIElementの仮想メンバへ、以下を導入することを目的とする。
http://msdn.microsoft.com/ja-jp/library/system.windows.controls.control.aspx
「テンプレート」を使用して外観を定義する UI 要素の基本クラス
Controlの最も重要な機能は、「テンプレート」である。
この「テンプレート」により、
http://msdn.microsoft.com/ja-jp/library/system.windows.controls.contentcontrol.aspx
と呼び、この中で、特にContentプロパティに子要素を設定するXAML構文を「コンテンツ構文」と呼ぶ。
WPFは、ビジュアル要素や、CLRオブジェクトによる「要素ツリー」を構築して、それを処理することでディスプレイへの表示を行う。
「要素ツリー」は、XAMLやプログラムにより、構築される。
例えばXAMLは、次の「プロパティ要素構文」の暗黙的、または明示的な記述で、ツリー構造のCLRオブジェクトをインスタンス化する。
<DockPanel> <!--implicit: <DockPanel.Children>--> → プロパティ要素構文(省略可能) <ListBox DockPanel.Dock="Top"> <!--implicit: <ListBox.Items>--> → プロパティ要素構文(省略可能) <ListBoxItem> <TextBlock>Dog</TextBlock> </ListBoxItem> <ListBoxItem> <TextBlock>Cat</TextBlock> </ListBoxItem> <ListBoxItem> <TextBlock>Fish</TextBlock> </ListBoxItem> <!--implicit: </ListBox.Items>--> → プロパティ要素構文(省略可能) </ListBox> <Button Height="20" Width="100" DockPanel.Dock="Top">Buy a Pet</Button> <!--implicit: </DockPanel.Children>--> → プロパティ要素構文(省略可能) </DockPanel>
「論理ツリー」は、CLRオブジェクトの要素のツリーを表し、「プロパティ継承」や、「ルーティング イベント」を理解する上で役立つ。
以下のXAMLとコードは、同じ「論理ツリー」(CLRオブジェクトの要素のツリー)を生成するため、同じUIを表示する。
<DockPanel> <ListBox Width="100"> <ListBoxItem>Dog</ListBoxItem> <ListBoxItem>Cat</ListBoxItem> <ListBoxItem>Fish</ListBoxItem> </ListBox> <Button Click="OnClick">OK</Button> </DockPanel>
DockPanel dp = new DockPanel(); ListBox lbx = new ListBox(); lbx.Width = 100; ListBoxItem lbxItem = null; lbxItem = new ListBoxItem(); lbxItem.Content = "Dog"; lbx.Items.Add(lbxItem); lbxItem = new ListBoxItem(); lbxItem.Content = "Cat"; lbx.Items.Add(lbxItem); lbxItem = new ListBoxItem(); lbxItem.Content = "Fish"; lbx.Items.Add(lbxItem); dp.Children.Add(lbx); Button btn = new Button(); btn.Content = "OK"; btn.Click += new RoutedEventHandler(this.OnClick); dp.Children.Add(btn); this.AddChild(dp);
「ビジュアル ツリー」は、「論理ツリー」と異なり、ビジュアル要素のツリーを表し、コントロールの「テンプレート」が展開される。
このため、アプリケーションのUIで使用する、全てのVisualオブジェクト(描画内容)が含まれる。
「ビジュアル ツリー」階層の最上位の要素(ルート ビジュアル)から、左から右に幅優先で走査・レンダリングされる。
ルート ビジュアルは、「ビジュアル ツリー」階層の最上位の要素で、ほとんどのアプリケーションでは、ルート ビジュアルは以下のいずれかになる。
MSDN > Windows Presentation Foundation > チュートリアル:
Win32アプリケーションでのビジュアル オブジェクトのホスト
http://msdn.microsoft.com/ja-jp/library/ms754039.aspx
実際に、「要素ツリー」の内容を確認したい場合は、以下のヘルパ クラスのGetChildren?メソッドを再帰的に使用して、各ツリーを出力すると良い。
上記のヘルパ クラスの使用例を、サンプルコードとして以下に示す。
LogicalTreeHelper?を使用する場合は、以下のPrintLogicalTree?メソッドを実行する。
private void PrintLogicalTree() { Debug.WriteLine("PrintLogicalTree"); PrintLogicalTree(0, this); } // 論理ツリーを出力する。 // DependencyObjectの場合は、子要素も再帰的に表示する private void PrintLogicalTree(int level, DependencyObject obj) { PrintObject(level, obj); foreach (var child in LogicalTreeHelper.GetChildren(obj)) { if (child is DependencyObject) { PrintLogicalTree(level + 1, (DependencyObject)child); } else { PrintObject(level + 1, child); } } }
VisualTreeHelper?を使用する場合は、以下のPrintVisualTree?メソッドを実行する。
private void PrintVisualTree() { Debug.WriteLine("PrintVisualTree"); PrintVisualTree(0, this); } // ビジュアル ツリーを表示する。 // DependencyObjectの場合はビジュアル ツリー上の子要素も再帰的に出力していく private void PrintVisualTree(int level, DependencyObject obj) { PrintObject(level, obj); foreach (var child in GetVisualChildren(obj)) { if (child is DependencyObject) { PrintVisualTree(level + 1, (DependencyObject)child); } else { PrintObject(level + 1, child); } } } // ビジュアル ツリーの子要素の列挙を返す private IEnumerable<object> GetVisualChildren(DependencyObject obj) { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { yield return VisualTreeHelper.GetChild(obj, i); } }
結果をインデントつきで出力
// ToStringの結果をインデントつきで出力 private void PrintObject(int level, object obj) { Debug.WriteLine(new string('\t', level) + obj); }
本項では、DependencyObject?により実装される「WPFプロパティ システム」の
について説明する。
なお、それぞれのプロパティの定義方法などについても触れるが、実際にこれらのプロパティを独自に定義する必要がある場合は、カスタム コントロール作成時にあたる。
「依存関係プロパティ」は、「WPFプロパティ システム」によってサポートされるプロパティで、WPFに管理される一種の辞書構造を用いて値を保持する。
「依存関係プロパティ」では、変更監視・有効値検証などの機能が使用可能である。
「依存関係プロパティ」は、下記で使用されている。
「依存関係プロパティ」は、一般的なプロパティであるCLRプロパティのように定義するのではなく、「WPFプロパティ システム」に対し「依存関係プロパティ」として登録する必要がある。
// 依存関係プロパティを実装するクラス public class ConcreteDependencyObject : DependencyObject { // 依存関係プロパティ public static readonly DependencyProperty CaptionProperty; // 静的コンストラクタ static ConcreteDependencyObject(){ // 静的コンストラクタで依存関係プロパティを登録 CaptionProperty = DependencyProperty.Register( "Caption", // プロパティ名 typeof(string), // プロパティの型 typeof(ConcreteDependencyObject), // プロパティの所有者 new PropertyMetadata()); // 各種メタデータ } // 依存関係プロパティは設定・取得方法が特殊であるので、 // 以下のように、CLRプロパティでラップする。 public string Caption { get { return this.GetValue(ConcreteDependencyObject.CaptionProperty) as string; } set { this.SetValue(ConcreteDependencyObject.CaptionProperty, value); } } }
「依存関係プロパティ」では、以下のような機能が使用可能になる。
なお、以下それぞれの機能の説明と、DependencyProperty?.Registerメソッドを使用して「依存関係プロパティ」を登録する際の第4引数に指定するPropertyMetadata? クラスと、そのコールバックのコード例を示す。
new PropertyMetadata("DefaultValue")
new PropertyMetadata("DefaultValue", new PropertyChangedCallback(OnPropertyChangedCallback)) // プロパティ値の変更監視コールバック private static void OnPropertyChangedCallback( DependencyObject d, DependencyPropertyChangedEventArgs e) { // プロパティ値の変更監視の例: string oldValue = (string)e.OldValue; string newValue = (string)e.NewValue; DependencyProperty dp = e.Property; }
new PropertyMetadata("DefaultValue", new PropertyChangedCallback(OnPropertyChangedCallback), new CoerceValueCallback(OnCoerceValueCallBack)) // 依存関係プロパティ値の強制コールバック private static object OnCoerceValueCallBack(DependencyObject d, object value) { // プロパティ値の強制の例: // Captionプロパティ値に「"ChangeValue"」が設定されない限り // Captionプロパティ値は「"DefaultValue"」を強制する。 if (value.ToString() == "ChangeValue") return value; return "DefaultValue"; }
new PropertyMetadata("DefaultValue", new PropertyChangedCallback(OnPropertyChangedCallback), new CoerceValueCallback(OnCoerceValueCallBack), new ValidateValueCallback(OnValidateValueCallback)); // プロパティ値の有効値検証コールバック private static bool OnValidateValueCallback(object value) { // プロパティ値の有効値検証の例: // Captionプロパティ値に「" DengerousValue "」が設定されるとエラーになる。 if (value.ToString() == "DengerousValue") return false; return true; }
「添付プロパティ」は、「WPFプロパティ システム」によってサポートされるプロパティで、「添付プロパティ」のほとんどが「依存関係プロパティ」として実装される。
を例に挙げて説明する。
TextBlock?のレイアウトを制御するため、
無駄なプロパティとデータを持つことになる。
この問題を解決するため、「添付プロパティ」は、親要素が子要素の動作を制御するためのプロパティを子要素から指定可能な一種のグローバル プロパティとして実装する。
以下は、レイアウトを制御するCanvas要素の「添付プロパティ」であるCanvas.Top、Canvas.Leftプロパティへ、XAMLからTextBlock?要素から値を設定する方法である。
<Canvas> <TextBlock Canvas.Top="10" Canvas.Left="20"> Hello World! </TextBlock> </Canvas>
子要素から親要素への「添付プロパティ」設定の際、内部的には「添付プロパティ」のアクセッサ メソッド(後述)のキーに子要素が指定される。
「添付プロパティ」を定義する場合は、「依存関係プロパティ」と異なり、
「添付プロパティ」として登録する。
また、「添付プロパティ」はCLRプロパティ ラッパではなく、
の名称付与基準に従った、専用の静的 get、setアクセッサ メソッドを実装する必要がある。
public static void Setプロパティ変数名(UIElement element, Boolean value) { element.SetValue(this.プロパティ変数, value); } public static Boolean Getプロパティ変数名(UIElement element) { return (Boolean)element.GetValue(this.プロパティ変数); }
これらのアクセッサ メソッドは、XAMLリーダが「添付プロパティ」をXAMLの属性として認識し、適切な型を解決できるようにするために必要となる。
コードビハインドから、これらのアクセッサ メソッドを使用して「添付プロパティ」を設定する際は、キーに子要素を指定する。
<Canvas x:Name="myCanvas"> </Canvas> TextBlock textBlock = new TextBlock(); textBlock.Text = "Hello World!"; // 添付プロパティを設定 Canvas.SetTop(textBlock, 10); Canvas.SetLeft(textBlock, 20); this.myCanvas.Children.Add(textBlock);
DependencyProperty?.RegisterAttached?メソッドの第4引数に指定するPropertyMetadata?(FrameworkPropertyMetadata?)クラスのオプション 設定で、「プロパティ値の継承」を可能に設定する。
なお、「プロパティ値の継承」のブロッキング境界として、Frameなどのコントロールを使用できる。
WPFでは、
を分離するための仕組みとして、「データ バインディング」という機能を提供している。
これには、前述の「依存関係プロパティ」を使用している。
と呼ぶ。
この2つのオブジェクトを結合するためにBinding オブジェクトを使用する。
なお、
妥当性検証の機能は、(継承する)DependencyObject?により提供される。
また、
このため、「データ バインディング」の機能は、
以下の3つの要素によって提供されると言って良い。
ビュー側「バインディング ターゲット」のDependencyObject?オブジェクト
モデル側「バインディング ソース」のINotifyPropertyChanged? インターフェイス
上記2つの要素を結び付けるBindingオブジェクト
Bindingクラスが持つ主要なプロパティ
項番 | プロパティ | 説明 |
1 | Mode | データが反映される方向を、以下の列挙型から選択して指定する。 ・OneWay?:「バインディング ソース」変更時、「バインディング ターゲット」を更新する。 ・OneWayToSource?:「バインディング ターゲット」変更時、「バインディング ソース」を更新する。 ※ WPFのみで、「Silverlight」には無いモード ・TwoWay?:「バインディング ソース」、「バインディング ターゲット」が変更された場合、対応する「バインディング ターゲット」、「バインディング ソース」を更新する。 ・OneTime?:アプリケーション起動時のみ、「バインディング ターゲット」のデータを更新(StaticResource?に接続する場合などに使用する) ・Default:「バインディング ターゲット」の既定のModeとなる。 ※ WPFのみで、「Silverlight」には無いモード |
2 | Source | 「バインディング ソース」を指定する。 |
3 | Path | 「バインディング ソース」のプロパティ名を指定する(名前空間のフルパスで指定可)。 |
「データ バインディング」は、XAML上の要素のプロパティ値として「バインディングのマークアップ拡張」である
「{Binding ・・・}」という記述を使用してBindingオブジェクトを生成・初期化することで実装する。
変更通知の追加(モード:OneWay? & OneWayToSource?)
値コンバータの使用(モード:OneWay? & OneWayToSource?)
モード:OneWay? & OneWayToSource?を併用した「データ バインディング」は、
できる。
イベントを生成したUIコントロール上だけでなく、「ビジュアル ツリー」内の複数のリスナ上でイベント ハンドラを呼び出すことができるWPFのイベント。
例えば、Buttonコントロールが「テンプレート」を使用し、複数のコントロールから構成される場合、
下位のコントロールのイベントを上位のButtonコントロールでもハンドルできる。
「ルーティング イベント」は、3つのルーティング方法のいずれかを使用する。
項番 | 区分 | 機能 |
1 | トンネル | ツリーを下方向へ辿る。 最初に、「ビジュアル ツリー」のルートのイベント ハンドラが呼び出され、次に、イベントを発生させた要素に到達するまで、「経路」沿いの「トンネル ルーティング イベント」のイベント ハンドラを呼び出す。 なお、「トンネル ルーティング イベント」のイベント名は、先頭に「Preview」を付与するルールとなっている。 |
2 | バブル | ツリーを上方向へ辿る。 最初に、イベントを発生させた要素のイベント ハンドラが呼び出され、次に、「ビジュアル ツリー」のルートに到達するまで、「経路」沿いの「バブル ルーティング イベント」のイベント ハンドラを呼び出す。 |
3 | 直接 | イベントを発生させた要素のみに、イベント ハンドラを呼び出す機会が与えられる。 これは、Windows フォームのイベントと似ている。ただし、標準のCLRイベントとは異なり、「クラス処理」をサポートする。 |
多くの場合、WPF から提供される入力イベントは、「トンネル ルーティング イベント」 / 「バブル ルーティング イベント」ペアとして実装される。
イベントを発生元の要素で処理する限り、「ルーティング イベント」の動作はあまり表面には見えないので、イベントが「ルーティング イベント」として実装されていることに気を配る必要はない。
これは、「ルーティング イベント」は、
「特定のシナリオ」で効果を発揮するためである。
例えば、以下は、Buttonコントロールのイベントを発生元の要素で処理するイベント ハンドラを実装する例である。
この場合、WPFのデザイナにButtonコントロールをD & Dし、これをダブル クリックすることで、発生元の要素で処理するButton.Clickイベント ハンドラを実装できる。
<Grid> <Button Height="23" Name="button1" Click="button1_Click">Button</Button> </Grid>
public partial class Window1 : Window { public Window1() { InitializeComponent(); } private void button1_Click(object sender, RoutedEventArgs e) { // button1_Click } }
XAMLでは、イベント リスナである要素の属性にイベント ハンドラを指定することで、
「型コンバータ」により、イベント ハンドラを設定できる。
上記のClick="button1_Click"が、それに該当する。
this.button1.AddHandler(Button.ClickEvent, new RoutedEventHandler(button1_Click));
this.button1.Click += new RoutedEventHandler(button1_Click);
下記のXAMLは、
の動作を確認するためのサンプル。
StackPanel? - Border - Rectangle
<StackPanel x:Name="stackPanel1" Height="100" Width="100" Orientation="Vertical" MouseDown="stackPanel1_MouseDown" PreviewMouseDown="stackPanel1_PreviewMouseDown"> <Border x:Name="border1" MouseDown="border1_MouseDown" PreviewMouseDown="border1_PreviewMouseDown"> <Rectangle x:Name="rect1" Height="100" Width="100" Fill="Black" MouseDown="rect1_MouseDown" PreviewMouseDown="rect1_PreviewMouseDown"> </Rectangle> </Border> </StackPanel>
/// <summary>stackPanel1のMouseDownイベント</summary> private void stackPanel1_MouseDown(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine("→ stackPanel1_MouseDown"); } /// <summary>stackPanel1のPreviewMouseDownイベント</summary> private void stackPanel1_PreviewMouseDown(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine("→ stackPanel1_PreviewMouseDown"); } /// <summary>border1のMouseDownイベント</summary> private void border1_MouseDown(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine("→ border1_MouseDown"); } /// <summary>border1のPreviewMouseDownイベント</summary> private void border1_PreviewMouseDown(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine("→ border1_PreviewMouseDown"); } /// <summary>rect1のMouseDownイベント</summary> private void rect1_MouseDown(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine("→ rect1_MouseDown"); } /// <summary>rect1のPreviewMouseDownイベント</summary> private void rect1_PreviewMouseDown(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine("→ rect1_PreviewMouseDown"); }
このコードを実装して、画面上のRectangleをクリックすると、以下のようなDebug出力を確認でき、この出力から「トンネル ルーティング イベント」 / 「バブル ルーティング イベント」ペアが正しく動作していることを確認できる。
stackPanel1_PreviewMouseDown → border1_PreviewMouseDown → rect1_PreviewMouseDown → rect1_MouseDown → border1_MouseDown → stackPanel1_MouseDown
なお、各イベント ハンドラで「e.Handled = true」を実行すると、ルーティングは停止する。例えば、「トンネル ルーティング イベント」でルーティングを停止すれば、「バブル ルーティング イベント」も発生しなくなる。