Inicialização de elementos de objeto que não estejam em uma árvore de objetos

Alguns aspectos da inicialização do Windows Presentation Foundation (WPF) são adiados para processos que normalmente dependem da conexão desse elemento com a árvore lógica ou a árvore visual. Este tópico descreve as etapas que podem ser necessárias para inicializar um elemento que não está conectado a nenhuma árvore.

Elementos e a árvore lógica

Quando você cria uma instância de uma classe do Windows Presentation Foundation (WPF) no código, você deve estar ciente de que vários aspectos da inicialização de objeto para uma classe do Windows Presentation Foundation (WPF) deliberadamente não são uma parte do código que é executado ao chamar o construtor de classe. Especialmente para uma classe de controle, a maior parte da representação visual do controle não é definida pelo construtor. Em vez disso, a representação visual é definida pelo modelo do controle. O modelo pode vir de uma variedade de fontes, mas geralmente é obtido de estilos de tema. Os modelos são efetivamente de associação tardia; o modelo necessário não é anexado ao controle em questão até que o controle esteja pronto para o layout. E o controle não está pronto para o layout até seja anexado a uma árvore lógica que se conecte a uma superfície de renderização na raiz. É esse elemento de nível raiz que inicia a renderização de todos os seus elementos filho, conforme definido na árvore lógica.

A árvore visual também participa deste processo. Elementos que fazem parte da árvore visual por meio de modelos também não são totalmente instanciados até que sejam conectados.

As consequências deste comportamento são que certas operações que dependem das características visuais concluídas de um elemento exigem etapas adicionais. Um exemplo é se você está tentando obter as características visuais de uma classe construída, mas ainda não anexada a uma árvore. Por exemplo, se você deseja chamar Render um e o visual que você está passando é um RenderTargetBitmap elemento não conectado a uma árvore, esse elemento não está visualmente completo até que etapas de inicialização adicionais sejam concluídas.

Utilizando BeginInit e EndInit para inicializar o elemento

Várias classes no WPF implementam a ISupportInitialize interface. Use os BeginInit métodos e EndInit da interface para indicar uma região em seu código que contém etapas de inicialização (como definir valores de propriedade que afetam a renderização). Depois EndInit de ser chamado na sequência, o sistema de layout pode processar o elemento e começar a procurar um estilo implícito.

Se o elemento no qual você está definindo propriedades for uma classe ou FrameworkContentElement derivada, você poderá chamar as versões de classe de e EndInit em vez de BeginInit converter para ISupportInitialize.FrameworkElement

Exemplo de código

O exemplo a seguir é um código de exemplo para um aplicativo de console que usa APIs de renderização e XamlReader.Load(Stream) de um arquivo XAML solto para ilustrar o posicionamento adequado de e em torno de outras chamadas de BeginInit API que ajustam EndInit propriedades que afetam a renderização.

O exemplo ilustra apenas a função principal. As funções Rasterize e Save (não mostradas) são funções utilitárias que cuidam do processamento de imagens e 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

Confira também