Темы

153

Учитывая удобство стилей CSS, тот факт, что разработчикам может требоваться что-то еще, может вызывать удивление. Дело в том, что правила CSS ограничены фиксированным набором атрибутов стилей. Они позволяют повторно использовать специфические нюансы форматирования (шрифты, границы, цвета переднего плана и фона и т.д.), но явно не способны контролировать другие аспекты элементов управления ASP.NET.

Например, CheckBoxList имеет свойства, которые управляют организацией элементов в виде строк и столбцов. Хотя эти свойства влияют на внешний вид элемента управления, они находятся вне области действия CSS, поэтому их придется устанавливать вручную. Кроме того, вместе с форматированием может требоваться определить нужные аспекты поведения элемента управления. Например, вам понадобится стандартизировать режим выбора элемента управления Calendar или перенос текста в окне TextBox. Понятно, что решить такие задачи с помощью CSS не получится.

Темы (theme) заполняют этот пробел. Как и CSS, темы позволяют определять набор атрибутов стилей, которые можно применять к элементам управления во многих страницах. Однако, в отличие от CSS, темы не реализуются браузером. Напротив, они являются встроенным решением ASP.NET, реализуемым на сервере. Хотя темы не заменяют стили, они обладают рядом возможностей, которые CSS не в состоянии предоставить. Основные различия описаны ниже:

Сказать, что темы заменяют CSS, было бы преувеличением. Напротив, темы представляют модель более высокого уровня. Для реализации указанных свойств форматирования ASP.NET будет часто генерировать встроенные правила стилей. Кроме того, если посчастливилось создать превосходную таблицу стилей, ее по-прежнему можно использовать. Вы сами должны решить, будете ли вы использовать одно или одновременно оба решения. Как будет показано далее, таблицу стилей CSS можно применять в качестве составной части темы.

Папки тем и обложки

Все темы специфичны для приложений. Чтобы использовать тему в веб-приложении, нужно создать папку, которая определяет данную тему. Эта папка должна быть помещена в папку App_Themes, размещенную внутри каталога верхнего уровня веб-приложения. Иначе говоря, веб-приложение по имени Super Commerce могло бы содержать тему FunkyTheme в папке SuperCommerce\App_Theme\FunkyTheme.

Приложение может содержать определения нескольких тем при условии, что каждая тема будет храниться в отдельной папке. На отдельно взятой странице одновременно может быть активной только одна тема. Динамическое изменение активной темы во время обработки страницы описано в разделе "Динамическое применение тем".

Чтобы тема действительно могла выполнять какие-либо действия, понадобится создать хотя бы один файл обложки (skin) в папке темы. Файл обложки представляет собой текстовый файл с расширением .skin. ASP.NET никогда не работает с файлами обложек напрямую - они используются "за кулисами" для определения темы.

По сути, файл обложки представляет собой список дескрипторов элементов управления, но с одной характерной особенностью. Дескрипторы элементов управления в файле обложки не обязательно должны определять полностью элемент управления. Вместо этого они должны устанавливать только те свойства, которые требуется стандартизировать. Например, если требуется применить единообразную цветовую схему, то интерес могут представлять только такие свойства, как ForeColor и BackColor. При добавлении дескриптора элемента управления ListBox, он может выглядеть следующим образом:

<asp:ListBox runat="server" ForeColor="White" BackColor="Orange" />

Часть runat="server" является обязательной, а все остальное - нет. Атрибут id для темы неприменим, т.к. он необходим для уникальной идентификации каждого элемента управления в реальной веб-странице.

Вы должны самостоятельно принять решение - создавать множество файлов обложек или же помещать все дескрипторы элементов управления в один такой файл. Оба подхода эквивалентны, поскольку ASP.NET обрабатывает все файлы обложек в каталоге темы как часть одного и того же определения темы. Часто дескрипторы сложных элементов управления (таких как элементы управления данными) целесообразно разделять на отдельные файлы обложек. Взаимосвязь между темами и обложками подробно показана на рисунке ниже:

