Платформы WPF и Silverlight используют язык разметки XAML для описания элементов, шаблонов и стилей пользовательского интерфейса.
Если вы разрабатываете одновременно для разных платформ XAML, то естественно возникает желание иметь общие файлы разметки для этих платформ.
Разметка в WPF и Silverlight очень похожа, но есть досадные различия, из-за которых ею очень сложно делиться.
В Наша компания эта проблема была решена несколько лет назад с помощью внутреннего инструмента под названием WPF2SL. WPF2SL слишком специфичен, чтобы быть полезен широкой публике, поэтому мы не планируем его публиковать.
В этой статье я расскажу об особенностях преобразований XSLT применительно к разметке XAML, а также о некоторых проблемах и особенностях, с которыми мы столкнулись.
Проект WPF2SL стартовал 4 года назад, когда мы решили создать линейку компонентов для платформ WPF и Silverlight. Ранее у нас были готовы элементы управления WPF, поэтому возникла идея разделить разметку между платформами.
В то время разрыв между разметкой WPF и Silverlight был больше, чем сейчас, поскольку в Silverlight 3 не было неявных стилей, расширений разметки и сильно ограниченных привязок.
Кстати, некоторые наши конкуренты пошли по другому пути.
У них сначала были готовые элементы управления Silverlight, а их линейка элементов управления WPF была получена из априори урезанной платформы, поэтому они до сих пор не используют в полной мере все возможности платформы WPF. Начнем с создания System.Xml.Xsl.XslCompiledTransform. Здесь все как написано в MSDN. Однако следует помнить, что загрузка XSLT-файла с помощью метода XslCompiledTransform.Load занимает много времени, поскольку в этот момент в памяти будет создана временная сборка для конечного автомата, описанного в XSLT-файле.
В одной из более ранних версий WPF2SL каждый входной файл XAML полностью инициализировался вызовом XslCompiledTransform.Load. Это сильно замедляло работу утилиты.
В XslCompiledTransform загружается XSLT-файл, содержащий описания преобразований узлов и атрибутов исходного дерева.
Преобразования в файле XSLT упорядочены по возрастанию приоритета.
Правило с наименьшим приоритетом — первое.
Это правило копирования.
Если для узла или атрибута не найдено правило с более высоким приоритетом, оно будет скопировано.<xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template>
Отсутствует DynamicResource в Silverlight
Если вы просто замените DynamicResource на StaticResource, полученная разметка будет содержать множество ошибок несовпадения, поскольку StaticResource требует, чтобы ресурс был объявлен, прежде чем его можно будет использовать.Решением было вручную организовать ресурсы внутри файла.
Шаблон XSLT для замены DynamicResource на StaticResource выглядит следующим образом.
<xsl:template match="@*">
…
<xsl:attribute name="{local-name(.
)}" namespace="{namespace-uri(.
)}"> <xsl:variable name="tempValue1"> <xsl:call-template name="globalReplace"> <xsl:with-param name="outputString" select=".
"/>
<xsl:with-param name="target" select="'DynamicResource'"/>
<xsl:with-param name="replacement" select="'StaticResource'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="normalize-space($tempValue1)"/>
</xsl:attribute>
</xsl:template>
Проблема усложняется, когда есть ссылки на ресурсы, объявленные в другом файле.
Эту часть проблемы нельзя было решить с помощью XSLT-преобразований.
Для этого у нас есть отдельный этап постобработки, о котором нужно написать отдельную статью.
Вырезание узлов и атрибутов, отсутствующих в Silverlight
Поскольку разметка WPF намного богаче разметки Silverlight, нам придется вырезать узлы и атрибуты из дерева XAML. Это очень легко сделать в XSLT. Пример вырезания атрибута: <xsl:template match="@FocusVisualStyle"/>
Пример вырезания поддерева:
<xsl:template match="wpf:Style.Triggers"/>
Особенности преобразования ключей ресурса
И в WPF, и в Silvelight вы можете указать ResourceDictionary в разметке XAML, в котором будут храниться ресурсы.Ресурсы доступны по ключу.
В WPF ключом может быть любой объект, но в SL ключ должен быть строкой.
Для унификации, конечно, можно ввести ограничение в WPF, чтобы ключ был только строкой, но нам нравится строгая типизация, которая достижима именно на ключах объекта.
В WPF можно написать так <SolidColorBrush x:Key="{dxt:FloatingContainerThemeKey ResourceKey=FloatingContainerBackground}" Color="#FFA3C3EC" />
Где FloatingContainerThemeKey — это специальный универсальный объект, унаследованный от System.Windows.ResourceKey. Универсальный тип принимает в качестве параметра тип Enum, который описывает возможные имена ключей.
public class FloatingContainerThemeKeyExtension : ThemeKeyExtensionBase<FloatingContainerThemeKey> { }
public enum FloatingContainerThemeKey {
FloatingContainerAdornerTemplate,
FloatingContainerPopupTemplate,
FloatingContainerWindowTemplate,
}
Это затрудняет ошибку в имени ключа в объявлении ресурса или в ссылке на ресурс в WPF.
Вернемся к преобразованию XAML. В Silverlight нет объектных ключей, поэтому <SolidColorBrush x:Key="{dxt:FloatingContainerThemeKey ResourceKey=FloatingContainerBackground}" Color="#FFA3C3EC" />
преобразован в строку <SolidColorBrush x:Key="FloatingContainerThemeKey_FloatingContainerBackground" Color="#FFA3C3EC" />
Пространства имен XML
Многие похожие элементы в WPF и Silverlight расположены в разных пространствах имен xml. Эта разница и привела к появлению этих закономерностей.
< xsl:template match=" wpf:Label ">
< sdk:Label xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk ">
< xsl:apply-templates select="@* | node()"/>
</ sdk:Label >
</ xsl:template >
< xsl:template match=" wpf:TreeView ">
< sdk:TreeView xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk ">
< xsl:apply-templates select="@* | node()"/>
</ sdk:TreeView >
</ xsl:template >
Когда мы поняли, что таких шаблонов придется делать очень много, мы создали своего преемника стандартного класса XmlTextWriter, который перегрузил метод WriteString. public override void WriteString(string text) {
if(NamespacesReplacementTable.ContainsKey(text)) base.WriteString(NamespacesReplacementTable[text]);
else base.WriteString(text);
…
}
Этот наследник может быть передан методу XslCompiledTransform.Transfrom(reader, writer) в качестве второго параметра.
Перегруженный WriteString в соответствии с таблицей замены заменяет пространство имен при записи.
Интеграция в процесс компиляции
WPF2SL — консольное приложение.В наших проектах SL вызов WPF2SL с соответствующими параметрами указывается в событии Pre-build. Но не все так просто, как кажется.
Почти у каждого сейчас есть машины с многоядерными процессорами, на которых msbuild делает одновременную сборку сразу для нескольких проектов.
WPF2SL создавал временные файлы в Temp во время работы.
Поскольку их имена были одинаковыми, возник конфликт доступа.
Проблема решилась добавлением идентификатора процесса к имени файла.
Диагностика проблем при преобразовании XSLT
К сожалению, удобного инструмента для диагностики XSLT-преобразований не существует (по крайней мере, автору о них неизвестно).Если некоторые преобразования XSLT не работают должным образом, наиболее эффективным способом является итеративное изменение XSLT и анализ результатов.
Если результат преобразования сильно отличается от ожидаемого, смело прокомментируйте половину XSLT-файла; если еще не ясно, еще половина и так далее.
Этот метод мы называем: «метод полукомментария».
Описанный метод универсален для всех декларативных языков, включая XAML. Если неясно, какой из шаблонов сгенерировал неверную строку в выходном файле, вы можете временно ввести в шаблон строку, которая позволит однозначно его идентифицировать.
Выводы
Преобразования XSLT хорошо подходят для задачи преобразования разметки XAML между различными платформами, а реализация преобразований XSLT в .NET, XslCompiledTransform, является достаточно гибкой, производительной и расширяемой.
Литература
Сал Мангано.XSLT. Сборник рецептов
Теги: #.NET #XAML #xslt #wpf #silverlight #разработка веб-сайтов #.
NET
-
Повышение Iq Бесплатные Онлайн Газы
19 Oct, 24 -
Тибетский Язык
19 Oct, 24 -
Как Мы Гасили Байку Техподдержки
19 Oct, 24 -
Настройка И Масштабирование Postgresql
19 Oct, 24 -
Il2Cpp: Интеграция Сборщика Мусора
19 Oct, 24