Skip to content

MS_WPFArchitecture

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

WPFのアーキテクチャ

  • 戻る(WPF)

概要

補足(本ページの読み方): 本ページは
WPF の内部構造を、クラス階層に沿って積み上げて説明するという
構成をとっている。この順序自体に意味がある。

【本ページの構成= WPF の設計そのもの】

  ① クラス階層        … 各層が「何を追加するか」
  ② 要素ツリー        … 論理ツリーとビジュアル ツリー
  ③ WPF プロパティ システム … 依存関係/添付/継承
  ④ データ バインディング   … ③ の上に成り立つ
  ⑤ ルーティング イベント   … ② の上に成り立つ

 → ③④⑤ は、それぞれ ①② のどの層で導入されたかが決まっている
 → 【階層を理解すれば、機能の有無が予測できる】★

Windows Forms との根本的な違いを先に押さえておくと、
以降が読みやすい。

Windows Forms WPF
描画 GDI+(各コントロールが HWND を持つ) DirectX(HWND は最上位のみ)★
見た目の変更 継承して OnPaint を書く ControlTemplate で差し替える
配置 絶対座標(Location / Size) レイアウト システム(相対・可変)
データ連携 DataBindings データ バインディング(宣言的) ★
UI の定義 デザイナが生成するコード XAML(宣言的マークアップ)
解像度 ピクセル依存 ベクタ(DPI 非依存)

**「なるべく XAML の宣言的プロパティで書く」**という
WPF の理念(本文中で言及される)が、
依存関係プロパティを必要とした——という因果関係が本ページの主題である。

クラス階層

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

移行メモ(一覧は階層構造である): 原文は箇条書きで並列に見えるが、
実際には順に継承した階層である
(WPFのコントロール にも同じ図を示した)。

System.Object
 └ DispatcherObject      … スレッド親和性
     └ DependencyObject   … 依存関係プロパティ
         └ Visual         … 描画
             └ UIElement  … レイアウト・入力・ルーティング イベント
                 └ FrameworkElement … スタイル・データ バインディング
                     └ Control      … テンプレート
                         ├ ContentControl
                         └ ItemsControl

移行メモ(名前空間の誤り): 原文の
System.Threading.DispatcherObject は、正しくは
System.Windows.Threading.DispatcherObject である
(本文中の MSDN リンク先は正しい名前空間を指している)。

System.Object

https://learn.microsoft.com/ja-jp/dotnet/api/system.object

マネージ

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

アンマネージ

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

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

補足(milcore.dll の位置付け): WPF の性能特性を決めている
重要な要素なので補足する。

【WPF の 3 層構造】

  PresentationFramework.dll  … マネージ。スタイル、テンプレート、
                                 データ バインディング、コントロール
  PresentationCore.dll       … マネージ。Visual、UIElement、描画の基礎
  ─────────────────────────────────────
  milcore.dll                … 【アンマネージ】★
    ・MIL(Media Integration Layer)
    ・【コンポジション エンジン】
    ・DirectX を直接叩く
    ・OS(DWM: デスクトップ ウィンドウ マネージャー)とも共有される
【保持モード描画(Retained Mode)】★ これが WPF の本質

 【Windows Forms(即時モード)】
    再描画が必要 → WM_PAINT → 【毎回 OnPaint で描き直す】
    → 描画コードはアプリ側にある

 【WPF(保持モード)】
    アプリは「何を描くか」を【ビジュアル ツリーとして登録する】
    → milcore が【それを保持し、独自に再描画する】
    → アプリのコードは再描画のたびに走らない ★
    → アニメーションが滑らかに動く(UI スレッドを介さない)

移行メモ(原文の「アンマネージ コンポーネント(milcore.dll)はだけ。」):
文が途中で切れていると読める。
文脈から**「アンマネージ コンポーネントは milcore.dll だけである」**
という意味と解する。

System.Windows.Threading.DispatcherObject

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.threading.dispatcherobject

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

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

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

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

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

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

WPFのスレッド モデル

補足(Control.Invoke との対応): この節の内容は、
Control.Invoke、.BeginInvoke の WPF 版である。
仕組みは同じで、名前が違うだけである。

Windows Forms WPF
同期 Control.Invoke Dispatcher.Invoke
非同期 Control.BeginInvoke Dispatcher.BeginInvoke / InvokeAsync
判定 InvokeRequired Dispatcher.CheckAccess()
現在の推奨 async/await async/await ★

DispatcherPriority は WPF 固有の重要な機能である。

【優先度(高い順)】
   Send          … 【即座に】(同期。Invoke の既定)
   Normal        … 通常(BeginInvoke の既定)
   DataBind      … データ バインディングの処理
   Render        … レンダリング
   Loaded        … レイアウト完了後 ★
   Input         … 入力処理
   Background    … 【他に何もないとき】
   ContextIdle / ApplicationIdle / SystemIdle … アイドル時

 → 「レイアウトが終わってから実行したい」といった
   タイミング制御ができる
// レイアウト完了後にフォーカスを当てる(よく使う)
Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
    new Action(() => textBox1.Focus()));

DispatcherObject.CheckAccess() / VerifyAccess():

if (!textBlock1.CheckAccess())                 // 別スレッドからか?
    textBlock1.Dispatcher.Invoke(() => textBlock1.Text = "完了");
else
    textBlock1.Text = "完了";
【注意】 CheckAccess / VerifyAccess は
         【IntelliSense に出ない】(EditorBrowsable 属性で隠されている)
         が、public であり呼び出せる ★

「レンダリング スレッドは別」という点の帰結:

・アニメーションは【UI スレッドが忙しくても動く】ことがある
   → milcore 側で補間されるため
・逆に、UI スレッドを止めると
  【入力とレイアウトは止まる】(描画だけ生きている)
