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


Структуры и основные понятия потока узлов XAML

Средства чтения и записи XAML, реализованные в службах XAML .NET, основаны на концепции проектирования потока узлов XAML. Поток узлов XAML — это концептуальное представление набора узлов XAML. В этом концептуальном представлении обработчик XAML проходит по структуре связей узлов в XAML поочередно. В каждый момент времени в открытом потоке узлов XAML существует только одна текущая запись или текущая позиция, и многие аспекты API сообщают только информацию, доступную из этой позиции. Текущий узел в потоке узлов XAML можно описать как объект, член или значение. Рассматривая XAML как поток узлов XAML, средства чтения XAML могут взаимодействовать со средствами записи XAML и позволять программе просматривать содержимое потока узлов XAML, взаимодействовать с ним или изменять его во время функционирования пути загрузки или пути сохранения, использующего XAML. Проектирование API чтения и записи XAML и концепция потока узлов XAML похожи на предыдущие связанные схемы и концепции чтения и записи, такие как модель объектов XML-документов (DOM) и XmlReader классы и .XmlWriter В этом разделе рассматриваются концепции потока узлов XAML и описывается, как можно создавать подпрограммы, взаимодействующие с представлениями XAML на уровне узлов XAML.

Загрузка XAML в средство чтения XAML

Базовый класс XamlReader не объявляет конкретный метод загрузки исходного XAML в средство чтения XAML. Вместо этого метод загрузки, включая общие характеристики и ограничения его источника входных данных для XAML, объявляет и реализует производный класс. Например, класс XamlObjectReader читает граф объектов, начиная с источника входных данных одного объекта, который представляет корень или базу. Затем класс XamlObjectReader создает поток узлов XAML из этого графа объектов.

Наиболее известным подклассом XamlReader служб XAML для .NET является XamlXmlReader. ПодклассXamlXmlReader загружает исходный XAML либо непосредственно, путем загрузки текстового файла через поток или путь к файлу, либо косвенно — через связанный класс средства чтения, такой как TextReader. Класс XamlReader можно рассматривать как содержащий весь источник входных данных XAML после его загрузки. Однако базовый API XamlReader разработан таким образом, чтобы средство чтения взаимодействовало с одним узлом XAML. После первой загрузки первый обнаруженный узел является корнем XAML и его начальным объектом.

Концепция потока узлов XAML

Если вы более знакомы с моделью модели DOM, метафорой дерева или подходом на основе запросов к доступу к технологиям на основе XML, полезным способом осмысления потока узлов XAML является следующий способ. Представьте себе загруженный XAML в виде модели DOM или дерева, где каждый возможный узел полностью развернут, а затем представлен в линейной форме. При переходе по узлам вы можете проходить «в» уровни или «из» уровней, которые относятся к модели DOM, но поток узлов XAML явно не отслеживается, так как эти понятия уровней не относятся к потоку узлов. Поток узлов имеет «текущую» позицию, но пока вы самостоятельно не сохраните другие части потока как ссылки, любой аспект потока узлов, кроме текущего положения узла, находится вне поля зрения за пределами окна просмотра.

Существенное преимущество концепции потока узлов XAML заключается в том, что при проходе по всему потоку узла будет гарантированно обработано все представление XAML; не нужно беспокоиться, что запрос, операция DOM или какой-либо другой нелинейный способ обработки информации пропустит часть представления XAML. По этой причине представление потока узлов XAML идеально подходит как для соединения средств чтения и записи XAML, так и для построения системы, в которой можно вставить собственный процесс, выполняемый между этапами чтения и записи операции обработки XAML. Во многих случаях порядок узлов в потоке узлов XAML намеренно оптимизируется или изменяется средствами чтения XAML по сравнению с тем порядком, который может отображаться в исходном тексте, двоичном объекте или графе объекта. Это поведение предусмотрено для обеспечения архитектуры обработки XAML, в которой средства записи XAML никогда не оказываются в позиции, где они должны переходить «назад» в потоке узлов. В идеале все операции записи XAML должны быть способны выполняться на основе контекста схемы и текущей позиции в потоке узлов.

