Средства форматирования страниц
Использование внешних CSS в теме
Если у нас имеются хорошие наработки CSS, то ASP.NET позволяет использовать их для форматирования HTML-дескрипторов активной страницы. Чтобы подключить CSS к активной странице, нужно сделать следующее:
- Скопировать файл внешней CSS в каталог темы
- Включить тему в свойстве Theme объекта DOCUMENT страницы
- Если CSS содержит классы, устанавливать имена нужных классов в свойстве CssClass элементов управления страницы
- Вместо дескриптора <link>, используемого при традиционном способе подключения к странице, проследить, чтобы заголовочный дескриптор страницы был объявлен как серверный ( <head runat="server"> ). В этом случае ASP.NET сама сгенерирует столько дескрипторов <link> в выходном HTML-коде, сколько файлов CSS будет находиться в подключенной к странице теме
Проверим это на примере. В панели Solution Explorer оболочки выполните следующие действия:
- Скопируйте файл TestCSS.aspx и назначьте копии имя ThemeCSS.aspx и назначьте новую страницу стартовой
- Скопируйте таблицу стилей ExternalCSS.css и назначьте копии имя ThemeCSS.css
- Переместите файл ThemeCSS.css в папку MyTheme проекта
- Удерживая клавишу Ctrl, выделите файлы BackgroundImage.bmp и MyWeb.jpg в корне виртуального каталога и выполните для них команду Copy контекстного меню
- Щелкните правой кнопкой мыши на узле MyTheme и выполните команду Paste, чтобы рисунки оказались в одном каталоге с таблицей ThemeCSS.css
- Откройте страницу ThemeCSS.aspx на редактирование, удалите в ней дескриптор <link> подключения внешней таблицы стилей. Удостоверьтесь, что заголовочный дескриптор <head> является серверным
- Исполните страницу и убедитесь, что стили пока не действуют, потому что не подключены
Теперь только осталось подключить тему MyTheme к странице.
- В панели Properties выделите объект DOCUMENT и установите его свойство Theme в значение MyTheme
- Выполните страницу ThemeCSS.aspx и убедитесь, что стили из внешней таблицы ThemeCSS.css работают
- Назначьте страницу TestCSS.aspx, где мы CSS использовали напрямую, стартовой и выполните ее
Чтобы сделать некоторые выводы, сравним полученные результаты, приведенные ниже в уменьшенном виде.
Выводы:
- На левом рисунке подключена только одна CSS, которая стилизует дескрипторы HTML на стороне броузера, и то, только те, которые не имеют внутренних стилей.
- На правом рисунке CSS и оформления темы действуют вместе. Система ASP.NET на стороне сервера трасформирует серверные элементы управления в HTML-дескрипторы с уже готовыми внутренними стилями, согласно оформлениям темы. На заключительном этапе, перед отсылкой сформированной HTML-страницы на броузер, она подключает дескриптором <link> ссылку на внешнюю CSS. Броузер, получив ссылку на внешнюю CSS, проверяет в соответствии с правилом приоритета стилей возможность их применения только в тех дескрипторах HTML, в которых нет своих внутренних стилей, сгенерированных на сервере.
Теперь осталось проверить тот факт, что ASP.NET подключает к странице все таблицы стилей, помещенные в тему.
- В каталоге Theme сделайте еще три копии файла ThemeCSS.css с именем ThemeCSS1.css, ThemeCSS2.css, ThemeCSS3.css
- Назначьте страницу ThemeCSS.aspx стартовой и выполните ее
- На появившемся окне броузера выполните команду контекстного меню View Source для присланной с сервера статической страницы
Исследуем блок кода заголовочного дескриптора <head>. Он будет содержать такие строки
<head> <title>Документ со встроенной CSS</title> <meta content="text/html; charset=windows-1251" http-equiv="Content-Type" /> <link href="App_Themes/MyTheme/ThemeCSS.css" type="text/css" rel="stylesheet" /> <link href="App_Themes/MyTheme/ThemeCSS1.css" type="text/css" rel="stylesheet" /> <link href="App_Themes/MyTheme/ThemeCSS2.css" type="text/css" rel="stylesheet" /> <link href="App_Themes/MyTheme/ThemeCSS3.css" type="text/css" rel="stylesheet" /> </head>
Мы видим, что среда исполнения ASP.NET автоматически вставляет в код столько дескрипторов подключения CSS, сколько файлов с таблицами находится в каталоге темы. Обратите внимание, что пути в ссылках прописываются относительно корня виртуального каталога, в котором находится сама страница. Ну и последнее, строки подключения CSS в HTML-коде прописываются системой ASP.NET, ну совсем как по теории.
- Удалите лишние файлы ThemeCSS1.css, ThemeCSS2.css и ThemeCSS3.css, чтобы они дальше не мельтешили у нас в глазах
Применение общей темы в конфигурационном файле
До сих пор мы спокойно удаляли файл web.config приложения с уверенностью, что он будет вновь создан оболочкой. Тем более, что там содержалась только заготовка кода. Теперь пришла пора редактировать этот файл и удалять его уже будет жалко.
Ранее мы говорили, что при подключении к странице темы (в наших примерах - MyTheme ) через свойство Theme объекта DOCUMENT оболочка автоматически вставляет соответствующий атрибут в директиву @Page этой страницы, тем самым подключая к странице индивидуальную тему.
Например
<% @Page Language="C#" Theme="MyTheme" %>
Обычно проектировщики ратуют за одинаковое стилевое оформление страниц и используют в каждой из них одну и ту же тему. В этом случае удобнее будет не подключать каждую страницу к одной и той же теме, а указать общую тему в конфигурационном файле, которая будет использоваться по умолчанию многими страницами, когда в них не подключена индивидуальная тема. Для этого нужно вставить в конфигурационный файл текущего каталога Web-сайта соответствующее указание для ASP.NET
- Откройте страницы наших примеров, использующих темы, а именно: TestTheme.aspx, TestCalendar.aspx, ThemeCSS.aspx, и удалите в них из директивы @Page атрибут Theme="MyTheme"
- Назначая каждую страницу стартовой, исполните их по очереди, чтобы убедиться, что отключенная тема в них не действует. Пока получилась грустная, унылая картина
- Откройте на редактирование заготовку файла web.config, автоматически созданную оболочкой
- Отредактируйте код файла web.config, добавив всего один дескриптор с указанием общей темы <pages theme="MyTheme" />, и сохраните изменения на диске
Ниже приведен окончательный код файла web.config с указанием общей темы по умолчанию, в котором убраны громоздкие комментарии и пустые дескрипторы
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true"/> <authentication mode="Windows"/> <pages theme="MyTheme" /> </system.web> </configuration>
- Назначая каждую из перечисленных выше страниц стартовой, вновь исполните их по очереди, чтобы убедиться, что тема опять заработала так, как будто-бы она была индивидуально подключена к каждой странице
Если нужно сделать так, чтобы общая тема действовала по умолчанию на множестве страниц, но не перезаписывала на них индивидуальные стилевые настройки элементов управления, то конфигурационный файл должен быть таким
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true"/> <authentication mode="Windows"/> <pages styleSheetTheme="MyTheme" /> </system.web> </configuration>
Если на какой-нибудь странице нужно сделать так, чтобы общая тема не действовала на нее по умолчанию, то есть два варианта:
- Установить для страницы индивидуальную тему, как это мы делали раньше, вставив в директиву @Page атрибут Theme="Другая_тема"
- Вообще отключить тему, чтобы действовали индивидуальные стиливые свойства элементов управления, назначенные нами или установленные по умолчанию библиотечными значениями. Для этого в директиву @Page страницы нужно вставить атрибут EnableTheming="false"
Во всех наших примерах мы создавали активные страницы в корневом виртуальном каталоге сайта. Но для сайта с тысячами страниц это неприемлемый способ. Страницы могут группироваться (брыкаться, кусаться, кричать "Я пойду жаловаться в обком!") по любому признаку, например тематическому, и размещаться в отдельных папках развитой древовидной системы виртуальных каталогов. Корнем этого дерева должен быть виртуальный каталог сайта, его еще называют корнем Web-дерева.
В развитом Web-дереве применение общей темы по умолчанию является удобным способом стилизации страниц. В каждом каталоге можно разместить отдельный файл web.config со своей общей темой и все страницы этого каталога будут использовать эту тему по умолчанию.
Применение тем по выбору пользователя
До сих пор мы рассматривали случай, когда разработчик сам жестко привязывает тему к странице или группе страниц. Но иногда (для крутизны) может возникнуть необходимость поручить выбор темы для конкретной страницы пользователю с немедленным изменением ее стилевого оформления. При этом неважно, была ли включена на странице индивидуальная тема или по умолчанию применялась тема конфигурационного файла.
Все, что нужно сделать, это предусмотреть на странице интерфейс выбора темы, а в коде страницы динамически присвоить свойству Page.Theme или Page.StyleSheetTheme выбранное пользователем значение. При этом важно понимать, что тема настраивается перед инициализацией страницы, поэтому соответствующий код нужно поместить в обработчик Page_PreInit(). Если это сделать позже, в обработчиках других событий, то попытка присвоить новое значение свойству Page.Theme или Page.StyleSheetTheme вызовет ошибку компиляции.
С другой стороны, прочитать выбор пользователя можно только после создания страницы, когда присваивать свойству темы новое значение уже поздно. Поэтому динамическое задание темы нужно выполнять в два этапа:
- Прочитать выбор пользователя, когда это станет возможным после полной загрузки страницы
- Принудительно перезапустить страницу на сервере, чтобы в обработчике Page_PreInit() назначить новую тему
При перезапуске страницы она должна быть переадресована на саму себя. Такую операцию можно выполнить либо методом Response.Redirect(), либо методом Server.Transfer(). Первый метод реализует полный цикл с отправкой на броузер и автоматическим возвращением на сервер. Он тратит лишнее время на пересылку. Второй метод перезапускает страницу сразу на сервере без цикла через клиента. Но есть одна существенная особенность: оба метода сбрасывают состояние вида всех элементов управления и запускают страницу как в при первом запросе. Поскольку выбирать не из чего, то мы выбираем Pepsi и второй метод Server.Transfer()!
Рассмотрим все это на примере. Прежде всего, нужно создать несколько тем, чтобы было из чего пользователю выбирать.
- В панели Sulution Explorer вызовите контекстное меню для папки MyTheme и выполните команду Copy
- В панели Sulution Explorer вызовите контекстное меню для папки App_Themes, где должны храниться все темы приложения, и выполните на ней команду Paste два раза, чтобы создать еще две темы
- Присвойте копиям такие замысловатые имена, какие давали нам в глубоком детстве на улице у родного батюшки, например, MyTheme1 и MyTheme2
Обратите внимание, что новые темы имеют старое содержание, которое было бы желательно подправить, чтобы наглядно видеть влияние выбранной темы на оформление страницы. Мы это сделаем попозже и помягше - об чем должна болеть голова, прежде всего, у нашего дизайнера ( за што ему, акаянному, только деньги плотють ). Наше дело - режь, клей и запускай. Студент Зиборов! - и... чтобы еще работало!
Учитывая, что протокол HTTP не поддерживает сохранение состояний и что страница существует только на время обработки обратной отсылки, выбранную пользователем тему нужно где-то сохранять. Это могут быть cookie -наборы ( куки -файлы) на компьютере клиента, которые мы туда запишем без его ведома, если он забыл выключить их поддержку. Это могут быть скрытые поля состояния вида ( VIEWSTATE ), которые мы будем пересылать туда-сюда. Это могут быть глобальные объекты уровня сеанса или приложения, которые существуют на сервере, пока не закончится сеанс с пользователем или не закроется ( на обед или профилактику ) приложение. Мы выбираем свободу, демократию и объект уровня сеанса Session!
- Через панель Solution Explorer копируйте в корневой виртуальный каталог нашего сайта с примерами файл TestTheme.aspx и назовите его TestSwitchThemes.aspx
- Определите новую страницу TestSwitchThemes.aspx стартовой
- Откройте файл TestSwitchThemes.aspx на редактирование и добавьте в верхнюю часть страницы из вкладки Standard элемент пользовательского интерфейса для выбора темы, раскрывающийся список DropDownList
- Переименуйте ID списка в lstSwitchThemes
- Через панель Properties в режиме Events создайте для списка заготовку обработчика события SelectedIndexChanged, которое поступит на сервер с обратной отсылкой, если пользователь изменит выбор в списке
- Добавьте на страницу перед списком серверный элемент Label с именем lblFirstLoad
На всякий пожарный случай (чтобы вам - его скопировать, а мне - отчитаться по объему текста перед зав. кафедрой Горенским Б.М.), приведу полный дескрипторный код, который будет иметь страница после проведения последних "оперативно-следственных" мероприятий
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestSwitchThemes.aspx.cs" Inherits="TestTheme" %> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Тест применения тем</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="lblFirstLoad" runat="server" Text="Label" /> <asp:DropDownList ID="lstSwitchThemes" runat="server" OnSelectedIndexChanged="lstSwitchThemes_SelectedIndexChanged"> </asp:DropDownList> <br /> <br /> <asp:BulletedList ID="BulletedList1" runat="server"> <asp:ListItem>Туризм</asp:ListItem> <asp:ListItem>Спорт</asp:ListItem> <asp:ListItem>Музыка</asp:ListItem> <asp:ListItem>Танцы</asp:ListItem> </asp:BulletedList> <asp:Label ID="Label1" runat="server" Text="Текстовая метка" /> <asp:CheckBox ID="CheckBox1" runat="server" Text="Флажок" BackColor="Black" ForeColor="Yellow" EnableTheming="True" /> <br /> <br /> <b>Тема подключена к странице через свойство Theme объекта DOCUMENT</b> <br /> <table width="100%" border="1"> <tr> <td> SkinID не подключен: действуют общие настройки по умолчанию </td> <td> <asp:Label ID="Label2" runat="server" Text="Label2" /> </td> <td> <asp:CheckBox ID="CheckBox2" runat="server" Text="CheckBox2" /> </td> </tr> <tr> <td> Именованные настройки подключены: SkinID="Variant3" </td> <td> <asp:Label ID="Label3" runat="server" Text="Label3" SkinID="Variant3" /> </td> <td> <asp:CheckBox ID="CheckBox3" runat="server" Text="CheckBox3" SkinID="Variant3" /> </td> </tr> <tr> <td> Именованные настройки подключены: SkinID="Variant4" </td> <td> <asp:Label ID="Label4" runat="server" Text="Label4" SkinID="Variant4" /> </td> <td> <asp:CheckBox ID="CheckBox4" runat="server" Text="CheckBox4" SkinID="Variant4" /> </td> </tr> <tr> <td> SkinID="Variant5" подключен, но тема подавлена: EnableTheming="False" </td> <td> <asp:Label ID="Label5" runat="server" Text="Label5" EnableTheming="False" SkinID="Variant5" /> </td> <td> <asp:CheckBox ID="CheckBox5" runat="server" Text="CheckBox5" EnableTheming="False" SkinID="Variant5" /> </td> </tr> <tr> <td> Подключен несуществующий SkinID="XXX": настройки темы не действуют </td> <td> <asp:Label ID="Label6" runat="server" Text="Label6" SkinID="XXX" /> </td> <td> <asp:CheckBox ID="CheckBox6" runat="server" Text="CheckBox6" SkinID="XXX" /> </td> </tr> </table> <br /> <asp:Button ID="Button1" runat="server" Text="Отправить" /> </div> </form> </body> </html>
- Откройте на редактирование файл кода TestSwitchThemes.aspx.cs и заполните его так
using System; //using System.Data; //using System.Configuration; //using System.Collections; //using System.Web; //using System.Web.Security; //using System.Web.UI; //using System.Web.UI.WebControls; //using System.Web.UI.WebControls.WebParts; //using System.Web.UI.HtmlControls; public partial class TestTheme : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { lblFirstLoad.Text = "Первая загрузка страницы"; // Заполняем список именами каталогов тем System.IO.DirectoryInfo themeDir = new System.IO.DirectoryInfo(Server.MapPath("App_Themes")); lstSwitchThemes.DataTextField = "Name"; lstSwitchThemes.DataSource = themeDir.GetDirectories(); lstSwitchThemes.DataBind(); } else lblFirstLoad.Text = "Повторная загрузка страницы"; } protected void Page_PreInit(object sender, EventArgs e) { if (Session["Theme"] != null) { // Был ранее сохраненный выбор, включить его Page.Theme = (string)Session["Theme"]; // Восстанавливаем новое состояние списка lstSwitchThemes.SelectedIndex = (int)Session["index"]; // Восстанавливаем текущее состояние "Флажка" CheckBox1.Checked = (bool)Session["Флажок"]; } } protected void lstSwitchThemes_SelectedIndexChanged(object sender, EventArgs e) { // Запоминаем новый выбор пользователя Session["Theme"] = lstSwitchThemes.SelectedValue; // Запоминаем новое состояние списка Session["index"] = lstSwitchThemes.SelectedIndex; // Сохраняем текущее состояние "Флажка" Session["Флажок"] = CheckBox1.Checked; // Первый вариант: // Обновляем страницу с полным циклом, // чтобы новая тема вступила в действие //Response.Redirect(Request.Url.ToString()); // Второй вариант: // Обновляем страницу без полного цикла, // чтобы новая тема вступила в действие Server.Transfer(Request.FilePath); } }
Прежде, чем выполнить страницу, сделаем некоторые пояснения по приведенному C#-коду
- Объявления пространства имен закомментированы специально как неиспользуемые в данном файле (хотя это можно и не делать). Действительно:
- объекты пользовательского интерфейса определены в дескрипторной части страницы;
- класс DirectoryInfo мы явно указали с пространством имен System.IO и компилятор его увидит
- объекты Session, Server, Response, Request являются общедоступными свойствами класса System.Web.UI.Page, который наследуется нашим классом TestTheme, а значит явно преобретает все эти свойства
- Оставлено незакомментированным объявление пространства имен System только потому, что в коде имеются ключевые слова string, int, bool, используемые при явном приведении типов. Но и его можно было бы закомментировать, если использовать полные имена библиотечных типов System.string, System.int, System.bool
- При принудительной перезагрузке страницы полностью сбрасывается состояние вида ее элементов управления, поэтому нужно искать другие способы сохранения их состояния. Мы позаботились об этом только для списка и одного флажка.
- Язык C# допускает использование в идентификаторах символов кириллицы при условии соблюдения общепринятых правил: должны состоять из букв цифр и знаков подчеркивания, не должны начинаться с цифры, и т.д.
- При внутренней обработке кода страницы можно (в разумных пределах) использовать весь арсенал библиотеки .NET Framework, а не только специализированные Web-средства
Теперь осталось внести какое-либо видимое различие в оформления тем, чтобы мы могли убедиться, что программно была подключена действительно другая тема. Для экономии своих сил внесем минимальные изменения, например, сделаем разными цвета фона страницы.
- Вызовите на редактирование файл App_Themes/MyTheme1/ThemeCSS.css и поменяйте в селекторе body определение стиля фона background, чтобы фон страницы стал зеленым
- Вызовите на редактирование файл App_Themes/MyTheme2/ThemeCSS.css и поменяйте в селекторе body определение стиля фона background, чтобы фон страницы стал красным
Наступил момент истины...
- Исполните страницу и убедитесь в справедливости сказанного. Должен получиться такой уменьшенный результат
Подобным образом программно можно менять и другие стилевые свойства страницы и ее элементов, например, именованные оформления элементов управления, определенные атрибутом SkinID. Не обязательно давать пользователю возможность настройки стилевого оформления напрямую. Можно сделать оформление страниц и элементов косвенно зависимым от выбора пользователя. Принципиальной разницы с точки зрения кода здесь мало, главное - не переусердствовать!
И последнее... Все, что было сказано выше для декларативного способа подключения тем и приоритетов их применения, остается справедливым и для программного способа. Это касается и страницы в целом, и отдельных элементов управления.
Вот и закончилась тема про "темы"!