・「固まっているのにアニメーションが動く」という
  一見奇妙な症状はこれが原因

System.Windows.DependencyObject

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.dependencyobject

「WPFプロパティ システム」

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

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

「依存関係プロパティ」

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

  • 「依存関係プロパティ」は、プロパティの依存関係を把握し、双方向の接続と変更通知を実現する。

  • 具体的には、ソースとなるオブジェクトのプロパティの変更が通知された場合、
    (必須ではないが、INotifyPropertyChanged インターフェイスを使用すると、
    オブジェクトによる変更通知の発行が可能になる。)
    ターゲットとなるオブジェクトのプロパティ値を自動的に検証、計算するなど、
    高度なオブジェクト プロパティ間の接続を実現する。

  • 「依存関係プロパティ」の入力には、次のものがある。

    • 動的な「リソース」

      • 「スタイル」
      • 「テンプレート」
      • 「アニメーション」
    • 「データ バインディング」

      • 「ビュー・モデル オブジェクト」
    • 「添付プロパティ」
      親子要素のリレーションの値(子要素 → 親要素)を設定する。

    • 「プロパティ値の継承」
      親子要素のリレーションの値(親要素 → 子要素)を設定する。

補足(「依存関係」という名前の由来): 「なぜ 依存関係 プロパティなのか」
は分かりにくいので補っておく。

【1 つのプロパティの値が、複数の入力に「依存」する】★

   Button.Background の値は、どこから来るか?

     ① アニメーション          ← 最も強い
     ② ローカル値(Background="Red" と直接書いた)
     ③ テンプレート トリガー
     ④ 暗黙のスタイル / スタイル トリガー
     ⑤ テーマ スタイル
     ⑥ 継承された値
     ⑦ 既定値                  ← 最も弱い

 → 「今どの値が有効か」を【プロパティ システムが判定する】
 → だから値をフィールドに持てない= 辞書構造で管理する
【この優先順位が実務で効く場面】★
   「スタイルで色を指定したのに反映されない」
     → コード または XAML で【ローカル値】を設定している
     → ローカル値はスタイルより強い
     → ClearValue() でローカル値を消すと、スタイルが効くようになる

「一種の辞書構造で値を保持する」ことの利点:

・【既定値のままなら記憶領域を消費しない】
   → Control には数百のプロパティがあるが、
     すべてをフィールドで持つとメモリを食う
   → 明示的に設定されたものだけを保持する ★
・変更通知が組み込まれる(イベントを自分で実装しなくてよい)
・アニメーション・バインディングの対象になれる

定義方法の詳細は本ページ後半の
「WPF プロパティ システム」節、
および WPFのコントロール を参照。

System.Windows.Media.Visual

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.media.visual

Visualによる描画のサポート

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

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

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

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

レンダリングの概要

┬Visual
├UIElement
│└FrameworkElement
├ContainerVisual
│├DrawingVisual
│├HostVisual
└Viewport3DVisual

移行メモ(原文の階層図とクラス名): 図の罫線が崩れているが、
内容としては以下の階層である。

Visual
 ├ UIElement
 │   └ FrameworkElement
 ├ ContainerVisual
 │   ├ DrawingVisual
 │   └ HostVisual
 └ Viewport3DVisual

また、原文の Media3D(全角の 3)は
Media3D が正しい(本ページでは半角に修正した)。

補足(DrawingVisual の使いどころ): 「レイアウトやイベントの処理を
実現しないため軽量」という記述は重要である。

【大量描画での性能】★
   ・UIElement 派生(Rectangle 等)を 1 万個置く
      → レイアウト計算・イベント処理・依存関係プロパティの
        オーバーヘッドが 1 万個分かかる → 【非常に重い】
   ・DrawingVisual を使う
      → 描画だけ。桁違いに軽い
      → グラフ、地図、シミュレーションの描画に使われる

【現在の選択肢】
   ・DrawingVisual(軽量)
   ・WriteableBitmap(ピクセル単位で自前描画)
   ・【SkiaSharp / D3DImage】(さらに高速。GPU を直接使う)
項番 命令リスト 説明
1 ベクタ グラフィックス
(GeometryDrawing)
Geometry、Pen、Brush などのベクタ グラフィックス データ
2 イメージ
(ImageDrawing)
デジタル メディア ファイル内の各種イメージ
3 モニター
(VideoDrawing)
デジタル メディア ファイル内の各種ビデオ
4 グリフ
(GlyphRunDrawing)
テキストとフォントで表わされるグリフ
(特定のフォントにおける文字の物理表現)
  • System.Windows.Media.Geometry クラス
    https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.media.geometry

  • System.Windows.Media.Pen クラス
    https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.media.pen

  • System.Windows.Media.Brush クラス
    https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.media.brush

  • レンダリング データのレンダリング
    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

移行メモ(BitmapEffect は廃止されている): 適用順に挙げられている
BitmapEffect は .NET Framework 3.5 SP1 で非推奨となった。

【理由】 BitmapEffect は【常にソフトウェア レンダリング】だった
           → 極端に遅い
           → ハードウェア アクセラレーションを無効化してしまう

【後継】 UIElement.Effect(Effect クラス)★
           ・BlurEffect、DropShadowEffect
           ・【GPU(ピクセル シェーダー)で実行される】
           ・ShaderEffect を継承して独自のエフェクトも作れる
<!-- ✗ 非推奨 -->
<Button BitmapEffect="{StaticResource ...}" />

<!-- ○ 現在 -->
<Button>
  <Button.Effect><DropShadowEffect BlurRadius="8" /></Button.Effect>
</Button>

GuidelineSet についても補足しておく。

【WPF はベクタ描画= 座標が実数】
   → 1 ピクセルの線が【2 ピクセルにまたがってぼやける】★