Основной цикл узлов чтения

Основной цикл узлов чтения для анализа потока узлов XAML включает следующие концепции. Применительно к циклам узлов, рассматриваемым в этом разделе, предположим, что вы читаете понятный для пользователя текстовый файл XAML с помощью XamlXmlReader. Ссылки в этом разделе относятся к конкретному API цикла узлов XAML, реализуемому XamlXmlReader.

  • Убедитесь, что вы не находитесь в конце потока узлов XAML (проверьте IsEof, или используйте возвращаемое значение Read ). Если вы находитесь в конце потока, то текущий узел отсутствует и следует выйти.

  • Проверьте, какой тип узла предоставляет в настоящий момент поток узлов XAML, путем вызова NodeType.

  • Если имеется связанное средство записи объектов XAML, которое подключено напрямую, то на этом этапе обычно вызывается метод WriteNode .

  • В зависимости от типа XamlNodeType , возвращенного для текущего узла или текущей записи, вызовите один из следующих объектов, чтобы получить сведения о содержимом узла.

    • Для типа NodeType объекта StartMember или EndMemberвызовите Member , чтобы получить сведения XamlMember о члене. Элемент может быть элементом XamlDirectiveи, следовательно, не обязательно быть обычным элементом, определяемым типом предыдущего объекта. Например, примененная к объекту директива x:Name отображается как член XAML, свойство IsDirective которого имеет значение true, свойство Name этого члена имеет значение Name, а другие свойства указывают, что эта директива находится в пространстве имен XAML языка XAML.

    • Для типа NodeType объекта StartObject или EndObjectвызовите Type , чтобы получить сведения XamlType об объекте.

    • Для типа NodeType объекта Valueвызовите Value. Узел имеет значение только в том случае, если он является простейшим выражением значения для члена или текстом инициализации объекта (однако следует иметь в виду поведение преобразования типов, как описано в следующем разделе этой статьи).

    • Для типа NodeType объекта NamespaceDeclarationвызовите Namespace , чтобы получить сведения о пространстве имен для узла пространства имен.

  • Вызовите метод Read , чтобы средство чтения XAML перешло к следующему узлу в потоке узлов XAML, и повторите эти действия.

Поток узлов XAML, предоставляемый средствами чтения XAML Служб XAML .NET, всегда обеспечивает полный, глубокий обход всех возможных узлов. Типичные методы управления потоком для цикла узлов XAML включают определение текста в цикле while (reader.Read())и переключение на NodeType в каждой точке узла в цикле узлов.

Если поток узлов находится в конце файла, текущий узел имеет значение null.

В следующем примере показан простейший цикл, использующий средства чтения и записи.

XamlXmlReader xxr = new XamlXmlReader(new StringReader(xamlStringToLoad));
//where xamlStringToLoad is a string of well formed XAML
XamlObjectWriter xow = new XamlObjectWriter(xxr.SchemaContext);
while (xxr.Read()) {
  xow.WriteNode(xxr);
}

Этот простой пример цикла узлов XAML пути загрузки прозрачно связывает средство чтения XAML и средство записи XAML, выполняя то же самое, что выполняется при использовании метода XamlServices.Parse. Но затем эта базовая структура расширяется для применения в сценарии чтения или записи. Далее приведены некоторые возможные сценарии.

  • Переключиться в NodeType. Выполнять разные действия в зависимости от того, какой узел читается.

  • Не вызывать метод WriteNode во всех случаях. Вызывать метод WriteNode только в некоторых случаях NodeType .

  • В пределах логики для конкретного типа узла анализировать особенности этого узла и работать с ними. Например, можно записывать только объекты, поступающие из определенного пространства имен XAML, а затем удалять или откладывать любые объекты, не из этого пространства имен XAML. Вы также можете удалить или иным образом повторно обработать все директивы XAML, которые не поддерживаются вашей системой XAML, в рамках обработки элементов.

  • Определить пользовательский объект XamlObjectWriter , который переопределяет методы Write* , возможно, выполняя сопоставление типов, обходящее контекст схемы XAML.

  • Создать объект XamlXmlReader для использования контекста схемы XAML не по умолчанию, чтобы настроенные различия в поведении XAML использовались как средством чтения, так и средством записи.

