マイクロソフト系技術情報 Wiki」は、「Open棟梁Project」,「OSSコンソーシアム .NET開発基盤部会」によって運営されています。

目次

概要

クラス階層

System.Object

  • System.Threading.DispatcherObject?
  • System.Windows.DependencyObject?
  • System.Windows.Media.Visual
  • System.Windows.UIElement
  • System.Windows.FrameworkElement?
  • System.Windows.Controls.Control
  • System.Windows.Controls.ContentControl?
  • System.Windows.Controls.ItemsControl?

System.Object

http://msdn.microsoft.com/ja-jp/library/system.object.aspx

マネージ

  • WPFのプログラミング モデルを提供するフレームワークは、CLR上のマネージ コンポーネント(PresentationFramework?.dll、PresentationCore?.dll)として公開される。
  • マネージ コンポーネントには、開発の生産性と信頼性を高める多数の機能(メモリ管理、エラー処理、共通型システムなど)が用意されているが、性能など犠牲となるものもある。

アンマネージ

これに対し、アンマネージ コンポーネント(milcore.dll)はだけ。

  • メモリと実行の細かい制御も、WPF に対する要件の 1 つ。
  • DirectXとの緊密な統合の実現するため。
    ハードウェア レンダリングおよびソフトウェア レンダリングの効率性を考え、
    WPF での表示はすべて DirectX エンジンによって実行されるようになっている。

System.Threading.DispatcherObject?

http://msdn.microsoft.com/ja-jp/library/system.windows.threading.dispatcherobject.aspx

DispatcherObject?は、派生したCLRオブジェクトに、STAオブジェクトとしての動作を実装する基本抽象クラスである。

通常、WPFアプリケーションは、

  • レンダリング スレッド:レンダリングを処理するスレッド
  • UIスレッド:アプリケーションの主スレッド(イベント処理、UI処理)

の2つのスレッドを使用して実行される。

「UIスレッド」と「レンダリング スレッド」の関係は、

  • 「レンダリング スレッド」は、「UIスレッド」がユーザ入力を受け取り、
    イベント処理・UI処理をしている間に、バック グラウンドで実行される。
  • このため、ほとんどのWPFアプリケーションは、単一の「UIスレッド」で済むが、
    状況によっては、応答性を高める目的でバック グラウンド スレッドを使用する場合もある。
  • WPFでは、バック グラウンド処理の実装を支援するDispatcherObject?を使用できる。

WPFのスレッド モデル

  • DispatcherObject?.Invoke、BeginInvoke?メソッドを使用することにより、
    バック グラウンド処理からの「UI変更処理」(= UI要素の操作処理)が、容易に実装可能となる。
  • Invoke、BeginInvoke?のメソッドは、
    • 内部的には(STAと同様にスレッド間通信の手段である)WindowsメッセージキューにUI変更処理をエンキューする。
    • エンキューされた「UI変更処理」は、「UIスレッド」がアイドル状態の際にデキューされて、「UIスレッド」により処理される。
  • これにより、
    • 「UI変更処理」は、必ず、特定の「UIスレッド」により処理されるようになる。
    • マルチ スレッド処理においてスレッド セーフではないUIコントロールの変更処理が問題とならなくなる。

System.Windows.DependencyObject?

http://msdn.microsoft.com/ja-jp/library/system.windows.dependencyobject.aspx

WPFプロパティ システム

DependencyObject?は、派生したCLRオブジェクトに「WPFプロパティ システム」を実装する。

  • WPFのアーキテクチャの理念は、「メソッドやイベント(コードビハインド)よりも、なるべくXAMLのマークアップによる宣言的プロパティを使用する」ことである。
  • これを実現する「WPFプロパティ システム」を実装するDependencyObject?により、開発者は多数の宣言的プロパティを使用し、コントロールに開発者の意図を設定できる。

依存関係プロパティ