【対策】
   ・GuidelineSet でピクセル境界に吸着させる
   ・または UseLayoutRounding="True"(後述の FrameworkElement)
   ・SnapsToDevicePixels="True"

 → 「線がぼやける」「文字がにじむ」という
   WPF の典型的な見た目の問題への対処

System.Windows.UIElement

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.uielement

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

目的

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

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

機能

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

プロパティの例

補足(レイアウトの 2 パス ── UIElement の中核): 「レイアウトを操作する」
の中身は、Measure → Arrange の 2 パスである。
カスタム パネルを作る際に必ず必要になる知識なので補っておく。

【① Measure パス(測定)】
   親が子に「使える最大サイズ」を渡し、
   子が「必要なサイズ(DesiredSize)」を返す
     MeasureOverride(Size availableSize) → Size

【② Arrange パス(配置)】
   親が子に「実際に使ってよい矩形」を渡し、
   子がその中に自分を配置する
     ArrangeOverride(Size finalSize) → Size

 → 【子のサイズは親が決めるのではなく、交渉で決まる】★
 → だから Width/Height を書かなくてもレイアウトが成立する
 → Windows Forms の絶対座標との根本的な違い
【性能上の注意】★
   ・レイアウトは【変更のたびに再計算される】
   ・深いツリー、大量の要素で重くなる
   ・InvalidateMeasure / InvalidateArrange を多用しない
   ・仮想化(VirtualizingStackPanel)を有効にする
      → ItemsControl で数千件を表示する場合は必須

Visibility の 3 値も実務で効く。

Visible     … 表示し、レイアウト領域も占める
Hidden      … 【非表示だが、レイアウト領域は占める】★
Collapsed   … 非表示で、レイアウト領域も占めない

 → Windows Forms の Visible(bool)にはない区別

RenderTransform と LayoutTransform の違い(後述の
FrameworkElement と対になる):

RenderTransform(UIElement)… 【レイアウト後】に変換する
   → 周囲の要素は動かない。重なることがある
   → 【速い】(アニメーション向き)★

LayoutTransform(FrameworkElement)… 【レイアウト前】に変換する
   → 変換後のサイズでレイアウトが再計算される
   → 周囲が押しのけられる。遅い

 → [XAMLの書き方(2)](MS_XAMLWriting2) で図付きで扱われている

System.Windows.FrameworkElement

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.frameworkelement

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

概要

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

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

機能

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

プロパティの例

移行メモ(LayoutTransform のリンク): 原文では
LayoutTransform の URL が UseLayoutRounding と同じになっていたため、
正しい URL に修正した。

補足(この層で「宣言的に書ける」ようになる): FrameworkElement が
導入する機能は、いずれも XAML から宣言的に扱うためのものである。

【UIElement までで揃うも】
   描画・入力・レイアウトの【仕組み】

【FrameworkElement が足すもの】★
   ・Style          … 見た目をまとめて指定する
   ・DataContext    … データ バインディングの起点
   ・Resources      … リソース辞書
   ・Name / FindName … 名前で引ける
   ・Loaded / Unloaded イベント
   ・Margin / HorizontalAlignment / VerticalAlignment
   ・Width / Height / MinWidth / MaxWidth …

 → 「XAML で書く WPF」の実体は、
   ほぼこの層から上である ★

UseLayoutRounding は実務で重要である。

【問題】 ベクタ描画のため、要素の境界が非整数ピクセルに来る
           → 線がぼやける、1px ずれる
【対策】 <Window UseLayoutRounding="True">
           → レイアウト結果を整数ピクセルに丸める
           → .NET 4 以降、既定で True の場面が増えた

System.Windows.Controls.Control

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.controls.control

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

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

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

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

補足(テンプレートが WPF の最大の特徴): 「Control の最も重要な機能は
テンプレートである」という原文の評価は正しい。
これが Windows Forms との決定的な差である。

【Windows Forms でボタンの見た目を変える】
   ・Button を継承する
   ・OnPaint をオーバーライドして自前で描く
   ・→ 【描画コードを書く】。状態(押下・ホバー)も自分で管理

【WPF でボタンの見た目を変える】★
   ・ControlTemplate を差し替える
   ・→ 【マークアップを書くだけ】
   ・→ Click イベント、コマンド、フォーカス、アクセシビリティは
     【そのまま動く】(Button の振る舞いは変わらない)
<Button Content="OK">
  <Button.Template>
    <ControlTemplate TargetType="Button">
      <Border x:Name="bd" CornerRadius="4" Background="{TemplateBinding Background}">
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
      </Border>
      <ControlTemplate.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
          <Setter TargetName="bd" Property="Background" Value="LightBlue" />
        </Trigger>
      </ControlTemplate.Triggers>
    </ControlTemplate>
  </Button.Template>
</Button>
【「振る舞い」と「見た目」の分離】★
   Button   … 「クリックできる」という【振る舞い】
   Template … 「どう見えるか」という【見た目】

 → 見た目を丸ごと変えても、振る舞いは壊れない
 → デザイナと開発者の分業が可能になる
   (これが Expression Blend の存在意義だった)

TemplateBinding と ContentPresenter はテンプレートの必須要素である。

TemplateBinding   … テンプレートの外から設定された値を引き込む
ContentPresenter  … Content の中身をここに描画する
ItemsPresenter    … ItemsControl の項目をここに並べる

 → [XAMLの書き方(1)](MS_XAMLWriting1) で図付きで詳説されている

System.Windows.Controls.ContentControl

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.controls.contentcontrol

  • 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 コレクション プロパティの表示に特化した「テンプレート」を持つ。

補足(Content に「何でも入る」ことの意味): 「文字列に限らず、
様々な子要素を 1 つだけ設定可能」——
これが WPF の柔軟さの源である。

<!-- ボタンの中に画像とテキストを入れられる -->
<Button>
  <StackPanel Orientation="Horizontal">
    <Image Source="save.png" Width="16" />
    <TextBlock Text="保存" Margin="4,0,0,0" />
  </StackPanel>
</Button>

<!-- ボタンの中にボタンすら入る(意味はないが、可能) -->
<Button><Button Content="入れ子" /></Button>
【Content が object 型であることの帰結】★
   ・文字列 → そのまま TextBlock として描画
   ・UIElement → その要素をそのまま描画
   ・それ以外の型 → 【DataTemplate を探して描画】
       → 見つからなければ ToString() の結果を表示

 → これが [WPFのコントロール](MS_WPFControls) で述べた
   「ViewModel を差し替えれば View が切り替わる」仕組みの土台

Items と ItemsSource の使い分け(実務で頻出):

Items       … 直接追加する(XAML に書く場合)
ItemsSource … 【コレクションをバインドする】★

【注意】 両方を同時に使うと例外になる
   「Items コレクションは空である必要があります。
     ItemsSource を使用する前に」

ItemsControl の 4 つのテンプレートは
XAMLの書き方(1) の主題である。

ItemTemplate         … 1 件をどう描くか
ItemsPanelTemplate   … 項目をどう並べるか(縦・横・グリッド)
ItemContainerStyle   … 各項目の入れ物(ListBoxItem)の見た目
Template             … コントロール全体の枠

要素ツリー

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 フレームワークの構造を理解する上で役立つ。

  • 「要素ツリー」には、「論理ツリー」・「ビジュアル ツリー」の2つの解釈方法がある。

論理ツリー

「論理ツリー」は、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 オブジェクト(描画内容)が
含まれる。

レンダリング順序

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

ルート ビジュアル

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

補足(2 つのツリーの違いが実務で効く場面): 「論理ツリー」と
「ビジュアル ツリー」の区別は、トラブル時に必ず必要になる。

【同じ Button の 2 つの見え方】

 【論理ツリー】          【ビジュアル ツリー】
   Button                  Button
     └ "OK"(文字列)        └ ButtonChrome(テンプレートの中身)★
                                 └ ContentPresenter
                                     └ TextBlock
                                         └ "OK"
論理ツリー ビジュアル ツリー
含むもの XAML に書いた要素 テンプレートの中身も含む全描画要素
要素数 少ない 非常に多い
関わる機能 プロパティ値の継承、ルーティング イベント、リソース検索 描画、ヒット テスト、RelativeSource FindAncestor
ヘルパー LogicalTreeHelper VisualTreeHelper
【実務での場面】★
 ・「ListBox の中の TextBox を探したい」
     → ItemTemplate で生成された要素は【論理ツリーに現れない】
     → VisualTreeHelper で辿る
 ・「Style が効かない」
     → リソース検索は【論理ツリーを遡る】
     → テンプレート内の要素からは辿れないことがある
 ・「イベントが飛んでこない」
     → ルーティングは【論理ツリー寄り】の経路をとる
       (厳密にはビジュアル ツリーだが、論理親も考慮される)

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

実際に、「要素ツリー」の内容を確認したい場合は、
以下のヘルパ クラスの 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 ライブ ビジュアル ツリー Visual Studio に標準搭載 ★(デバッグ中に見られる)
XAML ホット リロード 実行中に XAML を書き換えて即反映
Snoop 老舗の OSS。実行中のアプリに外部からアタッチできる
WPF Inspector 同種
【ライブ ビジュアル ツリー】
   デバッグ実行中に
     [デバッグ] → [ウィンドウ] → [ライブ ビジュアル ツリー]
   ・要素を選択すると【画面上でハイライト】される
   ・[ライブ プロパティ エクスプローラー] で
     【そのプロパティ値がどこから来たか】が分かる ★
      → 依存関係プロパティの優先順位の確認に最適

VisualTreeHelper.GetChildrenCount の注意:

・【テンプレートが適用される前は 0 を返す】★
   → コンストラクタや Loaded 前に呼んでも子が見つからない
   → Loaded イベント以降、または
     ApplyTemplate() を呼んでから使う
・仮想化されている ItemsControl では、
  【画面に出ていない項目は存在しない】

WPFプロパティ システム

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

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

について説明する。

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

依存関係プロパティ

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

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

用途

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

  • 動的な「リソース」参照

    • 「スタイル」
    • 「アニメーション」
  • 「データ バインディング」

  • 「添付プロパティ」

  • 「プロパティ値の継承」

定義

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

  • 定義方法
    以下、「依存関係プロパティ」の定義方法について説明する。

    • public static readonly(VB では、Public Shared ReadOnly)な
      DependencyProperty 型の変数として宣言する。
    • 「依存関係プロパティ」名の末尾に「Property」を付与する(WPF の名称付与基準)
    • 変数宣言時か、静的コンストラクタを初期化する。
      DependencyProperty.Register メソッドを使用し
      「依存関係プロパティ」として「WPF プロパティ システム」に登録する。
      https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.dependencyproperty.register
    • 「依存関係プロパティ」は、プロパティの設定・取得方法が特殊なので、
      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); }
  } 
}

補足(CLR プロパティ ラッパーの重大な注意点): 実装例は正しいが、
ここに WPF 最大の落とし穴がある。

【ラッパーには GetValue / SetValue 以外を書いてはならない】★

   public string Caption {
     get => (string)GetValue(CaptionProperty);
     set {
       SetValue(CaptionProperty, value);
       DoSomething();          // ✗ ここに処理を書いてはいけない
     }
   }

【理由】
   ・XAML から設定される場合、
     【ラッパーを経由せず SetValue が直接呼ばれる】
   ・バインディング、スタイル、アニメーションも同様
   ・→ DoSomething() が【呼ばれないことがある】

【正しい書き方】
   処理は【PropertyChangedCallback に書く】★

現在の簡略化(.NET Community Toolkit):

// 手書きは冗長なので、スニペット(propdp)か
// ソース ジェネレーターを使うのが現在の実務
[DependencyProperty]                       // ← Toolkit 等が生成
public partial string Caption { get; set; }

機能

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

  • プロパティの既定値の設定
  • ソース プロパティの変更監視コールバックの設定
  • プロパティ値の強制コールバックの設定
  • プロパティ値の有効値検証コールバックの設定

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

  • プロパティの既定値の設定
    プロパティの既定値を設定する。
new PropertyMetadata("DefaultValue")
  • Microsoft Learn > .NET Framework クラス ライブラリ > System.Windows.PropertyMetadata クラス
    https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.propertymetadata

  • ソース プロパティの変更監視コールバックの設定
    「依存関係プロパティ」が、有効なプロパティ値に変更された時に呼び出される
    コールバックを設定する。
    例えば、値を「最小値 ~ 最大値の間に強制する」などの用途で使用するコールバック。

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; 
}
  • Microsoft Learn > .NET Framework クラス ライブラリ > System.Windows.PropertyChangedCallback デリゲート
    https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.propertychangedcallback

  • プロパティ値の強制コールバックの設定
    「依存関係プロパティ」の値が再評価されたり、強制が明示的に要求されたりした場合に
    呼び出されるコールバックを設定する。

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

移行メモ(ValidateValueCallback は PropertyMetadata の引数ではない): 最後の
コード例は、PropertyMetadata のコンストラクタに
4 つ目の引数として ValidateValueCallback を渡す
形になっているが、
PropertyMetadata にそのようなコンストラクタは存在しない。

// ○ 正しい形:ValidateValueCallback は Register の【第 5 引数】★
DependencyProperty.Register(
    "Caption",
    typeof(string),
    typeof(ConcreteDependencyObject),
    new PropertyMetadata("DefaultValue",
        new PropertyChangedCallback(OnPropertyChangedCallback),
        new CoerceValueCallback(OnCoerceValueCallBack)),
    new ValidateValueCallback(OnValidateValueCallback));   // ← ここ

3 つのコールバックの呼ばれる順序と違いを整理しておく。

【値が設定されるときの流れ】★

  SetValue(dp, value)
    ↓
  ① ValidateValueCallback(object value) → bool
       ・【インスタンスを受け取らない】(static な検証)
       ・false を返すと【ArgumentException】
       ・「そもそもあり得ない値」を弾く(負の長さ等)
    ↓
  ② CoerceValueCallback(DependencyObject d, object value) → object
       ・【インスタンスを見て値を補正する】
       ・例: Value を Minimum ~ Maximum に丸める
       ・他プロパティとの関係で決まる制約に使う
    ↓
  ③ PropertyChangedCallback(d, e)
       ・【確定した後】に呼ばれる
       ・副作用(再描画、他プロパティの更新)を書く場所 ★
【使い分け】
   Validate … 「この値は不正だ」→ 例外にする
   Coerce   … 「この値はこう直す」→ 黙って補正する
   Changed  … 「変わったので、これをする」→ 副作用

移行メモ: 原文のサンプル中の「DengerousValue」は
DangerousValue の綴り誤りと読めるが、
サンプル内で一貫しているため、原文のまま残した。

添付プロパティ

「添付プロパティ」は、「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);

移行メモ(サンプル中の this.): アクセッサ メソッドは
static であるため、
this.プロパティ変数 とは書けない(static メンバから this は参照できない)。

// ○ 正しい形
public static void SetIsHighlighted(UIElement element, bool value)
    => element.SetValue(IsHighlightedProperty, value);      // ← this なし ★

public static bool GetIsHighlighted(UIElement element)
    => (bool)element.GetValue(IsHighlightedProperty);

補足(添付プロパティのもう 1 つの用途 ── ビヘイビア): 原文が挙げる
「レイアウト制御」以外に、現在よく使われる用途がある。

【添付ビヘイビア(Attached Behavior)】★
   添付プロパティの PropertyChangedCallback を使って、
   【既存のコントロールに振る舞いを後付けする】

   例: 「TextBox にフォーカスが入ったら全選択する」
        → TextBox を継承せずに実現できる
public static class TextBoxBehavior
{
    public static readonly DependencyProperty SelectAllOnFocusProperty =
        DependencyProperty.RegisterAttached(
            "SelectAllOnFocus", typeof(bool), typeof(TextBoxBehavior),
            new PropertyMetadata(false, OnChanged));

    public static void SetSelectAllOnFocus(DependencyObject d, bool v)
        => d.SetValue(SelectAllOnFocusProperty, v);
    public static bool GetSelectAllOnFocus(DependencyObject d)
        => (bool)d.GetValue(SelectAllOnFocusProperty);

    static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox tb && (bool)e.NewValue)
            tb.GotFocus += (s, _) => ((TextBox)s!).SelectAll();
    }
}
<TextBox local:TextBoxBehavior.SelectAllOnFocus="True" />
【なぜ重要か】
   ・MVVM では【コードビハインドを書きたくない】
   ・添付ビヘイビアなら、XAML だけで振る舞いを付けられる ★
   ・継承しないので、どのコントロールにも適用できる

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

  • 「WPF プロパティ システム」では、包含継承
    (HTML コードと同様に、親要素に書いた属性値が子要素に継承される。
    HTML では、例えば、body 要素に対して style 属性や font 属性でフォント・サイズを
    指定すると、body 要素内のすべての要素のフォント・サイズが変化する。
    ※ 注:オブジェクト指向プログラミングにおける継承
    (派生クラスがその基本クラスからメンバ定義を継承する)とは異なる概念。)を
    サポートしている。

  • これを実現するのが「プロパティ値の継承」の機能である。

    • 「プロパティ値の継承」がされる一部のプロパティは、
      「要素ツリー」の最も近い親要素から特定のプロパティの値を継承し、
      親要素のプロパティ値が変更された場合、自動的に子要素に反映する。
    • この動作は、子要素から親要素のプロパティを設定する「添付プロパティ」と
      逆の働きであるため、
      「プロパティ値の継承」の仕組みも、「添付プロパティ」と同様の方式で
      実装されていることが分かる
      (そのため、「添付プロパティ」と同様、プロパティの登録に
      DependencyProperty.RegisterAttached メソッドを使用する)。
  • Microsoft Learn > WPF の基礎 > プロパティ > プロパティ値の継承
    https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/properties/property-value-inheritance