Темы и обложки

ASP.NET поддерживает также глобальные темы. Глобальными являются темы, помещаемые в каталог c:\Inetpub\wwwroot\aspnet_client\system_web\[Версия]\Themes (здесь предполагается, что c:\Inetpub\wwwroot является корневым каталогом веб-сервера IIS по умолчанию). Тем не менее, рекомендуется использовать локальные темы, даже если требуется создать несколько веб-сайтов с одной и той же темой. Применение локальных тем облегчает развертывание веб-приложения и обеспечивает гибкость при внесении специфичных для того или иного сайта особенностей в будущем.

При наличии локальной темы, имя которой совпадает с именем глобальной темы, предпочтение отдается локальной теме, а глобальная тема игнорируется. Темы не объединяются друг с другом.

Применение простой темы

Чтобы добавить тему в проект, выберите пункт меню Website --> Add New item, а затем элемент Skin File (Файл обложки). Visual Studio предупредит о том, что файлы обложек должны быть помещены в подпапку App_Themes, и запросит, требуется ли сделать это. При выборе ответа "Yes" среда Visual Studio создаст папку с тем же именем, что и у файла темы. После этого папке и файлу можно назначить любые подходящие имена. Пример темы, содержащей единственный файл обложки, показан на рисунке ниже:

Тема в окне Solution Explorer

Visual Studio не поддерживает создание тем во время проектирования, поэтому копировать и вставлять дескрипторы элементов управления из других веб-страниц придется вручную. Ниже приведен пример обложки, которая устанавливает цвета изображения и фона для нескольких обычно используемых элементов управления:

<asp:ListBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:TextBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:Button runat="server" ForeColor="White" BackColor="Orange" />

Чтобы применить тему к веб-странице, атрибуту Theme директивы Page нужно присвоить имя папки своей темы. (ASP.NET автоматически просмотрит все файлы обложек в этой теме.)

<%@ Page Language="C#" ... Theme="FunkyTheme"  %>

Это изменение можно выполнить вручную, а можно выбрать объект DOCUMENT в окне Properties (Свойства) во время проектирования и установить свойство Theme (которое предоставляет удобный раскрывающийся список всех тем данного веб-приложения). Visual Studio соответствующим образом модифицирует директиву Page.

При применении темы к странице ASP.NET просматривает каждый элемент управления на этой странице и проверяет файлы обложек на предмет того, определяют ли они какие-то свойства данного элемента управления. При обнаружении совпадающего дескриптора в файле обложки информация из файла обложки заменяет текущие свойства элемента управления.

Результаты применения темы FunkyTheme к простой странице представлены на рисунке ниже. На первом рисунке показана страница Default.aspx в ее исходном состоянии, без темы. На втором рисунке изображена та же страница с примененной темой FunkyTheme. Все настройки темы FunkyTheme применяются к элементам управления в файле Default.aspx, даже если они замещают значения, которые явным образом заданы на странице (например, цвет фона окна списка). Однако те настройки, которые были установлены в исходной странице и не конфликтуют с темой (например, нестандартный шрифт для кнопок), остаются без изменений:

<body>    
    <form id="form1" runat="server">
        <div>
            <asp:TextBox ID="TextBox1" runat="server">Test</asp:TextBox>  <br />
            <br />
            <asp:ListBox ID="ListBox1" runat="server" BackColor="#FFFFC0" Width="154px">
                <asp:ListItem>Test</asp:ListItem>
            </asp:ListBox>
            <br /><br /><br />
            <asp:Button ID="Button1" runat="server" Text="OK" Font-Bold="False" 
            	Font-Names="Futura Hv BT" Font-Size="Large" Width="69px" />
            <asp:Button ID="Button2" runat="server" Text="Отмена" 
            	Font-Names="Futura Hv BT" Font-Size="Large" Width="83px" /><br />
        </div>
    </form>
