Поделиться через


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

Некоторые аспекты инициализации 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

См. также