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

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

元素和逻辑树

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

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

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

使用 BeginInit 和 EndInit 初始化元素

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

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

代码示例

下面的示例是一个控制台应用程序的示例代码,该应用程序使用呈现 API 和宽松 XAML 文件的 XamlReader.Load(Stream) 来说明如何将 BeginInitEndInit 正确放置在调整影响呈现的属性的其他 API 调用周围。

该示例仅演示主要函数。 函数 RasterizeSave(未显示)是负责图像处理和 IO 的实用工具函数。

[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

另请参阅