Inicialización de elementos de objeto no incluidos en un árbol de objetos

Algunos aspectos de inicialización de Windows Presentation Foundation (WPF) se fraccionan en procesos que suelen depender de la conexión del elemento con el árbol lógico o visual. En este tema se describen los pasos que pueden ser necesarios para inicializar un elemento que no esté conectado a ninguno de estos árboles.

Elementos y el árbol lógico

Cuando se crea una instancia de una clase de Windows Presentation Foundation (WPF) en el código, se debe tener en cuenta que varios aspectos de la inicialización de objetos para una clase de Windows Presentation Foundation (WPF) se excluyen deliberadamente del código que se ejecuta al llamar al constructor de la clase. En concreto, en el caso de las clases de control, el constructor no define la mayor parte de la representación visual del control, sino que es la plantilla del control la que define la representación visual. La plantilla puede proceder de diversos orígenes, pero, en la mayoría de casos, se obtiene a partir de estilos de tema. Las plantillas funcionan como enlace en tiempo de ejecución efectivo: la plantilla necesaria no se adjunta al control en cuestión hasta que este está listo para el diseño. Y el control no está listo para el diseño hasta que se adjunta a un árbol lógico que se conecta a una superficie de representación en la raíz. Este elemento de nivel de raíz inicia la representación de todos sus elementos secundarios, como se define en el árbol lógico.

El árbol visual también participa en este proceso. Tampoco se crean todas las instancias de los elementos que forman parte del árbol visual a través de las plantillas hasta que se conectan.

Las consecuencias de este comportamiento son que ciertas operaciones que dependen de las características visuales completadas de un elemento requieren pasos adicionales. Un ejemplo sería intentar obtener las características visuales de una clase que se construyó, pero todavía no se ha adjuntado a un árbol. Por ejemplo, si quiere llamar a Render en RenderTargetBitmap y el objeto visual que se pasa es un elemento no conectado a un árbol, ese elemento no estará completo visualmente hasta que se completen los pasos de inicialización adicionales.

Uso de BeginInit y EndInit para inicializar el elemento

Varias clases en WPF implementan la interfaz ISupportInitialize. Utilice los métodos BeginInit y EndInit de la interfaz para denotar una región en su código que contiene pasos de inicialización (como el establecimiento de valores de propiedades que afectan a la representación). Después de llamar a EndInit en la secuencia, el sistema de diseño puede procesar el elemento y empezar a buscar un estilo implícito.

Si el elemento sobre el que está estableciendo propiedades es una clase derivada FrameworkElement o FrameworkContentElement, entonces puede llamar a las versiones de clase de BeginInit y EndInit en lugar de hacer una conversión a ISupportInitialize.

Código de ejemplo

El siguiente ejemplo es un código de muestra para una aplicación de consola que utiliza las API de representación y XamlReader.Load(Stream) de un archivo XAML dinámico para ilustrar la colocación adecuada de BeginInit y EndInit alrededor de otras llamadas a la API que ajustan las propiedades que afectan a la representación.

El ejemplo solo ilustra la función principal. Las funciones Rasterize y Save (que no se muestran) son funciones de utilidad que se encargan del procesamiento de imágenes y la E/S.

[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

Vea también