このためWPFの開発元は、このXAMLのマークアップによる宣言的プロパティによる制御の範囲を拡大するために、「WPFプロパティ システム」の「依存関係プロパティ」が必要であると判断した。

  • 依存関係プロパティ」は、プロパティの依存関係を把握し、双方向の接続と変更通知を実現する。
  • 具体的には、ソースとなるオブジェクトのプロパティの変更が通知された場合、
    (必須ではないが、INotifyPropertyChanged?インターフェイスを使用すると、オブジェクトによる変更通知の発行が可能になる。)
    ターゲットとなるオブジェクトのプロパティ値を自動的に検証、計算するなど、高度なオブジェクト プロパティ間の接続を実現する。
  • 動的な「リソース」
    • 「スタイル」
    • 「テンプレート」
    • 「アニメーション」
  • 「データ バインディング」
    • 「ビュー・モデル オブジェクト」
  • 「添付プロパティ」
    親子要素のリレーションの値(子要素 → 親要素)を設定する。
  • 「プロパティ値の継承」
    親子要素のリレーションの値(親要素 → 子要素)を設定する。

System.Windows.Media.Visual

http://msdn.microsoft.com/ja-jp/library/system.windows.media.visual.aspx

Visualによる描画のサポート

Visualは、WPF の中心的な機能である描画をサポートする基本抽象クラスである。

Visualは、マネージ コンポーネントとアンマネージ コンポーネントの2つのサブシステムの接続ポイントであり、

  • Visualで定義されているマネージ データ(描画情報、描画方法など)から、
  • 「ビジュアル ツリー」(後述)と呼ばれるツリー構造のアンマネージ データを構成する。
  • これを「レンダリング スレッド」がツリー構造の上から下にスキャンする。

これによって、描画内容をレンダリングする。

レンダリングの概要

  • 出力表示 : 描画内容をレンダリングする。
    • クリッピング : CGで、画像の一部を切り抜く。
    • 変換 : 変換(回転、拡大縮小、傾斜、平行移動)を実行する。
    • 境界の計算 : ビジュアルの外接する四角形を決定する。
    • ヒット テスト : 境界に含まれているかどうかを判定する。
  • Visualオブジェクトの階層構造
    ┬Visual
    ├UIElement
    │└FrameworkElement
    ├ContainerVisual
    │├DrawingVisual
    │├HostVisual
    └Viewport3DVisual
  • System.Windows.Media.DrawingVisual?クラス
    http://msdn.microsoft.com/ja-jp/library/system.windows.media.drawingvisual.aspx
    • 図形(ベクタ グラフィックス)、イメージ、モニター(ビデオ)、グリフ(テキスト)の描画に使用する描画クラス。
    • レイアウトやイベントの処理を実現しないため軽量で、背景やクリップ アートの描画に適す。
    • また、ContainerVisual?クラスから派生するため、Visualオブジェクトのコレクションを格納できる。
  • Visualオブジェクトの描画コンテンツ
    • Visualオブジェクトは、描画コンテンツを格納した下記の4種類のレンダリング データを命令リストとして格納する。
    • また、命令リストを表すオブジェクトとして、DrawingGroup?オブジェクト、Drawingオブジェクトがある。
    • なお、Drawingクラスは基本抽象クラスであり、描画コンテンツの種類に合わせて、様々な派生クラスがある。
      • 描画コンテンツ:ベクタ グラフィックス、イメージ、モニター、グリフ
      • 派生クラス:GeometryDrawing?ImageDrawing?VideoDrawing?GlyphRunDrawing?
  • Visualオブジェクトの命令リスト
    項番命令リスト説明
    ベクタ グラフィックス
    GeometryDrawing?
    Geometry、Pen、Brushなどのベクタ グラフィックス データ
    イメージ
    ImageDrawing?
    デジタル メディア ファイル内の各種イメージ
    モニター
    VideoDrawing?
    デジタル メディア ファイル内の各種ビデオ
    グリフ
    GlyphRunDrawing?
    テキストとフォントで表わされるグリフ
    (特定のフォントにおける文字の物理表現)
  • レンダリング データのレンダリング
    Visualオブジェクトの描画コンテンツであるベクタ グラフィックスのレンダリング データは、
    • DrawingVisual?DrawingImage? オブジェクトのDrawingプロパティに設定されたDrawingGroup?オブジェクト内の1つ以上のDrawingオブジェクト、
    • もしくはDrawingオブジェクトのPen・Brushプロパティ

