オブジェクト ツリーに存在しないオブジェクト要素の初期化

Windows Presentation Foundation (WPF) の初期化処理では、プロセスに処理を委任することがあり、そのプロセスは、一般的にその要素が論理ツリーまたはビジュアル ツリーのいずれかに接続されていることを前提としています。 このトピックでは、どちらのツリーにも接続されていない要素を初期化するために必要となる場合がある手順について説明します。

要素と論理ツリー

コード内で Windows Presentation Foundation (WPF) クラスのインスタンスを作成するときは、Windows Presentation Foundation (WPF) クラスのオブジェクト初期化処理の一部が、クラス コンストラクターの呼び出し時に実行されるコードから意図的に除外されていることに注意してください。 特に、コントロール クラスの場合は、コントロールの視覚的表現のほとんどが、コンストラクターでは定義されません。 代わりに、視覚的表現は、コントロールのテンプレートによって定義されます。 このテンプレートのソースには、さまざまなものがありますが、ほとんどの場合は、テーマ スタイルから取得されます。 テンプレートは、実質的には遅延バインディングです。そのコントロールがレイアウト可能になるまで、必要なテンプレートはコントロールに適用されません。 コントロールは、ルートでレンダリングするための表面に接続される論理ツリーに適用されるまで、レイアウトが可能になりません。 このルート レベル要素によって、論理ツリーで定義されているすべての子要素のレンダリングが開始されます。

このプロセスには、ビジュアル ツリーも関与します。 テンプレートによってビジュアル ツリーに組み込まれる要素も、接続されるまでは完全にインスタンス化されません。

この動作の影響で、要素の視覚的特性がすべて揃っていることを前提とする特定の操作には、追加の手順が必要になります。 構築されているが、まだツリーに適用されていないクラスの視覚的特性を取得しようとする場合などが、その例です。 たとえば、RenderTargetBitmapRender を呼び出す必要があり、渡そうとしているビジュアルがツリーに接続されていない要素である場合は、追加の初期化手順が完了するまで、その要素は視覚的に完成されません。

BeginInit と EndInit を使用して要素を初期化する

WPF のさまざまなクラスでは、ISupportInitialize インターフェイスが実装されます。 このインターフェイスの BeginInit メソッドと EndInit メソッドを使用して、初期化手順を含むコード内の領域を表します (たとえば、レンダリングに影響を与えるプロパティ値の設定)。 シーケンス内で EndInit が呼び出された後に、レイアウト システムは要素を処理し、暗黙的スタイルの検索を開始します。

プロパティの設定しようとしている要素が FrameworkElement または FrameworkContentElement 派生クラスである場合は、ISupportInitialize にキャストするのではなく、BeginInitEndInit のクラス バージョンを呼び出すことができます。

サンプル コード

次の例は、ルーズ XAML ファイルのレンダリング API と XamlReader.Load(Stream) を使用して、レンダリングに影響を与えるプロパティを調整する他の API 呼び出しの周辺への BeginInitEndInit の適切な配置を示すコンソール アプリケーションのサンプル コードです。

この例では、main 関数のみを示します。 関数 Rasterize および Save (この例には示していません) は、イメージ処理および入出力を扱うユーティリティ関数です。

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

関連項目