用途

  • 包含継承の機能は、親要素、例えば、ルート要素(Window or Page)に
    一度だけ定義することで、画面全体のポリシーを統一するようなプロパティに適している。

  • 「プロパティ値の継承」がされるプロパティの一例として、以下のものがある。

    • Control.FontSize プロパティ
    • FrameworkElement.FlowDirection プロパティ.etc

定義

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

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

補足(設定は FrameworkPropertyMetadataOptions.Inherits): 「オプション設定で」
の具体形を示しておく。

public static readonly DependencyProperty ThemeProperty =
    DependencyProperty.RegisterAttached(
        "Theme", typeof(string), typeof(MyClass),
        new FrameworkPropertyMetadata(
            "Light",
            FrameworkPropertyMetadataOptions.Inherits));   // ← これ ★
【FrameworkPropertyMetadataOptions の主なもの】
   Inherits              … 【値が子要素に継承される】★
   AffectsMeasure        … 変更時に Measure をやり直す
   AffectsArrange        … 変更時に Arrange をやり直す
   AffectsRender         … 変更時に再描画する
   BindsTwoWayByDefault  … 既定で TwoWay バインディング
   NotDataBindable       … バインディング不可

 → 【カスタム コントロールでは AffectsMeasure / AffectsRender を
   正しく指定しないと、値を変えても画面が変わらない】★

FlowDirection が継承される意味は
国際化対応項目 と関わる。

・アラビア語・ヘブライ語は右から左(RTL)
・Window の FlowDirection を RightToLeft にすると
  【配下の全要素が反転する】★
・国際化対応で「レイアウトを 2 通り作る」必要がなくなる

データ バインディング

WPF では、

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

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

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

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

  • 「データ バインディング」を利用すれば、

    • ビューとモデルの接点を、任意のプロパティ接続だけに限定できる。
    • このため、ビューの内部にビューとモデルを接続するロジックを持つ必要が無くなり、
      ビュー内のコード量を削減すると同時に疎結合を実現できる。
  • 「データ バインディング」により、
    2つのオブジェクトを結合するが、通常、

    • ビュー側の XAML で生成された CLR オブジェクトを「バインディング ターゲット」(=結合先)
    • モデル側のコードビハインドで生成された CLR オブジェクトを「バインディング ソース」(=結合元)

    と呼ぶ。

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

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

  • なお、

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

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

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

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

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

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

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

DependencyObjectオブジェクト

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

INotifyPropertyChanged インターフェイス

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

Bindingオブジェクト

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

補足(コレクションには INotifyCollectionChanged が要る): 3 要素の
説明は正確だが、コレクションを扱う場合にもう 1 つ必要である。

【要素の追加・削除を画面に反映するには】
   INotifyPropertyChanged … 【プロパティの変更】を通知する
   INotifyCollectionChanged … 【コレクションの変更】を通知する ★

 → List<T> をバインドしても、Add/Remove が画面に反映されない
 → 【ObservableCollection<T>】を使う
// ✗ 追加しても画面が変わらない
public List<Item> Items { get; } = new();

// ○ 追加・削除が反映される
public ObservableCollection<Item> Items { get; } = new();
【注意点】★
 ・ObservableCollection は【UI スレッドからのみ変更する】
     → 別スレッドから Add すると例外
     → [Control.Invoke、.BeginInvoke](MS_ControlInvoke) と同じ問題
     → .NET 4.5 以降は BindingOperations.EnableCollectionSynchronization で緩和可
 ・要素【自身】のプロパティ変更は、
   要素の型が INotifyPropertyChanged を実装している必要がある
 ・大量件数の一括追加は【1 件ずつ通知が飛んで重い】
     → 一旦 ItemsSource を外す、または別コレクションを作って差し替える

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

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

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

補足(Mode の既定と、UpdateSourceTrigger): 表に加えて、
実務で必ず必要になる 2 点を補う。

【Mode の既定(Default)が何になるか】★
   ・依存関係プロパティごとに決まっている
   ・TextBox.Text、CheckBox.IsChecked 等
       → 【TwoWay】(BindsTwoWayByDefault が付いている)
   ・TextBlock.Text、Label.Content 等
       → 【OneWay】

 → 「明示しなくても双方向になる」ものと
   「明示しないと片方向のまま」のものがある
【UpdateSourceTrigger ── いつソースへ書き戻すか】★
   PropertyChanged … 【入力するたび】に即座に反映
   LostFocus       … フォーカスが外れたとき(TextBox.Text の既定)
   Explicit        … UpdateSource() を呼んだとき

 → 「入力しても ViewModel の値が変わらない」の典型的な原因は
   LostFocus のままになっていること
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />

Source 以外の指定方法も重要である。

Source          … 明示的にオブジェクトを指定
ElementName     … 【他の要素】を指定({Binding ElementName=slider1, Path=Value})
RelativeSource  … 【相対的に探す】
   ・Self                → 自分自身
   ・TemplatedParent     → テンプレートの適用先
   ・FindAncestor        → 【ビジュアル ツリーを遡って型で探す】★
   ・PreviousData        → 前の項目
(指定しない)  … DataContext を使う(後述)
<!-- ItemTemplate の中から、外側の ViewModel のコマンドを呼ぶ(頻出) -->
<Button Command="{Binding DataContext.DeleteCommand,
         RelativeSource={RelativeSource AncestorType=ListBox}}"
        CommandParameter="{Binding}" />

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

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

DataContextの使用

  • Microsoft Learn > .NET Framework クラス ライブラリ > FrameworkElement.DataContext プロパティ
    https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.frameworkelement.datacontext

  • 「バインディング ソース」と「バインディング ターゲット」の数が増え、
    それぞれのオブジェクト間のマッピングが面倒な場合、
    Binding オブジェクトの規定のターゲットである DataContext プロパティに
    「バインディング ソース」を設定することで、
    Binding オブジェクトへの「バインディング ソース」の指定が不要になる。

  • 「データ バインディング」では、「バインディング ターゲット」と
    「バインディング ソース」が、リフレクションによるプロパティ名にて結び付けられている。
    このため、FrameworkElement.DataContext プロパティに、任意のカスタム型を渡すことができる。

  • なお、DataContext プロパティは、「プロパティ値の継承」に対応している。
    このため、指定した「バインディング ソース」は、親要素から子要素に継承されるので、
    親要素(もしくは自身)の DataContext プロパティに
    「バインディング ソース」を1回指定すればよい。

補足(DataContext が MVVM の土台): 「プロパティ値の継承に対応している」
という点が決定的である。

【これにより】
   Window の DataContext に ViewModel を 1 回設定すれば、
   配下のすべての要素が【同じ ViewModel を見る】★

   <Window DataContext="{Binding MainViewModel, Source=...}">
     <Grid>
       <TextBox Text="{Binding Name}" />     ← ViewModel.Name
       <Button Command="{Binding SaveCommand}" />
     </Grid>
   </Window>

 → これが MVVM が成立する理由
 → View は ViewModel の【型を知らない】(リフレクションで解決)
【DataContext がずれる典型例】★
   ・ItemsControl の中 → DataContext は【その項目】になる
   ・ContextMenu / Popup / ToolTip
       → 【ビジュアル ツリーが別】なので DataContext が継承されない
       → PlacementTarget 経由で辿る必要がある

 → 「バインディングが効かない」ときは
   まず【今の DataContext が何か】を疑う
【バインディングのデバッグ】★
   ・失敗しても【例外にならない】(出力ウィンドウに警告が出るだけ)
   ・PresentationTraceSources.TraceLevel を使うと詳細が見える
       <TextBlock Text="{Binding Name,
           diag:PresentationTraceSources.TraceLevel=High}" />
   ・または Visual Studio の [出力] ウィンドウで
     "System.Windows.Data Error" を探す

型変換(値コンバータ)

  • 「データ バインディング」では、

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

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

  • 型変換の動作のカスタマイズの必要性

    • 暗黙の型変換が失敗する場合

    • 入力数値から背景色を変更するような処理を実装したい場合。

  • 型変換の動作のカスタマイズの実装

    • 暗黙の型変換(または値変換)が失敗する場合、null が返される。
      この対応として IValueConverter インターフェイスを実装した
      「値コンバータ」を実装することで明示的な型変換(または値変換)が可能になる。

    • なお、「値コンバータ」で型変換できない場合も同様に、null を返すように実装する。
      null が返された場合、通常は何も表示されないが、これをカスタマイズする場合は、
      Binding.TargetNullValue プロパティを実装し、
      ソース値が null のときに返される値を指定する。

補足(値コンバータの実装と注意点):

public class BoolToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
                          CultureInfo culture)
        => (value is bool b && b) ? Visibility.Visible : Visibility.Collapsed;

    public object ConvertBack(object value, Type targetType, object parameter,
                              CultureInfo culture)
        => value is Visibility v && v == Visibility.Visible;
}
【実装上の注意】★
 ① 【CultureInfo を無視しない】
      → 数値・日付の表示は文化圏で変わる
      → [カルチャ](MS_Culture) / [国際化対応項目](MS_InternationalizationItems)
 ② 【例外を投げない】
      → バインディングは例外を握りつぶすため、原因が分からなくなる
      → 変換できないときは DependencyProperty.UnsetValue を返す
 ③ 【ConvertBack を実装しないなら】
      → NotImplementedException ではなく Binding.DoNothing を返す
      → OneWay で使う前提なら、それを明示する
 ④ 【ステートレスにする】
      → 1 つのインスタンスが複数箇所で共有される
【値コンバータを使わずに済む場合】
 ・BooleanToVisibilityConverter は【標準で用意されている】
 ・{Binding X, StringFormat='{}{0:N0} 円'} で書式指定できる
 ・DataTrigger でも同等のことができる
      → [XAMLの書き方(1)](MS_XAMLWriting1) 参照
 → 【コンバータを増やしすぎない】方が保守しやすい ★

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

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

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

  • 「バインディング ソース」に DataContext を使用して、
    「バインディング ソース」と「バインディング ターゲット」を
    OneWay モードで結び付け、「データ バインディング」を行う。

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

  • (1)に以下を加えて、入力値の自動計算アプリケーションを作成できる。

    • (1)と同じ、OneWay モードで結び付け、「データ バインディング」を行う。
    • OneWayToSource モードで結び付け、「データ バインディング」を行う。
    • 変更通知を追加し、計算された値を「バインディング ターゲット」に反映する。
  • 変更通知を追加方法

    • OneWay の「バインディング ソース」には、INotifyPropertyChanged インターフェイスを
      実装して、プロパティ値の変更通知処理を実装する必要がある。
    • なお、UI 要素の表示に関するプロパティは、基本的に「依存関係プロパティ」として
      実装されており、既定で変更通知をサポートしている。このため、既定で双方向接続が可能である。
  • 連載 WPF/Silverlight UIフレームワーク入門:第2回 データの表示と入力に必要な知識 (3/5) - @IT
    https://atmarkit.itmedia.co.jp/fdotnet/vblab/uiframework_02/uiframework_02_03.html

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

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

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

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

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

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

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

