Инициализация для объектных элементов, которые не находятся в дереве объектов

За некоторые аспекты инициализации Windows Presentation Foundation (WPF) отвечают процессы, которые обычно предполагают, что элемент подключен либо к логическому, либо к визуальному дереву. В этом разделе описываются действия, необходимые для инициализации элемента, который не подключен ни к одному из деревьев.

Элементы и логическое дерево

При создании экземпляра класса Windows Presentation Foundation (WPF) в коде следует иметь в виду, что несколько аспектов инициализации объекта для класса Windows Presentation Foundation (WPF) преднамеренно не являются частью кода, выполняющегося при вызове конструктора класса. Большая часть визуального представления элемента управления, особенно для класса элемента управления, не определяется конструктором. Вместо этого визуальное представление определяется шаблоном элемента управления. Шаблон может быть получен из различных источников, но наиболее часто он берется из стилей темы. Шаблоны фактически выполняют позднее связывание. Необходимый шаблон не присоединяется к элементу управления до тех пор, пока элемент управления не будет подготовлен для макета. А элемент управления не готов для макета до тех пор, пока он не будет присоединен к логическому дереву, которое подключается к отрисовываемой поверхности в корневом элементе. Именно элемент корневого уровня инициирует отрисовку всех дочерних элементов, как определено в логическом дереве.

Визуальное дерево также участвует в этом процессе. Элементы, которые являются частью визуального дерева посредством шаблонов, также полностью не создаются до тех пор, пока не произойдет подключение.

Последствием этого является то, что для определенных операций, зависящих от завершенных визуальных характеристик элемента, требуется выполнение дополнительных действий. Примером является попытка получить визуальные характеристики класса, который был создан, но еще не присоединен к дереву. Например, если необходимо вызвать Render для RenderTargetBitmap и передаваемый визуальный элемент не подключен к дереву, то визуально этот элемент остается незавершенным, пока не будут выполнены дополнительные действия по инициализации.

Использование BeginInit и EndInit для инициализации элемента

Различные классы WPF реализуют интерфейс ISupportInitialize. Методы BeginInit и EndInit интерфейса используются для обозначения региона в коде, который содержит этапы инициализации (например, настройка значений свойства, влияющие на рендеринг). После вызова EndInit в последовательности система макета может обработать элемент и начать поиск неявного стиля.

Если элемент, в котором задаются свойства, является классом FrameworkElement или производным классом FrameworkContentElement, можно вызвать версии класса BeginInit и EndInit вместо приведения к ISupportInitialize.

Пример кода

В следующем примере показан пример кода для консольного приложения, которое использует API рендеринга и XamlReader.Load(Stream) свободного файла XAML для демонстрации корректного размещения BeginInit и EndInit вокруг других вызовов API, чтобы настроить свойства, влияющие на рендеринг.

В этом примере иллюстрируется только основная функция. Функции 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

См. также