</body>
Простая страница до и после применения темы

Обработка конфликтов темы

Как было показано, при возникновении конфликта между свойствами элементов управления и темы предпочтение отдается теме. Однако в некоторых случаях может потребоваться изменить это поведение, чтобы элементы управления могли уточнять тему, замещая определенные ее нюансы. ASP.NET предоставляет такую возможность, хотя и по принципу "все или ничего" - произведенное изменение применяется ко всем элементам управления целой страницы.

Чтобы внести это изменение, достаточно в директиве Page вместо атрибута Theme использовать атрибут StyleSheetTheme. (Настройка StyleSheetTheme работает подобно CSS.) Например:

<%@ Page Language="C#" ... StyleSheetTheme="FunkyTheme"  %>

Теперь нестандартный желтый цвет фона окна списка List Box получает преимущество над цветом фона, указанным в теме. Результат - и потенциальную проблему - можно видеть на рисунке ниже. Поскольку цвет изображения был изменен на белый, чтение стало затруднительным. Перекрытие спецификаций форматирования может вызвать затруднения подобного рода, поэтому часто лучше с помощью атрибута Theme предоставить темам всю полноту управления:

 Предоставление дескриптору элемента управления приоритета над темой

Атрибуты Theme и StyleSheetTheme можно использовать одновременно, чтобы одни настройки (те, которые определены в атрибуте Theme) применялись всегда, а другие (определенные в атрибуте StyleSheetTheme) - только в том случае, если они не были определены в элементе управления. В зависимости от персональных предпочтений (и уровня комфортности при использовании тем и стилей), этот подход может представляться чрезвычайно запутанным, либо служить удобным способом разделения настроек, которые желательно активизировать принудительно (Theme) и настроек, которые необходимо использовать по умолчанию (StyleSheetTheme).

Еще один вариант - конфигурирование отдельных элементов управления таким образом, чтобы полностью их исключить из процесса формирования темы. Для этого потребуется просто присвоить свойству EnableTheming элемента управления значение false. ASP.NET применит тему к остальным элементам управления на странице, но пропустит сконфигурированный вами элемент управления:

<asp:Button ID="Button1" runat="server" Text="OK" EnableTheming="false" ... />

Создание множества обложек для одного элемента управления

Ограничение всех элементов управления единым форматом - прекрасный способ стандартизации, но этот подход недостаточно гибок для реальных приложений. Например, может существовать несколько типов текстовых полей, которые различаются по месту их использования или по типу содержащихся в них данных. Вероятность наличия различных вариантов надписей еще выше - они могут применяться как в заголовках, так и в основном тексте. К счастью, ASP.NET позволяет создавать несколько объявлений для одного и того же элемента управления.

Обычно при создании более одной темы для одного элемента управления ASP.NET выведет сообщение об ошибке сборки, в котором будет сказано, что для каждого элемента управления может существовать только одна обложка по умолчанию. Чтобы обойти эту проблему, необходимо создать именованную обложку, указав атрибут SkinID. Например:

<asp:ListBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:TextBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:Button runat="server" ForeColor="White" BackColor="Orange" />
<asp:TextBox runat="server" ForeColor="White" BackColor="Green" 
    Font-Bold="True" SkinID="Dramatic" />
<asp:Button runat="server" ForeColor="White" BackColor="Green" 
    Font-Bold="True" SkinID="Dramatic"/>

Дело в том, что именованные обложки не применяются автоматически как обложки по умолчанию. Чтобы использовать именованную обложку, на веб-странице понадобится установить атрибут SkinID элемента управления. Это значение можно выбрать из раскрывающегося списка, который Visual Studio создает на основе имен всех определенных обложек, либо ввести вручную:

<asp:Button ID="Button1" runat="server" Text="OK" SkinID="Dramatic" ... />

Если навязываемая модель применения тем не устраивает, все свои обложки можно сделать именованными. В этом случае они никогда не будут применяться, если не установлен атрибут SkinID элемента управления.

Использование именованных тем аналогично применению правил CSS, основанных на имени класса. Правила классов CSS применимы только в том случае, если задан атрибут класса соответствующего HTML-дескриптора.

Среда ASP.NET достаточно интеллектуальна, чтобы выявлять попытки использования имени несуществующей обложки, выводя предупреждающее сообщение. После этого элемент управления будет вести себя так, словно параметру EnableTheming было присвоено значение false, в результате чего соответствующая обложка по умолчанию будет проигнорирована.

Значение атрибута SkinID не обязательно должно быть абсолютно уникальным, Оно должно быть уникальным только для каждого элемента управления. Например, предположим, что требуется создать альтернативный набор элементов управления с использованием обложек, для которых будет выбран шрифт чуть меньшего размера. Эти элементы управления соответствуют общей теме, но они будут полезны на тех страницах, которые отображают большой объем информации. В этом случае можно создать новые элементы управления Button, TextBox и Label и назначить каждому из них одно и то же имя обложки (например, Smaller).

Обложки с шаблонами и изображениями

До сих пор в примерах тем использовались довольно простые свойства. Однако в файле обложки можно создать более подробные дескрипторы элементов управления. Большинство свойств элементов управления поддерживают темы. Если свойство нельзя объявить в теме, то при попытке запуска приложения возникнет ошибка сборки.

Например, многие элементы управления поддерживают стили, которые указывают диапазон применения информации о форматировании. Одним из примеров являются элементы управления данными, а другим - элемент управления Calendar (календарь). Ниже показано, как стили элемента управления Calendar можно определить в файле обложки, чтобы они соответствовали теме:

<asp:Calendar runat="server" BackColor="White" BorderColor="Black" BorderStyle="Solid" 
    CellSpacing="1" Font-Names="Verdana" Font-Size="9pt" ForeColor="Black" Height="250px" 
    Width="500px" NextPrevFormat="ShortMonth" SelectionMode="Day">
            <SelectedDayStyle BackColor="DarkOrange" ForeColor="White" />
            <DayStyle BackColor="Orange" Font-Bold="True" ForeColor="White" />
            <NextPrevStyle Font-Bold="True" Font-Size="8pt" ForeColor="White" />
            <DayHeaderStyle Font-Bold="True" Font-Size="8pt" ForeColor="#333333" Height="8pt" />
            <TitleStyle BackColor="#12cd6f" BorderStyle="None" Font-Bold="True" Font-Size="12pt"
                ForeColor="White" Height="12pt" />
            <OtherMonthDayStyle BackColor="NavajoWhite" Font-Bold="False" ForeColor="DarkGray" />
 </asp:Calendar>

Эта обложка определяет шрифт, цвета и стили элемента управления Calendar. Оно устанавливает также режим выбора, форматирование ссылок навигации по месяцам и общий размер календаря. В результате, чтобы использовать этот отформатированный календарь, понадобится только следующий простой дескриптор:

<asp:Calendar ID="Calendar2" runat="server" />

Обычный вид календаря Calendar и его вид, когда страница использует соответствующую тему, показаны на рисунке ниже:

Неформатированный календарь Calendar на странице без темы и с темой

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

Еще один действенный подход - повторное использование изображений, когда они делаются частью темы. Предположим, что на веб-сайте нужно использовать одно изображение для кнопок ОК, а другое - для всех кнопок Cancel (Отмена). Для реализации такого дизайна первым делом понадобится добавить изображения в папку темы. Для лучшей организации целесообразно создать одну или более подпапок специально для хранения изображений. На рисунке ниже видно, что изображения хранятся в папке с именем ButtonImages:

Добавление изображений к теме

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

<asp:ImageButton runat="server" SkinID="OKButton"
    ImageUrl="ButtonImages/buttonOK.jpg" />
 
<asp:ImageButton runat="server" SkinID="CancelButton"
    ImageUrl="ButtonImages/buttonCancel.jpg" />

При добавлении ссылки на изображение в файл обложки всегда проверяйте, что URL-адрес изображения указан относительно папки темы, а не папки, в которой хранится страница. Когда эта тема будет применяться к элементу управления, ASP.NET автоматически вставит часть Themes\Имя_Темы в начало URL-адреса.

Теперь, чтобы применить эти изображения, достаточно создать на своей веб-странице элемент управления ImageButton, который будет ссылаться на соответствующее имя обложки:

<asp:ImageButton ID="Button1" runat="server" SkinID="OKButton" />
<asp:ImageButton ID="Button2" runat="server" SkinID="CancelButton" />

Эту же методику можно применить для создания обложек для других элементов управления, использующих изображения. Например, можно стандартизировать изображения узлов в элементе управления TreeView, изображение маркера в элементе управления BulletList или значки в элементе управления DataGridView.

Использование каскадной таблицы стилей CSS в теме

ASP.NET позволяет также использовать таблицу стилей в качестве части темы. Потребность в этой возможности может возникнуть по нескольким причинам:

Для использования в теме таблица стилей сначала должна быть добавлена в папку темы. ASP.NET произведет поиск файлов .css в этой папке и динамически свяжет их с любой страницей, на которой используется эта тема.

Однако при этом необходимо учитывать один нюанс. Чтобы связать страницу с таблицей стилей, среда ASP.NET должна иметь возможность вставить дескриптор <link> в раздел <head> веб-страницы. Это возможно только в том случае, если дескриптор <head> имеет атрибут runat="server" (веб-страницы, сгенерированные с помощью Visual Studio, получают этот атрибут по умолчанию).

Как только это будет сделано, для получения доступа к правилам таблицы стилей достаточно установить атрибут Theme страницы. Затем можно установить свойство CssClass элементов управления, которые требуется форматировать, как было показано в предыдущей статье. Любые правила стилей, связанные непосредственно с HTML-дескрипторами, применяются автоматически.

В теме можно иметь столько таблиц стилей, сколько требуется. ASP.NET добавит несколько дескрипторов <link> - по одному для каждой таблицы стилей, используемой в теме.

Применение тем через конфигурационный файл

Используя директиву Page, тему можно связать с отдельной страницей. Однако может выясниться, что тема подходит для оформления всего веб-приложения. Простейший способ применения такой темы - конфигурирование элемента <pages> в файле web.config приложения, как показано в следующем примере:

<configuration>
  <system.web>
    <pages theme="FunkyTheme"/>
  </system.web>
</configuration>

Если нужно использовать поведение таблицы стилей, чтобы тема не замещала конфликтующие свойства элементов управления, вместо атрибута theme должен быть установлен атрибут styleSheetTheme:

<configuration>
  <system.web>
    <pages styleSheetTheme="FunkyTheme"/>
  </system.web>
</configuration>

В любом случае, при указании темы в файле web.config эта тема будет применяться ко всем страницам веб-сайта, если только эти страницы не обладают собственными настройками темы. Если страница определяет атрибут Theme или StyleSheetTheme, то настройка страницы будет иметь преимущество перед настройкой web.config.

Используя эту методику, тему с такой же легкостью можно применить к части веб-приложения. Например, можно создать отдельный файл web.config для каждой подпапки и использовать параметр <pages> для конфигурирования различных тем.

Когда темы применяются через конфигурационный файл, их по-прежнему можно отключать для конкретных страниц. Для этого достаточно вставить атрибут EnableTheming в директиву Page и установить его в false. Ни одна из тем не будет применена к этой странице.

Динамическое применение тем

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