Доступ к XAML за рамками концепции цикла узлов

Помимо цикла узлов XAML существуют и другие способы работы с представлением XAML. Например, может существовать средство чтения XAML, которое может читать индексированный узел, в частности обращаться к узлам напрямую с помощью директивы x:Name, x:Uidили используя другие идентификаторы. Службы XAML для .NET не предоставляют полную реализацию, но предоставляют рекомендуемый шаблон через службы и типы поддержки. Дополнительные сведения см. в разделах IXamlIndexingReader и XamlNodeList.

Работа с текущим узлом

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

В типичном сценарии пути загрузки XamlXmlReader создает поток узлов XAML; узлы XAML обрабатываются в соответствии с заданной логикой и контекстом схемы XAML; затем узлы передаются в XamlObjectWriter. Затем вы интегрируете полученный граф объектов в свое приложение или структуру.

В типичном сценарии пути сохранения XamlObjectReader читает граф объектов; отдельные узлы XAML обрабатываются; затем XamlXmlWriter выдает сериализованный результат в виде текстового файла XAML. Ключ заключается в том, что пути и сценарии предполагают работу только с одним узлом XAML за раз, а узлы XAML доступны для обработки стандартизированным способом, который определяется системой типов XAML и the.NET API служб XAML.

Фреймы и область

Цикл узлов XAML проходит по потоку узлов XAML линейным образом. Поток узлов углубляется в объекты, в члены, которые содержат другие объекты, и т. д. Часто бывает удобно отслеживать область в потоке узлов XAML путем реализации концепции фрейма и стека. В частности, это справедливо для случая активной настройки потока узла при нахождении в нем. Поддержка кадра и стека, реализуемая в рамках логики цикла узлов, может учитывать области StartObject (или GetObject) и EndObject по мере углубления в структуру узлов XAML, если рассматривать структуру с точки зрения модели DOM.

Обход узлов объекта и вход в них

Первым узлом в потоке узлов при его открытии средством чтения XAML является узел начального объекта корневого объекта. По определению этот объект всегда является узлом одного объекта и не имеет одноранговых узлов. В любом реальном примере XAML корневой объект определяется с одним или несколькими свойствами, содержащими дополнительные объекты, и эти свойства имеют узлы-члены. В свою очередь, узлы-члены могут иметь один или несколько узлов-объектов или вместо этого заканчиваться в узле значения. Корневой объект обычно определяет области имен XAML, которые синтаксически назначаются как атрибуты в текстовой разметке XAML, но соответствуют типу узла Namescope в представлении потока узлов XAML.

Рассмотрим следующий пример XAML (это произвольный КОД XAML, не поддерживаемый существующими типами в .NET). Предполагается, что в этой объектной модели FavorCollection является List<T> объектов Favor, Balloon и NoiseMaker , которые могут быть присвоены Favor, свойство Balloon.Color поддерживается объектом Color аналогично тому, как в WPF цвета задаются при помощи известных названий цветов, а объект Color поддерживает преобразователь типов для синтаксиса атрибутов.

Разметка XAML Итоговый поток узлов XAML
<Party Namespace для Party
xmlns="PartyXamlNamespace"> StartObject для Party
<Party.Favors> StartMember для Party.Favors
StartObject для неявного объекта FavorCollection
УзелStartMember для свойства неявных элементов FavorCollection .
<Balloon StartObject для Balloon
Color="Red" StartMember для Color

Value для строки значения атрибута "Red"

EndMember для Color;
HasHelium="True" StartMember для HasHelium

Value для строки значения атрибута "True"

EndMember для HasHelium;
> EndObject для Balloon;
<NoiseMaker>Loudest</NoiseMaker> StartObject для NoiseMaker

StartMember для _Initialization

Value для строки значения инициализации "Loudest"

EndMember для _Initialization

EndObject для NoiseMaker;
УзелEndMember для свойства неявных элементов FavorCollection .
EndObject для неявного объекта FavorCollection
</Party.Favors> EndMember для Favors;
</Party> EndObject для Party;

