Open棟梁Project - マイクロソフト系技術情報 Wiki

目次

概要

クラス階層

System.Object

System.Object

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

マネージ

アンマネージ

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

System.Threading.DispatcherObject?

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

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

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

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

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

WPFのスレッド モデル

System.Windows.DependencyObject?

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

WPFプロパティ システム

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

依存関係プロパティ

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

System.Windows.Media.Visual

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

Visualによる描画のサポート

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

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

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

レンダリングの概要

として表される。

なお、

  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の仮想メンバへ、以下を導入することを目的とする。

機能

プロパティの例

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

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

System.Windows.Controls.ItemsControl?

要素ツリー

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を表示する。

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

レンダリング順序

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

ルート ビジュアル

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

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

実際に、「要素ツリー」の内容を確認したい場合は、以下のヘルパ クラスの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プロパティ システム」に対し「依存関係プロパティ」として登録する必要がある。

機能

「依存関係プロパティ」では、以下のような機能が使用可能になる。

なお、以下それぞれの機能の説明と、DependencyProperty?.Registerメソッドを使用して「依存関係プロパティ」を登録する際の第4引数に指定するPropertyMetadata? クラスと、そのコールバックのコード例を示す。

添付プロパティ

「添付プロパティ」は、「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?オブジェクト

ビュー側「バインディング ターゲット」の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?の使用

型変換

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

(1)一方向のデータ バインディング

(2)変更通知の追加

変更通知の追加(モード:OneWay? & OneWayToSource?

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

値コンバータの使用(モード:OneWay? & OneWayToSource?

モード:OneWay?OneWayToSource?を併用した「データ バインディング」は、

できる。

(3)

(4)

ルーティング イベント

イベントを生成した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は、

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

論理ツリー

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


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS