MS_WPFArchitecture - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

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 を直接使う)
項番 命令リスト 説明
ベクタ グラフィックス
(GeometryDrawing)
Geometry、Pen、Brush などのベクタ グラフィックス データ
イメージ
(ImageDrawing)
デジタル メディア ファイル内の各種イメージ
モニター
(VideoDrawing)
デジタル メディア ファイル内の各種ビデオ
グリフ
(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 の中核): 「レイアウトを操作する」
の中身は、MeasureArrange の 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)にはない区別

RenderTransformLayoutTransform の違い(後述の
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 の存在意義だった)

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

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 が切り替わる」仕組みの土台

ItemsItemsSource の使い分け(実務で頻出):

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

移行メモ(ValidateValueCallbackPropertyMetadata の引数ではない): 最後の
コード例は、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 クラスが持つ主要なプロパティ

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

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

補足(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 の実際のハンドラ型は
**MouseButtonEventHandlerMouseButtonEventArgs)**である。
MouseButtonEventArgsMouseEventArgs を継承しているため
概念的には誤りではないが、この通りに書くとコンパイルは通らない

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

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

⚠️ **GitHub.com Fallback** ⚠️