В потоке узлов XAML можно рассчитывать на следующее поведение.

  • Если узел Namespace существует, он добавляется в поток непосредственно перед объектом StartObject , который объявил пространство имен XAML с помощью xmlns. Давайте снова взглянем на предыдущую таблицу с XAML и примером потока узлов. Обратите внимание, как, по всей видимости, будут перемещены узлы StartObject и Namespace по сравнению с их объявленными позициями в разметке текста. Это типичное представление поведения, в котором узлы пространства имен всегда отображаются перед узлом, к которому они имеют отношение в потоке узлов. Смысл этой конструкции заключается в том, что сведения о пространстве имен, необходимые для средств записи объектов, должны быть известны до того, как средство записи объекта попытается выполнить сопоставление типов или другую обработку объекта. Помещение сведений о пространстве имен XAML перед его областью применения в потоке облегчает обработку потока узлов в представленном порядке.

  • По этой причине в большинстве случаев реального использования разметки существует один или несколько узлов Namespace , которые читаются первыми при обходе узлов с начала, а не с объекта StartObject корня.

  • За узлом StartObject может следовать StartMember, Valueили непосредственно EndObject. За ним никогда не следует сразу же другой StartObject.

  • За StartMember может следовать StartObject, Valueили непосредственно EndMember. За ним может следовать GetObject, для членов, в которых значение должно быть получено из существующего значения родительского объекта, а не из StartObject , который может создавать экземпляр нового значения. За ним также может следовать узел Namespace , который относится к следующему StartObject. За ним никогда не следует сразу же другой StartMember.

  • Узел Value представляет само значение; это не EndValue. За ним может следовать только EndMember.

    • Текст инициализации XAML объекта, который может использоваться конструкцией, не приводит к структуре «Объект-значение». Вместо этого создается выделенный узел для члена с именем _Initialization , и этот узел члена содержит строку значения инициализации. Если он существует, то _Initialization всегда является первым StartMember. Член_Initialization может быть определен в некоторых представлениях служб XAML при помощи области имен XAML языка XAML, чтобы уточнить, что _Initialization не является заданным свойством в резервных типах.

    • Комбинация «Член-значение» представляет настройку атрибута значения. В конечном итоге возможно включение преобразователя значений в обработку этого значения, и значение представляет собой простую строку. Однако это не вычисляется до тех пор, пока средство записи объектов XAML не обработает этот поток узлов. Средство записи объектов XAML обрабатывает необходимый контекст схемы XAML, сопоставление системы типов и другую поддержку, необходимую для преобразований значений.

  • За узлом EndMember может следовать узел StartMember для последующего члена или узел EndObject для владельца этого члена.

  • За узлом EndObject может следовать узел EndMember . За ним может также следовать узел StartObject для случаев, когда эти объекты являются одноранговыми узлами в элементах коллекции. Кроме того, за ним может следовать узел Namespace , который относится к следующему StartObject.

    • В исключительном случае закрытия всего потока узлов за объектом EndObject корня не следует ничего; средство чтения достигает конца файла, и метод Read возвращает значение false.

Преобразователи значений и поток узлов XAML

Преобразователь значений — это общий термин для расширения разметки, преобразователя типов (включая сериализаторы значений) или другого выделенного класса, указанного как преобразователь значений в системе типов XAML. В потоке узлов XAML использование преобразователя типов и использование расширения разметки имеют совершенно разные представления.

Преобразователи значений в потоке узлов XAML

Набор атрибутов, который в конечном итоге приводит к использованию преобразователя типов, сообщается в поток узлов XAML как значение члена. Поток узлов XAML не пытается создать экземпляр объекта преобразователя типов и передать ему значение. Использование реализации преобразования преобразователя типов требует вызова контекста схемы XAML и использования его для сопоставления типов. Даже для определения того, какой класс преобразователя типов следует использовать для обработки значения, косвенно требуется контекст схемы языка XAML. При использовании контекста схемы XAML по умолчанию эти сведения можно получить из системы типов XAML. Если сведения класса преобразователя типов на уровне потока узлов XAML требуются до подключения к средству записи XAML, их можно получить из сведений XamlMember устанавливаемого члена. Но в противном случае входные данные преобразователя типов должны сохраняться в потоке узлов XAML в виде обычного значения до тех пор, пока не будут выполнены остальные операции, для которых требуется система сопоставления типов и контекст схемы XAML, например создание объекта с помощью модуля записи объектов XAML.