として表される。

なお、

  • DrawingVisual?DrawingGroup?オブジェクトに、DrawingGroup?、Drawingオブジェクトを設定するには、DrawingContext?オブジェクトを取得して行う。
  • DrawingImage?オブジェクトにDrawingGroup?、Drawingオブジェクトを設定するには、コンストラクタを使用する。
  • DrawingVisual?DrawingImage?オブジェクトが「レンダリング スレッド」によりレンダリングされるとき、
    DrawingGroup?オブジェクトは、自身に設定されているプロパティを次の順に適用する。
  1. Children
  2. OpacityMask?
  3. Opacity
  4. BitmapEffect?
  5. ClipGeometry?
  6. GuidelineSet?
  7. Transform

System.Windows.UIElement

http://msdn.microsoft.com/ja-jp/library/system.windows.uielement.aspx

UIElementは、WPFコア レベル実装の基本クラス

目的

UIElementは、WPFコア レベル実装の基本クラスで、下記を目的とする。

  • 派生クラスで、オーバーライドする仮想メソッドを公開する。
  • 派生クラスで、仮想メソッドをオーバーライドすることによって、中心的なサブシステムを定義する。
    • 「レイアウト」
    • 「ルーティング イベント」
  • 自要素と子要素の「レイアウト」を操作する。
  • キーボード、マウス、およびスタイラスなどによる入力操作に
    対する「ルーティング イベント」と、関連するプロパティが含まれる。

機能

  • 子要素の「レイアウト」(サイズ指定・配置)
  • ユーザ入力への応答、「ルーティング イベント」
  • 「アニメーション」システムを部分的にサポート

プロパティの例

System.Windows.FrameworkElement?

http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.aspx

UIElementに、「レイアウト」、「スタイル」を中心とした機能を追加する基本クラス。

概要

FrameworkElement?は、UIElementの仮想メンバへ、以下を導入することを目的とする。

  • ポリシー・カスタマイズの導入
    • 「レイアウト」
    • 動的「リソース」
    • 「スタイル」
      「テンプレート」は、FrameworkElement?ではなく、コントロール クラスによって導入される。
    • 「アニメーション」
      アニメーションは、 UIElementで定義されているが、FrameworkElement?では、
      FrameworkElement?.BeginStoryboard?メソッドと、その関連メンバを実装することによって拡張できる。
    • 「データ バインディング」
  • 新しいサブシステムの導入

機能

  • 追加の「レイアウト」特性の定義
  • 動的「リソース」
    • 「スタイル」のサポート
    • 「アニメーション」のサポートの強化
  • 「データ バインディング」

プロパティの例

System.Windows.Controls.Control

http://msdn.microsoft.com/ja-jp/library/system.windows.controls.control.aspx

「テンプレート」を使用して外観を定義する UI 要素の基本クラス

Controlの最も重要な機能は、「テンプレート」である。

この「テンプレート」により、

  • コントロールの「外観」を宣言型マークアップでカスタマイズ可能になる(「テンプレート」を複数の子要素から構成する)。
  • また、「イベント ハンドラ」や「イベント トリガ」などもこの「テンプレート」により定義可能で、
    「テンプレート」を「スタイル」化することで、任意の型のコントロールに、これらの「テンプレート」の定義の適用を強制できる。

System.Windows.Controls.ContentControl?

http://msdn.microsoft.com/ja-jp/library/system.windows.controls.contentcontrol.aspx

  • ContentControl? は、Contentプロパティを持つコントロールである。
  • Contentプロパティには、文字列に限らず、様々な子要素を1つだけ設定可能である。
    • 代表的なContentControl?型の(ContentControl?クラスから派生した)コントロールには、Button、CheckBox?RadioButton?などがある。
    • なお、プロパティへ子要素を設定するXAML構文を、
      • 「プロパティ属性構文」
      • 「プロパティ要素構文」

と呼び、この中で、特にContentプロパティに子要素を設定するXAML構文を「コンテンツ構文」と呼ぶ。

  • ContentControl?も、Contentプロパティの表示に特化した「テンプレート」を持つ。

