Можно ли использовать константы условной компиляции в XAML
Мы рассматривали такое мощное средство Visual Studio как константы условной компиляции. Это средство позволяет, в зависимости от значения заданной константы, включать и исключать из исполняемого файла те или иные участки кода. А существует ли аналогичная возможность для разметки XAML?
Представьте, например, такой вариант:
#If DEBUG Then <TextBlock Text="Это сборка Debug" /> #Else <TextBlock Text="Это сборка Release" /> #End If
Это было бы удобно и здорово! Но, к сожалению, XAML не позволяет использовать такой вариант. Придётся немного схитрить.
Давайте рассмотрим такую задачу. Допустим, мы написали приложение, которое имеет две редакции: одна – для загрузки в интернет и общего доступа, а вторая – для личного использования. Пусти эти версии различаются несколькими элементами управления, видимость которых и будет определяться этой константой компиляции. Определим константу типа Boolean и назовём её WEBSITE. Присвоим ей значение True для сетевой версии, и False – для локальной:
WEBSITE=True
Воспользуемся таким понятием XAML, как расширение разметки (markup extension). Напишем класс расширения, который будет наследоваться от класса MarkupExtension. Этот класс будет иметь два свойства и один метод – ProvideValue(), возвращающий значение, которое мы будем далее использовать в своей разметке XAML. Возвращаемое значение будет зависеть от константы условной компиляции:
Imports System.Windows.Markup Namespace ConditionalXAML ''' <summary> ''' Расширение XAML для изменения разметки в зависимости от констант компиляции. ''' </summary> Public Class Condition Inherits MarkupExtension Public Property Website As New Object Public Property Local As New Object Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object #If WEBSITE Then Return Website #Else Return Local #End If End Function End Class End Namespace
В файле разметки, в разделе импорта, импортируем пространство имён, содержащее наш класс Condition():
xmlns:Conditional="clr-namespace:RootNamespace.ConditionalXAML"
Здесь RootNamespace – это пространство имён вашего приложения.
Далее в коде XAML файла, в тех местах, где где нужно использовать разметку, зависящую от констант компиляции, мы напишем следующий код:
<Conditional:Condition> <Conditional:Condition.Website> <TextBlock Text="Hello Web" /> </Conditional:Condition.Website> <Conditional:Condition.Local> <TextBlock Text="Hello Local" /> </Conditional:Condition.Local> </Conditional:Condition>
Естественно, вместо элементов TextBlock из этого примера вы вставите свою разметку.
Примечание 1: контейнер для вставки
Вставлять блоки со своей «условной разметкой» желательно в элементы, являющиеся специализированными списками, наследуемыми от класса ItemsControl(). Это базовый класс для элементов управления, которые отображают коллекции каких-либо объектов. Иначе получим предупреждение «Невозможно добавить экземпляр типа "Condition" в семейство типа "UIElementCollection". Допускаются только элементы типа "UIElement"»:
Такой проект скомпилируется и будет работать, несмотря на предупреждение компилятора. Но корректным вариантом будет, например, такой:
Как видно, мы вставили блок с той же самой разметкой, но поместили его внутрь контейнера ViewBox.
Визуальными элементами для отображения коллекций, являются, например, ListBox, TreeView, ViewBox, Menu и другие.
Примечание 2: использование всех вариантов константы условной компиляции
Предположим, мы хотим исключить в сетевой версии какие-то элементы управления. Тогда в блоке Condition.Local никакой разметки вроде бы не надо.
Однако даже если в Condition.Local никакой разметки нет, необходимо использовать оба условия – и Condition.Website, и Condition.Local. В противном случае в том месте, куда вставлен блок кода с условием, пропущенное условие будет отображено в GUI как визуальный элемент с текстом System.Object.ToString() (ведь наш класс ConditionalXAML.Condition возвратит объект Local в любом случае).
Чтобы этого не произошло, нужно вставить внутрь объекта Conditional:.Condition.Local какой-то пустой контейнер или блок текста со значением String.Empty() (на рисунке выше мы использовали пустой контейнер StackPanel).