Например, рассмотрим следующую структуру определения класса и использования для него XAML.

public class BoardSizeConverter : TypeConverter {
  //converts from string to an int[2] by splitting on an "x" char
}
public class GameBoard {
  [TypeConverter(typeof(BoardSizeConverter))]
  public int[] BoardSize; //2x2 array, initialization not shown
}
<GameBoard BoardSize="8x8"/>

Текстовое представление потока узлов XAML для данного использования можно выразить следующим образом:

StartObject с XamlType , представляющий GameBoard

StartMember с XamlMember , представляющий BoardSize

УзелValue с текстовой строкой "8x8"

EndMember , соответствующий BoardSize

EndObject , соответствующий GameBoard

Обратите внимание, что в этом потоке узлов отсутствует экземпляр преобразователя типов. Однако вы можете получить сведения о преобразователе типов, вызвав XamlMember.TypeConverter в XamlMember для BoardSize. При наличии допустимого контекста схемы XAML можно также вызвать методы преобразователя, получив экземпляр из ConverterInstance.

Расширения разметки в потоке узлов XAML

Об использовании расширения разметки сообщается в поток узлов XAML как об узле объекта в члене, где объект представляет экземпляр расширения разметки. Таким образом, использование расширения разметки более явно представлено в представлении потока узлов, чем использование преобразователя типов, и содержит больше сведений. СведенияXamlMember могут ничего не сообщать о расширении разметки, поскольку его использование зависит от ситуации и меняется в каждом возможном случае разметки; оно не является выделенным и неявным согласно типу или члену, как в случае с преобразователями типов.

Представление потока узлов расширений разметки как узлов объектов происходит даже в случае, когда использование расширения разметки было сделано в форме атрибута в текстовой разметке XAML (что происходит часто). Использование расширения разметки с применением явных форм элементов объектов интерпретируется таким же образом.

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

При использовании позиционного параметра поток узлов XAML содержит заданное на уровне языка XAML свойство _PositionalParameters , регистрирующее это использование. Это свойство является универсальным List<T> с ограничением Object . Это ограничение является объектом, а не строкой, поскольку предположительно использование позиционного параметра может содержать в себе вложенные использования расширения разметки. Для доступа к позиционным параметрам из использования можно выполнить итерацию по списку и применять индексаторы для отдельных значений списка.

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

ProvideValue из расширения разметки еще не вызывался. Однако он вызывается при подключении средства чтения XAML и средства записи XAML, чтобы вызывался WriteEndObject в узле расширения разметки при его исследовании в потоке узлов. По этой причине обычно требуется доступ к тому же контексту схемы XAML, который использовался бы для формирования графа объектов в пути загрузки. В противном случае ProvideValue из любого расширения разметки может вызывать здесь исключения, поскольку не имеет доступа к ожидаемым службам.

Члены, заданные на уровне XAML и XML, в потоке узлов XAML

Определенные члены введены в поток узлов XAML из-за определений и соглашений средства чтения XAML, вместо того чтобы использовать явный поиск или построение XamlMember . Часто эти члены являются директивами XAML. В некоторых случаях это действие чтения XAML, вводящее директиву в поток узлов XAML. Иными словами, в исходном входном тексте XAML явно не указана директива-член, но средство чтения XAML вставляет директиву, чтобы удовлетворить структурное соглашение XAML и сообщить сведения в потоке узлов XAML до потери этой информации.