System.Windows.Controls.ItemsControl?

  • ItemsControl?は、1つのコンテンツを設定するContentControl?に対し、
    複数のコンテンツを設定できるItemsコレクション プロパティを持つコントロールである。
  • Itemsコレクション プロパティには、様々な子要素を複数設定可能である。
    • 代表的なItemsControl?型の(ItemsControl?クラスから派生した)コントロールには、
      ComboBox?ListBox?TabControl?TreeView?などがある。
    • なお、同様にItemsコレクション プロパティに子要素を設定するXAML構文を「コンテンツ構文」と呼ぶ。
  • ItemsControl?も、Itemsコレクション プロパティの表示に特化した「テンプレート」を持つ。

要素ツリー

WPFは、ビジュアル要素や、CLRオブジェクトによる「要素ツリー」を構築して、それを処理することでディスプレイへの表示を行う。

「要素ツリー」は、XAMLやプログラムにより、構築される。

例えばXAMLは、次の「プロパティ要素構文」の暗黙的、または明示的な記述で、ツリー構造のCLRオブジェクトをインスタンス化する。

  • DockPanel?.Children(Panel.Children)プロパティ
  • ListBox?.Items(ItemsControl?.Items)プロパティ
<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>
  • ただし、「要素ツリー」は、実体そのものではない「メタファ」であるため、XML DOM のようなXMLツリー操作用のAPIを使用して直接操作することはない。
  • これは、WPFのUIサブシステムのアーキテクチャ、UIフレームワークの構造を理解する上で役立つ。

論理ツリー

「論理ツリー」は、CLRオブジェクトの要素のツリーを表し、「プロパティ継承」や、「ルーティング イベント」を理解する上で役立つ。

以下のXAMLとコードは、同じ「論理ツリー」(CLRオブジェクトの要素のツリー)を生成するため、同じUIを表示する。

XAML

<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オブジェクト(描画内容)が含まれる。

レンダリング順序

「ビジュアル ツリー」階層の最上位の要素(ルート ビジュアル)から、左から右に幅優先で走査・レンダリングされる。

ルート ビジュアル

ルート ビジュアルは、「ビジュアル ツリー」階層の最上位の要素で、ほとんどのアプリケーションでは、ルート ビジュアルは以下のいずれかになる。

  • Window : 「Window画面」
  • NavigationWindow? : 「ナビゲーション フレームワーク」
  • Win32アプリケーションにホストされる場合、Win32ウィンドウにホストされる最上位のビジュアル要素がルート ビジュアルになる。

    MSDN > Windows Presentation Foundation > チュートリアル:
    Win32アプリケーションでのビジュアル オブジェクトのホスト
    http://msdn.microsoft.com/ja-jp/library/ms754039.aspx

論理ツリーと、ビジュアル ツリーの確認方法

実際に、「要素ツリー」の内容を確認したい場合は、以下のヘルパ クラスのGetChildren?メソッドを再帰的に使用して、各ツリーを出力すると良い。

上記のヘルパ クラスの使用例を、サンプルコードとして以下に示す。

LogicalTreeHelper?

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?

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);  
}

WPFプロパティ システム

本項では、DependencyObject?により実装される「WPFプロパティ システム」の

  • 「依存関係プロパティ」
  • 「添付プロパティ」
  • 「プロパティ値の継承」

について説明する。

なお、それぞれのプロパティの定義方法などについても触れるが、実際にこれらのプロパティを独自に定義する必要がある場合は、カスタム コントロール作成時にあたる

依存関係プロパティ

「依存関係プロパティ」は、「WPFプロパティ システム」によってサポートされるプロパティで、WPFに管理される一種の辞書構造を用いて値を保持する。

「依存関係プロパティ」では、変更監視・有効値検証などの機能が使用可能である。

用途

「依存関係プロパティ」は、下記で使用されている。

  • 動的な「リソース」参照
    • 「スタイル」
    • 「アニメーション」
  • 「データ バインディング」
  • 「添付プロパティ」
  • 「プロパティ値の継承」

定義

