オブジェクト ツリーに存在しないオブジェクト要素の初期化
Windows Presentation Foundation (WPF) における初期化処理を委託されるプロセスの中には、その要素が論理ツリーまたはビジュアル ツリーのいずれかに接続されていることを前提とするものがあります。 ここでは、どちらのツリーにも接続されていない要素を初期化するために必要となる可能性のある手順について説明します。
このトピックは、次のセクションで構成されています。
- 要素と論理ツリー
- 関連トピック
要素と論理ツリー
コード内で Windows Presentation Foundation (WPF) クラスのインスタンスを作成するときは、Windows Presentation Foundation (WPF) クラスのオブジェクト初期化処理の一部が、クラス コンストラクターの呼び出し時に実行されるコードから意図的に除外されていることに注意してください。 特に、コントロール クラスの場合は、コントロールの視覚的表現の大部分が、コンストラクターではなく コントロールのテンプレートによって定義されます。 このテンプレートのソースにはさまざまなものがありますが、ほとんどの場合はテーマ スタイルから取得されます。 テンプレートは、実質的には遅延バインディングです。必要なテンプレートがコントロールに結び付けられるのは、そのコントロールがレイアウト可能になったときです。 コントロールがレイアウト可能になるのは、ルートでレンダリング サーフェイスに接続される論理ツリーに結び付けられたときです。 このルート レベル要素によって、論理ツリーで定義されているすべての子要素のレンダリングが行われます。
このプロセスには、ビジュアル ツリーも関与します。 テンプレートによってビジュアル ツリーに組み込まれる要素も、接続されなければ完全にはインスタンス化されません。
この動作の影響で、要素の視覚的特性がすべて揃っていることを前提とする操作には追加の手順が必要になります。 既に構築されているがまだツリーに関連付けられていないクラスの視覚的特性を取得する場合などがそうです。 たとえば、RenderTargetBitmap に対して Render を呼び出すときに、渡そうとしているビジュアルがツリーに接続されていない要素である場合は、追加の初期化手順が実行されるまでその要素の視覚的特性は揃いません。
BeginInit と EndInit を使用して要素を初期化する
WPF のさまざまなクラスに、ISupportInitialize インターフェイスが実装されています。 このインターフェイスの BeginInit メソッドと EndInit メソッドを使用して、コード内の初期化手順 (たとえばレンダリングに影響を与えるプロパティ値の設定) の部分を示します。 シーケンス内で EndInit が呼び出された後に、レイアウト システムは要素を処理し、暗黙的なスタイルの検索を開始します。
プロパティ設定対象の要素が FrameworkElement または FrameworkContentElement 派生クラスである場合は、ISupportInitialize にキャストする代わりに、クラス バージョンの BeginInit と EndInit を呼び出します。
サンプル コード
次のコンソール アプリケーションのサンプル コードでは、Loose XAML ファイルのレンダリング APIs および XamlReader.Load(Stream) を使用して、BeginInit と EndInit の正しい配置方法を示します。これらのメソッドは、レンダリングに影響を与えるプロパティを調整する他の API 呼び出しを囲むように配置します。
この例では、Main 関数のみを示します。 関数 Rasterize および Save (この例には示していません) は、イメージ処理および入出力を扱うユーティリティ関数です。
<STAThread>
Shared Sub Main(ByVal args() As String)
Dim e As UIElement
Dim _file As String = Directory.GetCurrentDirectory() & "\starting.xaml"
Using stream As Stream = File.Open(_file, FileMode.Open)
' loading files from current directory, project settings take care of copying the file
Dim pc As New ParserContext()
pc.BaseUri = New Uri(_file, UriKind.Absolute)
e = CType(XamlReader.Load(stream, pc), UIElement)
End Using
Dim paperSize As New Size(8.5 * 96, 11 * 96)
e.Measure(paperSize)
e.Arrange(New Rect(paperSize))
e.UpdateLayout()
'
' * Render effect at normal dpi, indicator is the original RED rectangle
'
Dim image1 As RenderTargetBitmap = Rasterize(e, paperSize.Width, paperSize.Height, 96, 96)
Save(image1, "render1.png")
Dim b As New Button()
b.BeginInit()
b.Background = Brushes.Blue
b.Height = 200
b.Width = b.Height
b.EndInit()
b.Measure(paperSize)
b.Arrange(New Rect(paperSize))
b.UpdateLayout()
' now render the altered version, with the element built up and initialized
Dim image2 As RenderTargetBitmap = Rasterize(b, paperSize.Width, paperSize.Height, 96, 96)
Save(image2, "render2.png")
End Sub
[STAThread]
static void Main(string[] args)
{
UIElement e;
string file = Directory.GetCurrentDirectory() + "\\starting.xaml";
using (Stream stream = File.Open(file, FileMode.Open))
{
// loading files from current directory, project settings take care of copying the file
ParserContext pc = new ParserContext();
pc.BaseUri = new Uri(file, UriKind.Absolute);
e = (UIElement)XamlReader.Load(stream, pc);
}
Size paperSize = new Size(8.5 * 96, 11 * 96);
e.Measure(paperSize);
e.Arrange(new Rect(paperSize));
e.UpdateLayout();
/*
* Render effect at normal dpi, indicator is the original RED rectangle
*/
RenderTargetBitmap image1 = Rasterize(e, paperSize.Width, paperSize.Height, 96, 96);
Save(image1, "render1.png");
Button b = new Button();
b.BeginInit();
b.Background = Brushes.Blue;
b.Width = b.Height = 200;
b.EndInit();
b.Measure(paperSize);
b.Arrange(new Rect(paperSize));
b.UpdateLayout();
// now render the altered version, with the element built up and initialized
RenderTargetBitmap image2 = Rasterize(b, paperSize.Width, paperSize.Height, 96, 96);
Save(image2, "render2.png");
}