移行メモ(3 項目の記述が「TwoWay モードに対応した」で始まっている): それぞれ
OneWay / OneWayToSource / TwoWay に対応する説明のはずだが、
3 つとも「TwoWay モードに対応した」で始まっている(コピー時の誤りと読める)。
内容としては以下が正しい。

OneWay を使う値コンバータ         … Convert のみ実装すればよい
OneWayToSource を使う値コンバータ … ConvertBack のみ実装すればよい
TwoWay を使う値コンバータ         … 【両方】実装する必要がある

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

  • ItemsSource へのデータ バインディング
    ItemsControl から派生した要素の ItemsSource 属性にコレクションを
    「データ バインディング」する場合、
    対象オブジェクトは反復処理をサポートしている必要がある。

  • インデクサによるデータ バインディング
    DataGrid の列に DataTable の指定の列をデータバインディングする場合、
    XAML の「バインディングのマークアップ拡張」にて、Binding プロパティである
    Path 属性に角括弧を指定することで、インデクサを使用して接続可能である。

補足(インデクサ バインディングの書き方): 実務で使う機会が多いので
具体例を示しておく(ASP.NET MVCでDataTableを使用する。 の
WPF 版に当たる)。

<!-- DataTable の列名でバインドする -->
<DataGrid ItemsSource="{Binding MyDataTable}" AutoGenerateColumns="False">
  <DataGrid.Columns>
    <DataGridTextColumn Header="名前"  Binding="{Binding [Name]}" />
    <DataGridTextColumn Header="金額"  Binding="{Binding [Price], StringFormat={}{0:N0}}" />
  </DataGrid.Columns>
</DataGrid>
【DataTable が扱える理由】
   DataView が ITypedList / IBindingList を実装しており、
   WPF がそれを解釈する
     → List<T> と違い、【列の追加・削除も反映される】
     → ただし型情報が弱く、コンパイル時に検証されない

【現在の推奨】
   ・可能なら【POCO のコレクション】にする
   ・列が動的に決まる画面なら DataTable も選択肢
     ([ASP.NET MVCでDataTableを使用する。](MS_ASPNETMVCDataTable) と同じ判断)

ルーティング イベント

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

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

ルーティング方法

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

項番 区分 機能
1 トンネル ツリーを下方向へ辿る。
最初に、「ビジュアル ツリー」のルートのイベント ハンドラが呼び出され、次に、イベントを発生させた要素に到達するまで、「経路」沿いの「トンネル ルーティング イベント」のイベント ハンドラを呼び出す。
なお、「トンネル ルーティング イベント」のイベント名は、先頭に「Preview」を付与するルールとなっている。
2 バブル ツリーを上方向へ辿る。
最初に、イベントを発生させた要素のイベント ハンドラが呼び出され、次に、「ビジュアル ツリー」のルートに到達するまで、「経路」沿いの「バブル ルーティング イベント」のイベント ハンドラを呼び出す。
3 直接 イベントを発生させた要素のみに、イベント ハンドラを呼び出す機会が与えられる。 これは、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);

補足(AddHandler にしかできないこと): 2 つは等価に見えるが、
AddHandler には第 3 引数がある点が重要である。

// handledEventsToo: true にすると、
// 【e.Handled = true にされた後でも】ハンドラが呼ばれる ★
this.button1.AddHandler(Button.ClickEvent,
    new RoutedEventHandler(button1_Click),
    handledEventsToo: true);
【使う場面】
   ・TextBox の PreviewMouseDown が内部で Handled にされていて、
     自前のハンドラが呼ばれない
   ・ListBoxItem の選択処理に割り込みたい

 → 「イベントが飛んでこない」ときの最終手段 ★

サンプル

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

補足(このサンプルが示す実務上の教訓): 出力順が
**「上から下(Preview)→ 下から上(本イベント)」**になることを
実測で示している点が有用である。

【設計上の含意】★

 ① 【Preview で握りつぶせる】
      親が Preview で e.Handled = true にすると、
      子は【イベントを受け取れない】
      → 入力の一括禁止、ショートカットの横取りに使える

 ② 【子の処理を親で拾える】
      Button のテンプレート内の Border をクリックしても、
      Button.Click が発生する
      → これがテンプレートを自由に組める理由

 ③ 【共通処理を 1 箇所に書ける】
      Window で TextBox.GotFocus をまとめて拾い、
      全 TextBox を全選択する、等
【e.Handled の落とし穴】★
   ・Handled にされたイベントは、以降のハンドラに【届かない】
   ・WPF の組み込みコントロールは【内部で Handled にしている】
      → 例: TextBox は PreviewKeyDown の一部を消費する
      → 「なぜかキー イベントが来ない」の原因
   ・対策: AddHandler(..., handledEventsToo: true)(前述)

移行メモ: サンプルのイベント ハンドラの引数は
MouseEventArgs となっているが、
MouseDown / PreviewMouseDown の実際のハンドラ型は
**MouseButtonEventHandler(MouseButtonEventArgs)**である。
MouseButtonEventArgs は MouseEventArgs を継承しているため
概念的には誤りではないが、この通りに書くとコンパイルは通らない。

// ○ 正しいシグネチャ
private void rect1_MouseDown(object sender, MouseButtonEventArgs e) { }

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

NetDevInfraWiki

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

(未着手)

開発基盤部会 Wiki

移行管理: DONE / TODO

Clone this wiki locally