「依存関係プロパティ」は、一般的なプロパティであるCLRプロパティのように定義するのではなく、「WPFプロパティ システム」に対し「依存関係プロパティ」として登録する必要がある。

  • 定義方法
    以下、「依存関係プロパティ」の定義方法について説明する。
    • public static readonly(VBでは、Public Shared ReadOnly?)なDependencyProperty?型の変数として宣言する。
    • 「依存関係プロパティ」名の末尾に「Property」を付与する(WPFの名称付与基準)
    • 変数宣言時か、静的コンストラクタを初期化する。
      DependencyProperty?.Register メソッドを使用し「依存関係プロパティ」として「WPFプロパティ システム」に登録する。
      http://msdn.microsoft.com/ja-jp/library/system.windows.dependencyproperty.register.aspx
    • 「依存関係プロパティ」は、プロパティの設定・取得方法が特殊なので、
      CLRプロパティでラップし、プロパティにアクセスし易いようにする。
  • 実装例
    以下、「依存関係プロパティ」の定義の実装例を示す。
    // 依存関係プロパティを実装するクラス
    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プロパティ システム」によってサポートされるプロパティで、「添付プロパティ」のほとんどが「依存関係プロパティ」として実装される。

用途

  • 「添付プロパティ」は、親要素で定義されるプロパティに対し、
    子要素がそれぞれ別の値を指定するなど、任意のオブジェクトに対して設定可能なグローバル プロパティとして機能する。
  • 「添付プロパティ」の代表的な使用例には、
    「パネル要素」などの子要素を格納する親要素が、子要素のレイアウトを制御するための各プロパティがある。
  • ここでは、
    • 親要素として、Canvas要素
    • 子要素として、TextBlock?要素

を例に挙げて説明する。

TextBlock?のレイアウトを制御するため、

  • TextBlock?に(仮に)CanvasTop?CanvasLeft?プロパティを持たせた場合、
  • TextBlock?が、Canvas要素の子要素ではない場合、

無駄なプロパティとデータを持つことになる。

この問題を解決するため、「添付プロパティ」は、親要素が子要素の動作を制御するためのプロパティを子要素から指定可能な一種のグローバル プロパティとして実装する。
以下は、レイアウトを制御するCanvas要素の「添付プロパティ」であるCanvas.Top、Canvas.Leftプロパティへ、XAMLからTextBlock?要素から値を設定する方法である。

<Canvas>
  <TextBlock Canvas.Top="10" Canvas.Left="20">
    Hello World!
  </TextBlock>
</Canvas>

子要素から親要素への「添付プロパティ」設定の際、内部的には「添付プロパティ」のアクセッサ メソッド(後述)のキーに子要素が指定される。

定義

「添付プロパティ」を定義する場合は、「依存関係プロパティ」と異なり、

  • DependencyProperty?.Registerメソッドではなく、
  • DependencyProperty?.RegisterAttached? メソッドを使用し

「添付プロパティ」として登録する。

また、「添付プロパティ」はCLRプロパティ ラッパではなく、

  • Get(プロパティ名)
  • Set(プロパティ名)

の名称付与基準に従った、専用の静的 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);

プロパティ継承(包含継承)

  • 「WPFプロパティ システム」では、包含継承(HTMLコードと同様に、親要素に書いた属性値が子要素に継承される。
    HTMLでは、例えば、body要素に対してstyle属性やfont属性でフォント・サイズを指定すると、body要素内のすべての要素のフォント・サイズが変化する。
    ※ 注:オブジェクト指向プログラミングにおける継承(派生クラスがその基本クラスからメンバ定義を継承する)とは異なる概念。)をサポートしている。
  • これを実現するのが「プロパティ値の継承」の機能である。
    • 「プロパティ値の継承」がされる一部のプロパティは、「要素ツリー」の最も近い親要素から特定のプロパティの値を継承し、親要素のプロパティ値が変更された場合、自動的に子要素に反映する。
    • この動作は、子要素から親要素のプロパティを設定する「添付プロパティ」と逆の働きであるため、
      「プロパティ値の継承」の仕組みも、「添付プロパティ」と同様の方式で実装されていることが分かる
      (そのため、「添付プロパティ」と同様、プロパティの登録にDependencyProperty?.RegisterAttached?メソッドを使用する)。

