Можно ли использовать константы условной компиляции в 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).