Этот прием необыкновенно прост. Все, что понадобится сделать - это динамически установить свойство Page.Theme или Page.StyleSheet в коде. Фокус в том, что этот шаг должен быть выполнен во время генерации события Page.PreInit. После него попытка задания этого свойства вызовет исключение.

Ниже приведен пример, в котором динамическая тема применяется за счет чтения имени темы из текущей коллекции Session:

protected void Page_PreInit(object sender, EventArgs e)
{
        if (Session["Theme"] == null)
        {
            // Тема не выбрана. Выберите тему по умолчанию (или 
            // укажите пустую строку, чтобы ни одна тема 
            // не использовалась)
            Page.Theme = "DefaultTheme";
        }
        else
        {
            Page.Theme = (string)Session["Theme"];
        }
}

Разумеется, выбранную тему можно сохранить в cookie-наборе, состоянии сеанса, профиле или в любом другом месте, специфичном для пользователя.

Если требуется создать страницу, которая позволит пользователям выбирать тему, придется прибегнуть к одному ухищрению. Дело в том, что выбор, произведенный пользователем, нельзя прочитать до окончания загрузки страницы и завершения этапа PreInit. Однако на этом этапе устанавливать тему уже слишком поздно.

Одним из способов решения этой проблемы является обновление страницы, при котором страница переадресуется к себе самой. Наиболее эффективный прием - воспользоваться методом Server.Transfer(), чтобы вся обработка происходила на сервере. (Метод Response.Redirect() отправляет заголовок переадресации клиенту и поэтому требует дополнительного кругового обмена.) Этот прием будет продемонстрирован в следующем примере.

Возможны и другие подходы, но, пожалуй, наилучшее практическое решение заключается в том, чтобы пользователи осуществляли выбор темы на отдельной странице (или во всплывающем окне, как сделано на этом сайте). Результат выбора темы можно хранить в cookie-наборе, состоянии сеанса или в хранилище другого типа. Тогда выбранная тема всегда будет доступна обработчику событий Page.PreInit на других страницах.

Ниже приведен код, который сначала представляет список элементов выбора во время загрузки страницы, а затем регистрирует выбранный элемент и пересылает страницу после щелчка на кнопке:

<asp:ListBox ID="lstThemes" runat="server" />
<br /><br />
<asp:Button ID="cmdApply" runat="server" Text="Поменять тему" OnClick="cmdApply_Click" />
using System.IO;
// ...

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, System.EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            // Заполнить список доступными темами, прочитав папки внутри App_Themes
            DirectoryInfo themeDir = new DirectoryInfo(Server.MapPath("App_Themes"));
            lstThemes.DataTextField = "Name";
            lstThemes.DataSource = themeDir.GetDirectories();
            lstThemes.DataBind();
        }
    }

    protected void cmdApply_Click(object sender, EventArgs e)
    {
        // Установить выбранную тему
        Session["Theme"] = lstThemes.SelectedValue;

        // Обновить страницу
        Server.Transfer(Request.FilePath);
    }

    protected void Page_PreInit(object sender, EventArgs e)
    {
        if (Session["Theme"] == null)
        {
            // Тема не выбрана
            Page.Theme = "";
        }
        else
        {
            Page.Theme = (string)Session["Theme"];
        }
    }
}

Имейте в виду, что для действительного применения выбранной темы к странице по-прежнему необходим обработчик события Page.PreInit. Результат показан на рисунке ниже:

Динамический выбор тем в ASP.NET

Если вы используете именованные обложки, атрибут SkinID элемента управления можно установить декларативно во время проектирования страницы или указать динамически в своем коде.

При использовании именованных обложек нужно следить за тем, чтобы каждая тема применяла одни и те же имена и обеспечивала дескрипторы для одних и тех же элементов управления. Если элемент управления определяет атрибут SkinID, a ASP.NET не может найти соответствующую обложку для данного элемента управления в теме, тема не будет применена к элементу управления, и он сохранит текущее форматирование.

Пройди тесты
Лучший чат для C# программистов