用途

  • 包含継承の機能は、親要素、例えば、ルート要素(Window or Page)に
    一度だけ定義することで、画面全体のポリシーを統一するようなプロパティに適している。
  • 「プロパティ値の継承」がされるプロパティの一例として、以下のものがある。
    • Control.FontSize?プロパティ
    • FrameworkElement?.FlowDirection?プロパティ.etc

定義

DependencyProperty?.RegisterAttached?メソッドの第4引数に指定するPropertyMetadata?FrameworkPropertyMetadata?)クラスのオプション 設定で、「プロパティ値の継承」を可能に設定する。

なお、「プロパティ値の継承」のブロッキング境界として、Frameなどのコントロールを使用できる。

データ バインディング

WPFでは、

  • ビュー(XAML:データの表示)と
  • モデル(コードビハインド:チェック処理や、データアクセス処理などアプリケーションの処理)

を分離するための仕組みとして、「データ バインディング」という機能を提供している。

「依存関係プロパティ」との関係

これには、前述の「依存関係プロパティ」を使用している。

  • 「データ バインディング」を利用すれば、
    • ビューとモデルの接点を、任意のプロパティ接続だけに限定できる。
    • このため、ビューの内部にビューとモデルを接続するロジックを持つ必要が無くなり、
      ビュー内のコード量を削減すると同時に疎結合を実現できる。
  • 「データ バインディング」により、
    2つのオブジェクトを結合するが、通常、
    • ビュー側のXAMLで生成されたCLRオブジェクトを「バインディング ターゲット」(=結合先)
    • モデル側のコードビハインドで生成されたCLRオブジェクトを「バインディング ソース」(=結合元)

と呼ぶ。

「バインディング ターゲット」と「バインディング ソース」の結合

この2つのオブジェクトを結合するためにBinding オブジェクトを使用する。

  • なお、
    • 「バインディング ターゲット」に入力されたデータや、
    • 「バインディング ソース」の更新通知によるデータの、

妥当性検証の機能は、(継承する)
「WPFプロパティ システム」を実装するDependencyObject?により提供される。

  • また、この「バインディング ソース」からの更新通知の機能は、
    (実装する)INotifyPropertyChanged?インターフェイスにより提供される。

それぞれ、これに合わせた実装が必要になる。

データ バインディングの構成要素

このため、「データ バインディング」の機能は、
以下の3つの要素によって提供されると言って良い。

  • DependencyObject?オブジェクト
  • INotifyPropertyChanged? インターフェイス
  • Bindingオブジェクト

DependencyObject?オブジェクト

ビュー側「バインディング ターゲット」のDependencyObject?オブジェクト

INotifyPropertyChanged? インターフェイス

モデル側「バインディング ソース」のINotifyPropertyChanged? インターフェイス

Bindingオブジェクト

上記2つの要素を結び付けるBindingオブジェクト

「データ バインディング」のプロパティ

Bindingクラスが持つ主要なプロパティ

項番プロパティ説明
Modeデータが反映される方向を、以下の列挙型から選択して指定する。
OneWay?:「バインディング ソース」変更時、「バインディング ターゲット」を更新する。
OneWayToSource?:「バインディング ターゲット」変更時、「バインディング ソース」を更新する。
 ※ WPFのみで、「Silverlight」には無いモード
TwoWay?:「バインディング ソース」、「バインディング ターゲット」が変更された場合、対応する「バインディング ターゲット」、「バインディング ソース」を更新する。
OneTime?:アプリケーション起動時のみ、「バインディング ターゲット」のデータを更新(StaticResource?に接続する場合などに使用する)
・Default:「バインディング ターゲット」の既定のModeとなる。
 ※ WPFのみで、「Silverlight」には無いモード
Source「バインディング ソース」を指定する。
Path「バインディング ソース」のプロパティ名を指定する(名前空間のフルパスで指定可)。

XAMLで「データ バインディング」を実装

「データ バインディング」は、XAML上の要素のプロパティ値として「バインディングのマークアップ拡張」である
「{Binding ・・・}」という記述を使用してBindingオブジェクトを生成・初期化することで実装する。

DataContext?の使用

  • 「バインディング ソース」と「バインディング ターゲット」の数が増え、それぞれのオブジェクト間のマッピングが面倒な場合、
    Bindingオブジェクトの規定のターゲットであるDataContext?プロパティに「バインディング ソース」を設定することで、Bindingオブジェクトへの「バインディング ソース」の指定が不要になる。
  • 「データ バインディング」では、「バインディング ターゲット」と「バインディング ソース」が、リフレクションによるプロパティ名にて結び付けられている。
    このため、FrameworkElement?.DataContext?プロパティに、任意のカスタム型を渡すことができる。
  • なお、DataContext?プロパティは、「プロパティ値の継承」に対応している。
    このため、指定した「バインディング ソース」は、親要素から子要素に継承されるので、
    親要素(もしくは自身)のDataContext?プロパティに「バインディング ソース」を1回指定すればよい。

型変換(値コンバータ)

  • 「データ バインディング」では、
    • 「バインディング ターゲット」と
    • 「バインディング ソース」で

プロパティ型が異なる場合、暗黙的な型変換が行われる。

  • 型変換の動作のカスタマイズの必要性
  • 暗黙の型変換が失敗する場合
  • 入力数値から背景色を変更するような処理を実装したい場合。
  • 型変換の動作のカスタマイズの実装
  • 暗黙の型変換(または値変換)が失敗する場合、nullが返される。
    この対応としてIValueConverter? インターフェイスを実装した
    「値コンバータ」を実装することで明示的な型変換(または値変換)が可能になる。
  • なお、「値コンバータ」で型変換できない場合も同様に、nullを返すように実装する。
    nullが返された場合、通常は何も表示されないが、これをカスタマイズする場合は、
    Binding.TargetNullValue?プロパティを実装し、ソース値が nullのときに返される値を指定する。

色々なデータ バインディングのパターン

(1)コードビハインドからの単方向のデータ バインディング

  • コードビハインドからのデータ バインディング
    「データ バインディング」の基本的な動作を理解するために、
    Bindingオブジェクトをコードビハインドで自作し、
    「バインディング ソース」と「バインディング ターゲット」を
    OneWay?モードで結び付け、「データ バインディング」を行う。
  • 「バインディング ソース」にDataContext?を使用して、
    「バインディング ソース」と「バインディング ターゲット」を
    OneWay?モードで結び付け、「データ バインディング」を行う。

(2)変更通知の追加(入力値の自動計算アプリケーション等)

  • (1)に以下を加えて、入力値の自動計算アプリケーションを作成できる。
    • (1)と同じ、OneWay?モードで結び付け、「データ バインディング」を行う。
    • OneWayToSource?モードで結び付け、「データ バインディング」を行う。
    • 変更通知を追加し、計算された値を「バインディング ターゲット」に反映する。
  • 変更通知を追加方法
    • OneWay? の「バインディング ソース」には、INotifyPropertyChanged?インターフェイスを実装して、プロパティ値の変更通知処理を実装する必要がある。
    • なお、UI要素の表示に関するプロパティは、基本的に「依存関係プロパティ」として実装されており、既定で変更通知をサポートしている。このため、既定で双方向接続が可能である。

(3)双方向のデータ バインディング(TextBox?とSliderの双方向接続等)

TwoWay?モードで結び付け、「データ バインディング」を行う。

(4)値コンバータの使用

以下に値コンバータを適用できる。

  • OneWay?モードを使用した「データ バインディング」
    TwoWay?モードに対応した「値コンバータ」は、Convertメソッドを実装する必要がある。
  • OneWayToSource?モードを併用した「データ バインディング」
    TwoWay?モードに対応した「値コンバータ」は、ConvertBack?メソッドを実装する必要がある。
  • TwoWay?モードを使用した「データ バインディング」
    TwoWay?モードに対応した「値コンバータ」は、Convert、ConvertBack?メソッドの双方を実装する必要がある。

(5)ItemsSource?へのデータ バインディング

  • ItemsSource?へのデータ バインディング
    ItemsControl?から派生した要素のItemsSource?属性にコレクションを「データ バインディング」する場合、対象オブジェクトは反復処理をサポートしている必要がある。
  • インデクサによるデータ バインディング
    DataGrid?の列にDataTable?の指定の列をデータバインディングする場合、
    XAMLの「バインディングのマークアップ拡張」にて、Bindingプロパティである
    Path属性に角括弧を指定することで、インデクサを使用して接続可能である。

ルーティング イベント

イベントを生成したUIコントロール上だけでなく、「ビジュアル ツリー」内の複数のリスナ上でイベント ハンドラを呼び出すことができるWPFのイベント。

例えば、Buttonコントロールが「テンプレート」を使用し、複数のコントロールから構成される場合、
下位のコントロールのイベントを上位のButtonコントロールでもハンドルできる。

ルーティング方法

「ルーティング イベント」は、3つのルーティング方法のいずれかを使用する。

項番区分機能
トンネルツリーを下方向へ辿る。
最初に、「ビジュアル ツリー」のルートのイベント ハンドラが呼び出され、次に、イベントを発生させた要素に到達するまで、「経路」沿いの「トンネル ルーティング イベント」のイベント ハンドラを呼び出す。
なお、「トンネル ルーティング イベント」のイベント名は、先頭に「Preview」を付与するルールとなっている。
バブルツリーを上方向へ辿る。
最初に、イベントを発生させた要素のイベント ハンドラが呼び出され、次に、「ビジュアル ツリー」のルートに到達するまで、「経路」沿いの「バブル ルーティング イベント」のイベント ハンドラを呼び出す。
直接イベントを発生させた要素のみに、イベント ハンドラを呼び出す機会が与えられる。 これは、Windows フォームのイベントと似ている。ただし、標準のCLRイベントとは異なり、「クラス処理」をサポートする。

多くの場合、WPF から提供される入力イベントは、「トンネル ルーティング イベント」 / 「バブル ルーティング イベント」ペアとして実装される。

通常の使い方

イベントを発生元の要素で処理する限り、「ルーティング イベント」の動作はあまり表面には見えないので、イベントが「ルーティング イベント」として実装されていることに気を配る必要はない。

これは、「ルーティング イベント」は、

  • 共通ハンドラを定義する場合や、
  • カスタム コントロールを複合化する場合などの、

「特定のシナリオ」で効果を発揮するためである。

例えば、以下は、Buttonコントロールのイベントを発生元の要素で処理するイベント ハンドラを実装する例である。
この場合、WPFのデザイナにButtonコントロールをD & Dし、これをダブル クリックすることで、発生元の要素で処理するButton.Clickイベント ハンドラを実装できる。

XAML

<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

XAMLでは、イベント リスナである要素の属性にイベント ハンドラを指定することで、
「型コンバータ」により、イベント ハンドラを設定できる。
上記のClick="button1_Click"が、それに該当する。

  • コードビハインド
    なお、イベントの指定をXAMLの「型コンバータ」からではなく、コード (C#) から指定する場合は、
    次の2種類の方法(メソッド、演算子)で行うことができる。
    • UIElement.AddHandler?メソッドを利用する場合:
      this.button1.AddHandler(Button.ClickEvent, new RoutedEventHandler(button1_Click));
    • オーバーロードされた演算子を利用する場合:
      this.button1.Click += new RoutedEventHandler(button1_Click);

サンプル

下記のXAMLは、

  • 「トンネル ルーティング イベント」:PreviewMouseDown?イベント
  • 「バブル ルーティング イベント」:MouseDown?イベント

の動作を確認するためのサンプル。

論理ツリー

StackPanel? - Border - Rectangle

XAML

<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」を実行すると、ルーティングは停止する。例えば、「トンネル ルーティング イベント」でルーティングを停止すれば、「バブル ルーティング イベント」も発生しなくなる。


Tags: :.NET開発, :UIサブシステム, :WPF/Silverlight, XAML


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-03-12 (日) 15:22:59 (826d)