В следующем списке описаны все случаи, когда средство чтения XAML должно ввести узел-член директивы XAML, а также то, как этот узел-член определяется в реализациях служб XAML .NET.

  • Текст инициализации для узла объекта. Имя этого узла члена — _Initialization, он представляет собой директиву XAML и определен в пространстве имен XAML языка XAML. Вы можете получить для него статическую сущность из Initialization.

  • Позиционные параметры для расширения разметки. Имя этого узла члена — _PositionalParameters, и он определен в пространстве имен XAML языка XAML. Он всегда содержит универсальный список объектов, каждый из которых является позиционным параметром, предварительно отделенным по символу-разделителю , , как представлено во входных данных XAML. Вы можете получить статическую сущность для директивы позиционных параметров из PositionalParameters.

  • Неизвестное содержимое. Имя этого узла члена — _UnknownContent. Строго говоря, это директива XamlDirective, которая определяется в пространстве имен XAML языка XAML. Эта директива используется как сигнальная метка в случаях, когда в элементе объекта XAML имеется содержимое в исходном XAML, но никакое свойство содержимого невозможно определить в рамках текущего доступного контекста схемы XAML. Такой случай можно обнаружить в потоке узлов XAML, проверив члены с именем _UnknownContent. Если в потоке узлов XAML пути загрузки никакие другие действия не выполняются, вызывается XamlObjectWriter по умолчанию при попытке WriteEndObject , когда в каком-либо объекте обнаруживается член _UnknownContent . Объект XamlXmlWriter по умолчанию не вызывается и рассматривает этот член как неявный. Вы можете получить статическую сущность для _UnknownContent из UnknownContent.

  • Свойство collection коллекции: Хотя резервный тип CLR класса коллекции, используемого для XAML, обычно имеет выделенное именованное свойство, которое содержит элементы коллекции, это свойство не известно системе типов XAML до резервного разрешения типов. Вместо этого поток узлов XAML вводит заполнитель Items в качестве члена типа коллекции XAML. В реализации служб XAML .NET имя этой директивы или члена в потоке узлов — _Items. Константу для этой директивы можно получить из Items.

    Обратите внимание, что поток узлов XAML может содержать свойство Items с элементами, которые не могут быть проанализированы на основе разрешения резервного типа и контекста схемы XAML. Например,

  • Члены, определяемые XML: Члены , определяемые xml:baseXML, xml:lang и xml:space передаются как директивы XAML с именами base, langи space в реализациях служб XAML .NET. Их пространство имен — это пространство имен XML http://www.w3.org/XML/1998/namespace. Константы для каждого из них можно получить из XamlLanguage.

Порядок узлов

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

Некоторые директивы предназначены специально в целях предоставления дополнительных сведений для создания объекта из элемента объекта. Это директивы Initialization, PositionalParameters, TypeArguments, FactoryMethod, Arguments. Читатели XAML служб .NET пытаются разместить эти директивы в качестве первых членов в потоке узлов после объекта StartObjectпо причинам, которые описаны в следующем разделе.

Поведение XamlObjectWriter и порядок узлов

StartObject в XamlObjectWriter не обязательно сигнализирует средству записи объектов XAML немедленно создать экземпляр объекта. XAML включает в себя несколько языковых функций, которые позволяют инициализировать объект с дополнительными входными данными и не полностью полагаться на вызов конструктора без параметров для создания исходного объекта и только после этого задания свойств. Эти возможности включают XamlDeferLoadAttribute; текст инициализации; x: TypeArguments; позиционные параметры расширения разметки; фабричные методы и связанные узлы x: Arguments (XAML 2009 г.). Каждый из этих случаев задерживает фактическое создание объекта, и так как поток узла переупорядочен, модуль записи объектов XAML может полагаться на поведение фактического создания экземпляра всякий раз, когда обнаруживается начальный член, который не является директивой конструктора для этого типа объекта.

GetObject

GetObject представляет узел XAML, где вместо создания нового объекта средство записи объектов XAML должно получать значение содержащегося свойства объекта. Типичный GetObject случай, когда узел встречается в потоке узлов XAML, относится к объекту коллекции или объекту словаря, когда свойство, содержащее, намеренно доступно только для чтения в модели объектов резервного типа. В этом сценарии коллекция или словарь часто создаются и инициализируются (обычно пустыми) логикой инициализации типа-владельца.

См. также