不在对象树中的对象元素的初始化

Windows Presentation Foundation (WPF) 初始化的某些方面被推迟到通常依赖连接到逻辑树或可视化树的元素的进程。 本主题介绍初始化未连接到两种树之一的元素可能需要的步骤。

本主题包括下列各节。

  • 元素和逻辑树
  • 相关主题

元素和逻辑树

当在代码中创建 Windows Presentation Foundation (WPF) 类的实例时,应注意 Windows Presentation Foundation (WPF) 类的对象初始化的几个方面特意不在调用类构造函数时所执行的代码的某一部分实现。 这种情况对于控件类尤为突出,该控件的大部分可视化表示都不是由构造函数定义的, 而是由控件的模板定义的。 模板可能来自于各种源,但是最常见的情况是来自于主题样式。 模板实际上是后期绑定的;只有在相应的控件已准备好应用布局时,才会将所需的模板附加到该控件上。 并且控件只有在已附加到连接到根级别的呈现图面的逻辑树时,才能准备好应用布局。 将由根级元素启动在逻辑树中定义的所有子元素的呈现。

可视化树也参与此过程。 通过模板成为可视化树一部分的元素也是在连接后才完全实例化的。

此行为的结果是,依赖元素的已完成可视化特征的某些操作需要额外的步骤。 例如,如果您尝试获取一个已构造但还未附加到树的类的可视化特征,就需要额外的步骤。 例如,如果您需要对 RenderTargetBitmap 调用 Render,并且要传递的 Visual 是未连接到树的元素,则直到完成了额外的初始化步骤,该元素在可视化效果方面才完成。

使用 BeginInit 和 EndInit 初始化元素

WPF 中有多个类实现 ISupportInitialize 接口。 使用该接口的 BeginInitEndInit 方法可以表示代码中包含初始化步骤(例如,设置影响呈现的属性值)的一个区域。 在按顺序调用 EndInit 之后,布局系统就可以处理元素并开始查找隐式样式。

如果针对其设置属性的元素是 FrameworkElementFrameworkContentElement 派生类,则您可以调用 BeginInitEndInit 的类版本,而不是强制转换为 ISupportInitialize

代码示例

下面的示例是一个控制台应用程序的代码示例,该应用程序使用呈现 APIs 和一个松散 XAML 文件的 XamlReader.Load(Stream) 来演示 BeginInitEndInit 相对于其他调整影响呈现的属性的 API 调用的正确位置。

此示例仅演示主要函数。 函数 Rasterize 和 Save(未显示)是负责图像处理和 IO 的实用程序函数。

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

请参见

概念

WPF 中的树

WPF 图形呈现疑难解答

XAML 概述 (WPF)