?
Глава 9. Новые возможности навигации по Web сайту
Параграф 1. Элемент управления Multiview и множественное представление вида страницыПоказ того или иного вида одной страницы или отдельной ее части до Asp.Net 2.0 обычно решался с использованием нескольких элементов Panel. Каждая панель оформлялась в соответствии с необходимостью, а показ выполнялся в каждый момент только одной. Для этого использовалось свойство "Visible" элемента. Элемент управления Multiview явился логическим продолжением этого подхода. Multiview может содержать несколько элементов управления View из которых отображается в конкретный момент только один. Но поскольку все представления View объединены одним элементом Multiview, то стало значительно проще выполнять позиционирование (один элемент Multiview против нескольких элементов Panel). Уже это стало большим плюсом нововведения Asp.Net 2.0. Если учесть, что при проектировании страницы мы можем видеть все, не перекрываемые представления одновременно, а навигация между представлениями значительно упрощена, то использование программистами старого и доброго метода с множеством панелей похоже ушло навсегда. И так, элемент MultiView задает группу представлений (объектов View), из которых может быть выбрано активным и показано клиенту в конкретный момент только одно. Каждый объект View имеет собственное "ID". Для показа конкретного элемента View достаточно установить свойство "ActiveViewIndex" элемента MultiView в значение, равное значению "ID" элемента View, который требуется показать. Для демонстрации возможностей использования MultiView создадим простое решение Web сайта, поместив на панель один контрол MultiView. Добавим в него два контрола View. В каждый контрол View добавим по одному контролу Label и два контрла Button (Рис.1.). Создадим обработчики событий нажатия кнопок.
Рис.1. Решение сайта HTML код примера будет следующим: <body> <form id="form1" runat="server"> <div> <asp:MultiView ID="MultiView1" runat="server"> <asp:View ID="View1" runat="server"> <asp:Label ID="Label1" runat="server" Text="Label2"></asp:Label><br /> <asp:Button ID="Button1" runat="server" Text="Button1" Height="40px" OnClick="Button1_Click" Width="99%" /><br /> <asp:Button ID="Button2" runat="server" Text="Button2" Height="40px" OnClick="Button2_Click" Width="99%" /><br /> </asp:View> <asp:View ID="View2" runat="server"> <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label><br /> <asp:Button ID="Button3" runat="server" OnClick="Button3_Click" Text="Button3" Height="40px" Width="99%" /><br /> <asp:Button ID="Button4" runat="server" Height="40px" OnClick="Button4_Click" Text="Button4" Width="99%" /> </asp:View> </asp:MultiView> </div> </form> </body> Элемент MultiView при добавлении в него элементов View присваивает каждому из них индекс, начиная с номера "0". Выбирая индекс представления в свойстве "ActiveViewIndex", мы, тем самым, выбираем отображаемый элемент View. Обратим внимание еще на два свойства контролов Button: "CommandName" и "CommandArgument" (Рис.1.). Эти свойства позволяют задать четыре предопределенных команды, распознаваемых элементом MultiView и выполнять смену представлений.
Следующий код демонстрирует возможность выбора представлений с использованием свойств "ActiveViewIndex" элемента MultiView и команд "NextView" и "PrevView". При каждом нажатии любой из кнопок мы будем менять представление: protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { MultiView1.ActiveViewIndex = 0; Label1.Text = "Мы находимся в View1"; Button2.CommandName = "NextView"; Button2.CommandArgument = ""; Button4.CommandName = "PrevView"; Button4.CommandArgument = ""; } } protected void Button1_Click(object sender, EventArgs e) { MultiView1.ActiveViewIndex = 1; Label2.Text = "Мы пришли в View2 из View1 через выбор ActiveViewIndex."; } protected void Button2_Click(object sender, EventArgs e) { Label2.Text = "Мы пришли в View2 из View1 через команду " + Button2.CommandName; } protected void Button3_Click(object sender, EventArgs e) { Label1.Text = "Мы пришли в View1 из View2 через выбор ActiveViewIndex."; MultiView1.ActiveViewIndex = 0; } protected void Button4_Click(object sender, EventArgs e) { Label1.Text = "Мы пришли в View1 из View2 через команду " + Button4.CommandName; } Для демонстрации использования команд "SwitchViewByID" и "SwitchViewByIndex" немного изменим обработчики нажатия кнопок 2 и 4: protected void Button2_Click(object sender, EventArgs e) { Label2.Text = "Мы пришли в View2 из View1 через команду " + Button2.CommandName; if (Button2.CommandName.Trim().ToUpper() == "NEXTVIEW") { Button2.CommandName = "SwitchViewByID"; Button2.CommandArgument = "View2"; } else { Button2.CommandName = "SwitchViewByIndex"; Button2.CommandArgument = "1"; } } protected void Button4_Click(object sender, EventArgs e) { Label1.Text = "Мы пришли в View1 из View2 через команду " + Button4.CommandName; if (Button4.CommandName.Trim().ToUpper() == "PREVVIEW") { Button4.CommandName = "SwitchViewByID"; Button4.CommandArgument = "View1"; } else { Button4.CommandName = "SwitchViewByIndex"; Button4.CommandArgument = "0"; } }
Рис.2. Выполнение решения Отметим одну особенность использования элемента MultiView - при добавлении контролов одного типа в различные представления, имена добавляемых контролов не могут повторяться. Контролы существуют не в каждом представлении, а являются элементами всего Web сайта. В силу этого - состояние каждого элемента отдельно хранится в ViewState, что при большом количестве элементов и представлений не совсем оправдано и ведет к значительному повышению трафика "клиент-сервер". Разработчик может обойти это, отключая ViewState для элементов, состояние которых нет необходимости сохранять (EnableViewState=false). Но необходимость слежения за свойством "EnableViewState" является явным недостатком такого подхода. Параграф 2. Элемент управления WizardЭлемент управления Wizard является более эффективной версией MultiView. Он поддерживает возможность разработки одновременно нескольких представлений в рамках одного элемента, имеет встроенные кнопки навигации и меню. Создадим простое решение Web сайта, поместив на панель один контрол Wizard (Рис.3.).
Рис.3. Использование элемента Wizard По умолчанию элемент управления Wizard имеет левую панель с перечнем шагов, которые уже сопоставлены с представлениями (пока пустыми) и кнопки навигации. Число шагов можно изменять используя мастер настройки. Левую панель можно убрать, установив свойство "DisplaySideBar" контрола Wizard в значение "false". В этом случае навигация возможна только нажатием кнопок "Next" и "Previous". Имеется возможность выбора нескольких предопределенных оформлений, используя пункт "AutoFormat" меню настройки элемента (Рис.4.).
Рис.4. Использование элемента Wizard Следующий пункт меню контрола, позволяет добавлять и удалять шаги навигации (отображаемых представлений). Кроме того, пункт позволяет задать некоторые свойства для шагов навигации (Рис.5.).
Рис.5. Использование элемента Wizard Среди свойств элемента отметим:
Элемент Wizard имеет свойство "ActiveStepWizard" (начинается от "0"). Если запрета на возврат к шагу не установлено, то свойство позволяет программно выполнять навигацию между представлениями. Содержимое, которое будет отображено на данном шаге, должно находиться (быть внесено) в пределах пунктирного прямоугольника в правом верхнем углу отображения шага. Сюда можно поместить любые контролы и HTML тэги. Прямоугольник расширяется по мере добавления содержимого, однако в нем не производится никакого выравнивания. Об этом разработчик должен позаботиться сам (использовать тэги "table", "div"...). Пример разработки трех шагового элемента дан в следующем HTML коде: <asp:Wizard ID="Wizard1" runat="server" ActiveStepIndex="2" BackColor="#FFE9D2" StartNextButtonText="Вперед" StepNextButtonText="Вперед" StepPreviousButtonText="Назад" Width="99%" CancelButtonText="Выход" FinishCompleteButtonText="Выход" FinishPreviousButtonText="Назад"> <stepstyle borderstyle="Solid" borderwidth="1px" /> <WizardSteps> <asp:WizardStep runat="server" title="Домашняя страничка"> <div align=center> <asp:Label ID="Label1" runat="server" Font-Size="Large" ForeColor="#CC0000" Text="Trade firm OOO "Horn and Hoofs""></asp:Label> <br /> <asp:Image ID="Image1" runat="server" ImageUrl="~/Images/a1.gif" /> <br /> <asp:Label ID="Label2" runat="server" Font-Size="Large" ForeColor="Red" Text="Наша фирма - лучшая в мире!" Width="99%"></asp:Label> </div> </asp:WizardStep> <asp:WizardStep runat="server" title="Что на складе"> <div align=center> <asp:Label ID="Label3" runat="server" Text="Рогов 2. Копыт нет." Font-Bold="True" Font-Size="Large" ForeColor="#0066FF"></asp:Label> </div> </asp:WizardStep> <asp:WizardStep runat="server" Title="Новости сайта"> Страничка оформляется! </asp:WizardStep> </WizardSteps> <steppreviousbuttonstyle borderstyle="Solid" /> </asp:Wizard> Для самого контрола Wizard предусмотрена возможность установки множества свойств, большинство из которых интуитивно понятны и служат, в основном, для изменения внешнего вида отображений, меню, кнопок, задания шрифтов, фона и т.п. Некоторые из свойств использованы в коде, приведенном выше. Выполнение решения показано на Рис.6.
Рис.6. Использование элемента Wizard Основные события элемента Wizard, события связанные с нажатием кнопок ("CancelButtonClick", "FinishButtonClick", "NextButtonClick", "PreviousButtonClick" "SideBarButtonClick") и событие "ActiveStepChanged" (произошла смена отображения). Необходимость в использовании событий возникает, как и обычно, когда требуется дополнительно выполнить те или иные действия связанные с событием и ничем не отличаются от их обычного использования на Web сайте. По этой причине мы не будем останавливаться на данном вопросе. Отметим, что, как и для элемента MultiView, при добавлении контролов одного типа в различные представления имена добавляемых контролов не могут повторяться. Контролы существуют не в каждом представлении, а являются элементами всего Web сайта. В силу этого - состояние каждого элемента отдельно хранится в ViewState, что, при большом количестве элементов и представлений не совсем оправдано, и ведет к значительному повышению трафика "клиент-сервер". Разработчик может обойти это, отключая ViewState для элементов, состояние которых нет необходимости сохранять (EnableViewState=false). Но необходимость слежения за свойством "EnableViewState" является явным недостатком такого подхода и использования элемента Wizard. Параграф 3. Использование карт сайтаВыше мы рассматривали навигацию в пределах одной страницы. Это, можно сказать, "псевдо навигация". Собственно навигацией принято считать переход между страницами сайта. Наиболее часто для этого ранее использовались прямые ссылки для вызова страниц и методы Server.Transfer и Response.Redirect. Начиная с ASP.NET 2.0 появились несколько дополнительных средств, основа которых - карты сайта. Навигация по сайту может быть описана в карте сайта. ASP.NET 2.0 предлагает механизм реализации навигации по сайту, при котором карта сайта хранится в файле формата XML. Файл должен иметь расширение ".sitemap" и находиться в корневой папке решения. На основе информации, которая будет сохранена в файле, средства ASP.NET имеют возможность построения объектной модели навигации. Эту задачу выполняют XmlSiteMapProvider (поставщик данных) и SiteMapDataSource (объект, формируемый по информации карты сайта). Для рассмотрения вопроса создадим простое решение Web сайта (пока с пустой формой). В контекстном меню решения сайта выбираем пункт "Add New Item" и в диалоговом окне выбираем шаблон "Site Map". В дерево решения сайта будет добавлен файл "Web.sitemap". <?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode url="" title="" description=""> <siteMapNode url="" title="" description="" /> <siteMapNode url="" title="" description="" /> </siteMapNode> </siteMap> Файл, созданный мастером имеет корневой узел siteMap и только один узел siteMapNode, который задает принцип формирования узлов и, по мнению разработчиков, должен представлять домашнюю страничку сайта. Узлы siteMapNode могут содержать любое число вложенных siteMapNode (показано два). Каждый узел карты сайта должен содержать свойства "url", "title", "description", например: <siteMapNode url="~/Default.aspx" title="Домашняя страничка" description="Моя домашняя страничка"> Кроме параметра "URL" siteMapNode должен иметь параметр "title" - то, что будет отображено на странице и может иметь параметр "description" - подсказка, которая высвечивается при нахождении курсора над текстом, заданном в параметре "title". Отметим, что два узла сайта не могут иметь одинаковые URL адреса. Для того, чтобы на странице можно было использовать карту сайта, разработан контрол SiteMapPath. Ему нет необходимости задавать источник данных. Будучи помещенным на любую страницу сайта, он, автоматически, читает карту сайта из файла Web.sitemap и отображает на данной странице информацию (ветвь навигационных узлов) о ее месте по отношению к корневому узлу сайта. Иначе, если карта описывает пять узлов, и мы хотим видеть свое положение в пределах всех страниц сайта и иметь возможность вернуться к корневому узлу, то SiteMapPath должен быть помещен на каждую из пяти страниц (правда, не надо забывать о технологии Master Pages, которая позволяет разместить контрол SiteMapPath в шаблоне и использовать его на любой странице). Добавим к нашему решению еще четыре страницы a1-a4 (Рис.7, слева). Изменим карту сайта, чтобы она имела два уровня вложения Default - a1 - a2 и Default - a3 - a4 (редактирование карты значительно облегчается если использовать технологию IntelliSense): <?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode url="~/Default.aspx" title="Домашняя страничка" description="Моя домашняя страничка"> <siteMapNode url="~/a1.aspx" title="Page 1" description="Страница 1" > <siteMapNode url="~/a2.aspx" title="Page 2" description="Страница 2" /> </siteMapNode> <siteMapNode url="~/a3.aspx" title="Page 3" description="Страница 3" > <siteMapNode url="~/a4.aspx" title="Page 4" description="Страница 4" /> </siteMapNode> </siteMapNode> </siteMap>
Рис.7. Решение сайта Поместим на все страницы сайта контрол SiteMapPath. Обратим внимание, что при помещении контрола на страницы, место страниц в установленной в карте сайта иерархии сразу отображается в решении (Рис.7, слева и справа). Если добавить на страницу "Default" контрол Button и в обработчике его нажатия записать код перехода на страницу a4, то можно убедиться, что карта сайта выглядит (при переходе на страницу a4) аналогично, как и в решении, а ссылки позволяют вернуться к любой верхней странице в заданной в карте сайта ее иерархии (Рис.8.). protected void Button1_Click(object sender, EventArgs e) { Response.Redirect("a4.aspx"); }
Рис.8. Выполнение решения Отметим, что ряд свойств SiteMapPath позволяют задать стиль отображения его элементов на страницах сайта ("ControlStyle", "BorderStyle", "PathSeparatorStyle", "RootNodeStyle", "CurrentNodeStyle", "NodeStyle"). Среди других интересных свойств отметим свойство "ParentLevelsDisplayed" - задает число одновременно отображаемых родительских уровней (-1 - все), свойство "PathDirection" - задает направление отображения уровней иерархии ("RootToCurrent", "CurrentToRoot"), свойство "PathSeparator" - задает разделитель между узлами пути (любой строковый литерал) и свойства "RenderCurrentNodeAsLink" и "ShowToolTips", определяющие возможность отображения текущего узла как текста или ссылки (на самого себя) и подсказки для него из свойства "description". Заметим, что не обязательно начинать карту сайта с реальной страницы. Если начать карту сайта с фиктивной страницы, как показано ниже, то Default страница (любая начальная страница) становится одной из вложенных страниц и можно создать несколько вложенных блоков, всего лишь одним из которых будет блок с родительским узлом Default. <siteMapNode url="~/" title="" description=""> <siteMapNode url="~/Default.aspx" title="Домашняя страничка" description="Моя домашняя страничка"> <siteMapNode url="~/a1.aspx" title="Page 1" description="Страница 1" > ....... </siteMapNode> <siteMapNode url="~/Default1.aspx" title="Рабочая страничка" description="Моя рабочая страничка"> <siteMapNode url="~/a10.aspx" title="Page 10" description="Страница 10" > ....... </siteMapNode> <siteMapNode> Использование карт сайта в "чистом виде", как можно было заметить, имеет не только плюсы, но и минусы. Так, мы можем двигаться по ссылкам только в одном направлении - к верхним в иерархии узлам и, тем самым, карта сайта и SiteMapPath для навигации является лишь базой. Удобство навигации призваны обеспечить элементы управления TreView и Menu. Параграф 4. Элемент управления TreeViewЭлемент управления TreeView предназначен для показа иерархии страниц сайта, определенной либо непосредственно в элементе управления, либо в XML-файле (или карте сайта). Структура элемента TreeView аналогична карте сайта и может иметь корневые (один или несколько), родительские (один или несколько) и дочерние (один или несколько) узлы. Как и в карте сайта любой узел может иметь сколько угодно потомков. Узлы, у которых нет потомков, принято называть листьями. Используя элемент TreeView, создадим структуру взаимосвязи страниц сайта аналогичную рассмотренной выше. Поместим контрол TreeView со вкладки Navigation на страничку Default.aspx нашего решения (используем решение параграфа 3 со страницами Default.aspx, Default2.aspx, a1.aspx, a2.aspx, a3.aspx, a4.aspx). В предыдущем решении пока ничего удалять не будем. Для добавления узлов и выбора внешнего вида дерева навигации сайта, кликнем мышкой на треугольничке в правом верхнем углу кантрола TreeView (или вызовем контекстное меню контрола). Пункт меню "AutoFormat" позволяет выбрать один из предопределенных видов дерева навигации, а пункт "Edit Nodes" добавлять узлы (Рис.9.). Можно также включить показ линий, соединяющих узлы, поставив галочку для свойства "ShowLines" и отредактировать линии используя мастер Asp.Net TreeView Line Image Generator (пункт меню "Customize Line Images"). В качестве изображения для узлов можно задать картинки (свойства "CollapseImageUrl", "ExpandImageUrl", "LeafImageUrl").
Рис.9. Настройка TreeView Код элемента TreeView содержит основные тэги для отображения узлов Nodes и TreeNode и много дополнительных тэгов, отвечающих за стиль отображения узлов. Сформированный нами код (Рис.9.) показан ниже: <asp:TreeView ID="TreeView1" runat="server" ImageSet="BulletedList4" ShowExpandCollapse="False" ShowLines="True"> <ParentNodeStyle Font-Bold="False" /> <HoverNodeStyle Font-Underline="True" ForeColor="#5555DD" /> <SelectedNodeStyle Font-Underline="True" ForeColor="#5555DD" HorizontalPadding="0px" VerticalPadding="0px" /> <Nodes> <asp:TreeNode Text="Мой сайт" Value="Сайт Wlada" NavigateUrl="~/Default.aspx"> <asp:TreeNode Text="Домашняя страничка" Value="Моя домашняя страничка"> <asp:TreeNode NavigateUrl="~/a1.aspx" Text="Моя биография" Value="Моя биография"></asp:TreeNode> <asp:TreeNode NavigateUrl="~/a2.aspx" Text="Моя семья" Value="Моя семья"></asp:TreeNode> </asp:TreeNode> <asp:TreeNode Text="Рабочая страничка" Value="Моя рабочая страничка" NavigateUrl="~/Default2.aspx"> <asp:TreeNode NavigateUrl="~/a3.aspx" Text="Мои дела" Value="Мои дела"></asp:TreeNode> <asp:TreeNode NavigateUrl="~/a4.aspx" Text="Мои победы" Value="Мои победы"></asp:TreeNode> </asp:TreeNode> </asp:TreeNode> </Nodes> <NodeStyle Font-Names="Tahoma" Font-Size="10pt" ForeColor="Black" HorizontalPadding="5px" NodeSpacing="0px" VerticalPadding="0px" /> </asp:TreeView> Выполнение решения показано на Рис.10.
Рис.10. Выполнение решения Параграф 5. Элемент управления TreeView и карты сайтаУберем со страницы решения Default.aspx контролы SiteMapPath и TreeView и вновь поместим контрол TreeView с вкладки Navigation на страницу. Теперь обратим внимание на пункт меню контрола "Choise Data Soutce". В выпадающем списке выберем "New Data Source". В появившемся окне "Data Source Configuration Wizard" выберем "Site Map" (Рис.11).
Рис.11. Использование карты сайта в TreeView После нажатия кнопки "OK" на форме появится новый контрол-провайдер Site MapDataSource, связанный по ID с контролом TreeView (на этом этапе можно задать и XML файл, использование которого будет обеспечивать контрол-провайдер XmlDataSource, но эта возможность более относится к отображению иерархических данных, нежели к навигации): <asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1"></asp:TreeView> <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" /> Выберем в контекстном меню контрола TreeView пункт "Refresh" и обратим внимание, что на сайте отобразится наша карта, созданная ранее (параграф.3.). Выполним решение (Рис.12.).
Рис.12. Выполнение решения Отметим, что нам по прежнему доступны все настройки для TreeView (пункты меню "AutoFormat", "Show Lines", "Customize Line Images"). Замечательную возможность предоставляет совместное использование SiteMapPath и TreeView на одной странице. Напомним, что у нас сейчас на всех страницах, кроме страницы Default.aspx, размещены контролы SiteMapPath. Поместим на страничку a3.aspx контрол TreeView и настроим его на использование нашей карты сайта. Установим свойство "StartFromCurentNude" для контрола-провайдера SiteMapDataSource1 в значение "true". Выполним решение и перейдем на страницу a3. Результат - SiteMapPath отображает только дорогу к корню, а TreeView дорогу к дочерним узлам (Рис.13.). Это дополнительная возможность построения навигационных схем, позволяющая в пределах узла видеть все, что выше в иерархии сайта по прямому пути к корневому узлу и пути ко всем расположенным ниже узлам.
Рис.13. Выполнение решения Отметим, что для SiteMapDataSource можно задать свойство "StartingNudeOffset", которое позволяет выбрать для элемента TreeView стартовый узел отображения относительно текущего. Это число, которое может быть и отрицательным. Так, при значении свойства равным "-1", на страничке a3, будет отображена полная иерархия сайта (Рис.14. слева), а при тех же условиях для страницы 4 - отображение иерархии начнется с узла a3 (Рис.14. справа).
Рис.14. Варианты использования свойства StartingNudeOffset Стартовый узел можно задать и используя свойство "StartNudeUrl", присвоив свойству конкретные URL адреса сайта (задание сразу двух свойств "StartNudeUrl" и "StartingNudeOffset" приведет к ошибке, причем свойство "StartingNudeOffset" при задании "StartNudeUrl" должно быть установлено в "false"). Параграф 6. Возможности программного управления SiteMap и TreeViewЭлемент управления TreeView имеет множество не только свойств, но и методов обработки событий ("SelectedNodeChanged", "TreeNodePopulate", "TreeNodeExpanded", "TreeNodeDataBound", "TreeNodeCollapsed", "TreeNodeCheckChanged"...). Однако, возможность программно управлять внешним видом элемента управления и его узлов, а также и навигацией по сайту, с использованием этих методов практически недоступна. Дело в том, что элемент управления TreeView может работать в двух режимах: навигации и выбора. Если он используется для навигации (не зависимо от того, создан ли он на базе карты сайта или сформирован, как мы это делали в параграфе 4.), то клик мышкой на узле приводит к переходу на другую страницу и перечисленные события не возникают (вспомним "Жизненный цикл страницы"). Обработчики всех этих событий доступны только тогда, когда элемент управления TreeView используется для отображение иерархической информации (и не содержит URL адресов сайтов - свойства "NavigateUrl"). Клик мышкой по узлу, в этом случае, будет приводить к обратной отправке страницы на сайт. поскольку, мы ведем речь в данном разделе о навигации, то эту возможность мы рассматривать не будем. Другая возможность программного взаимодействия с картой сайта - API-интерфейс карты сайта, который включает класс System.Web.SiteMap. Он имеет статические свойства "CurrentNode", "RootNode" и "Provider". Свойства "CurrentNode" и "RootNode" возвращают объект SiteMapNude, который имеет свойства "Title", "Description", "Url", "ParentNode", "ChildNodes" (коллекция дочерних узлов), "PreviousSibling" (предыдущий узел того же уровня или null), "NextSibling" (следующий узел того же уровня или null), "Key" (строка представляющая расположение карты сайта) и другие. Некоторые возможности использования данных свойств показаны ниже: protected void Page_Load(object sender, EventArgs e) { //Текущий узел заглавие Label1.Text = SiteMap.CurrentNode.Title; //Текущий узел подсказка Label1.Text = SiteMap.CurrentNode.Description; //Текущий узел URL Label1.Text = SiteMap.CurrentNode.Url; ...... //Корневой узел имеет аналогичные свойства Label1.Text = SiteMap.RootNode.Title; ....... //Провайдер Label1.Text = SiteMap.Provider.ToString(); } protected void Button1_Click(object sender, EventArgs e) { //Переход на родительский узел string s=SiteMap.CurrentNode.ParentNode.Url; Response.Redirect(s); } Отдельно в классе TreeView определены public функции ExpandAll() и CollapseAll(). Они доступны в любом событии элементов страницы при статическом содержании TreeView, и любом событии элементов страницы, кроме событий самой страницы, при загрузке TreeView из карты сайта (в PageLoad, например, недоступны). Так, следующий код позволяет программно закрывать и раскрывать узлы TreeView: protected void Button1_Click(object sender, EventArgs e) { TreeView1.CollapseAll(); } protected void Button2_Click(object sender, EventArgs e) { TreeView1.ExpandAll(); } Замечание: Мы рассматривали примеры в предположении, что SiteMapPath и TreeView необходимо было помещать на каждую страницу сайта. Но, не надо забывать о технологии Master Pages, которую мы уже рассматривали и которая позволяет установить SiteMapPath и TreeView в шаблоне мастер страницы и использовать их на любой странице сайта. Автор это проделал. Отличий от описанного выше использования элементов управления SiteMapPath и TreeView на мастер странице, от их использования на обычных страницах сайта, нет, но множественность кода только затрудняет усвоение основ. Ниже приведен код внедряемой мастер страницы и код одной из шести страниц примера рассмотренного выше решения (a4.aspx). Все остальные коды страниц используют мастер страницу полностью аналогично. Пример выполнение решения с внедренными в страницу SiteMapPath и TreeView показан на Рис.15. Пример мастер страницы: <%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <title>Мастер страница</title> </head> <body> <form id="form1" runat="server"> <table border="1" width="99%"> <tr> <th width="25%"> </th> <th width="75%"> <asp:SiteMapPath ID="SiteMapPath1" runat="server"> </asp:SiteMapPath> </th> </tr> <tr><th colspan="2">Trade firm OOO "Horns and hoofs"</th></tr> <tr > <td id="menu" rowspan ="2" width="25"> <asp:TreeView ID="TreeView1" runat="server" ImageSet="BulletedList4" ShowExpandCollapse="False" ShowLines="True" > <ParentNodeStyle Font-Bold="False" /> <HoverNodeStyle Font-Underline="True" ForeColor="#5555DD" /> <SelectedNodeStyle Font-Underline="True" ForeColor="#5555DD" HorizontalPadding="0px" VerticalPadding="0px" /> <Nodes> <asp:TreeNode Text="Мой сайт" Value="Сайт Wlada" NavigateUrl="~/Default.aspx"> <asp:TreeNode Text="Домашняя страничка" Value="Моя домашняя страничка"> <asp:TreeNode NavigateUrl="~/a1.aspx" Text="Моя биография" Value="Моя биография"></asp:TreeNode> <asp:TreeNode NavigateUrl="~/a2.aspx" Text="Моя семья" Value="Моя семья"></asp:TreeNode> </asp:TreeNode> <asp:TreeNode Text="Рабочая страничка" Value="Моя рабочая страничка" NavigateUrl="~/Default2.aspx"> <asp:TreeNode NavigateUrl="~/a3.aspx" Text="Мои дела" Value="Мои дела"></asp:TreeNode> <asp:TreeNode NavigateUrl="~/a4.aspx" Text="Мои победы" Value="Мои победы"></asp:TreeNode> </asp:TreeNode> </asp:TreeNode> </Nodes> <NodeStyle Font-Names="Tahoma" Font-Size="10pt" ForeColor="Black" HorizontalPadding="5px" NodeSpacing="0px" VerticalPadding="0px" /> </asp:TreeView> </td> <td width="75" valign ="top" align="center"> </td> </tr> <tr> <td valign ="top" align="center"> <asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server"> </asp:ContentPlaceHolder> </td> </tr> </table> </form> </body> </html> Страница a4.aspx с внедренной мастер страницей: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="a4.aspx.cs" Inherits="a4" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label><br/> <asp:SiteMapPath ID="SiteMapPath1" runat="server" ShowToolTips="False"> </asp:SiteMapPath> <br /> <asp:TreeView ID="TreeView1" runat="server" DataSourceID="SiteMapDataSource1" onselectednodechanged="TreeView1_SelectedNodeChanged"> </asp:TreeView> <asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" StartFromCurrentNode="false" StartingNodeUrl="~/a3.aspx" /> </div> </form> </body> </html>
Рис.15. Пример использования SiteMapPath и TreeView совместно с мастер страницей Параграф 7. Элемент управления MenuЭлемент управления Menu, как и TreeView, разработан для отображения иерархической информации и может быть использован для выполнения навигации по сайту (именно вопросы навигации мы рассматриваем в данном разделе). По функциональным возможностям он несколько уступает элементу управления TreeView, но имеет и свои плюсы. Так, раскрывающиеся узлы (при помещении курсора на имени узла) позволяют экономить место на сайте, а наличие стрелочек (или картиночек) рядом с именем пункта, дает посетителю сайта подсказки о наличии подменю, что несколько облегчает их ориентацию на сайте. Картиночки могут использоваться и в качестве разделителей между пунктами меню. Элемент позволяет задать любое число пунктов меню (уровней иерархии) постоянно отображаемых на страничке сайта (статические пункты) и появляющихся как всплывающие (динамические пункты). Это, вместе с возможностью как горизонтального, так и вертикального расположения отображения пунктов, позволяет, при небольших затратах, добиться выполнения компактного меню даже при очень большом числе узлов. В отличие от TreeView пункты элемента Menu не могут быть программно свернуты или полностью развернуты, не поддерживаются флажки, но, в основном, структура и большинство свойств у обоих элементов достаточно схожи или просто одинаковы. Поэтому, при изложении материала, мы будем подробно говорить только о новых свойствах и возможностях. Структура элемента Menu аналогична карте сайта и элемента TreeView и может иметь корневые (один или несколько), родительские (один или несколько) и дочерние (один или несколько) узлы. Как и в карте сайта любой узел может иметь сколько угодно потомков. Узлы, у которых нет потомков, принято называть листьями. Структура элемента может отражать как карту сайта, так и быть загруженной из XML файла. Кроме того, она может быть задана на этапе конструирования элемента. Используя контрол Menu создадим структуру взаимосвязи страниц сайта аналогичную рассмотренной выше для решения со страницами Default.aspx, Default2.aspx, a1.aspx, a2.aspx, a3.aspx, a4.aspx (может быть использован предыдущий проект в котором уберем контролы TreeView со страниц сайта). Поместим контрол Menu с вкладки Navigation на страничку Default.aspx нашего решения. Для добавления узлов и выбора внешнего вида дерева навигации сайта, кликнем мышкой на треугольничке в правом верхнем углу контрола "TreeView" (или вызовем контекстное меню контрола). Пункт меню "AutoFormat" позволяет выбрать один из предопределенных видов меню навигации, а пункт "Edit Menu Items" - добавлять узлы (Рис.16.). В качестве изображения для разделителей узлов, стрелок и указателя на наличие дочерних узлов можно задать картинки (свойства "SeparatorImageUrl", "ImageUrl", "PopOutImageUrl").
Рис.16. Создание и настройка Menu Создав меню таким образом, далее скопируем его код из файла Default.aspx на другие страницы (например, a3.aspx и a4.aspx). Разработанный код будет выглядеть примерно так (здесь на странице a4.aspx): <%@ Page Language="C#" AutoEventWireup="true" CodeFile="a4.aspx.cs" Inherits="a4" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label><br/> <asp:SiteMapPath ID="SiteMapPath1" runat="server" ShowToolTips="False"> </asp:SiteMapPath> <br /> <br /> <asp:Menu ID="Menu1" runat="server" BackColor="#F7F6F3" DynamicHorizontalOffset="2" Font-Names="Verdana" Font-Size="0.8em" ForeColor="#7C6F57" StaticSubMenuIndent="10px"> <staticselectedstyle backcolor="#5D7B9D" /> <staticmenuitemstyle horizontalpadding="5px" verticalpadding="2px" /> <dynamichoverstyle backcolor="#7C6F57" forecolor="White" /> <dynamicmenustyle backcolor="#F7F6F3" /> <dynamicselectedstyle backcolor="#5D7B9D" /> <dynamicmenuitemstyle horizontalpadding="5px" verticalpadding="2px" /> <statichoverstyle backcolor="#7C6F57" forecolor="White" /> <items> <asp:menuitem Text="Мойт сайт" Value="Мойт сайт"> <asp:menuitem NavigateUrl="~/Default.aspx" Text="Домашняя страничка" Value="Домашняя страничка"> <asp:menuitem NavigateUrl="~/a1.aspx" Text="Моя биография" Value="Моя биография"></asp:menuitem> <asp:menuitem NavigateUrl="~/a2.aspx" Text="Моя семья" Value="Моя семья"> </asp:menuitem> </asp:menuitem> <asp:menuitem NavigateUrl="~/Default2.aspx" Text="Рабочая страничка" Value="Рабочая страничка"> <asp:menuitem NavigateUrl="~/a3.aspx" Text="Мои дела" Value="Мои дела"> </asp:menuitem> <asp:menuitem NavigateUrl="~/a4.aspx" Text="Мои победы" Value="Мои победы"> </asp:menuitem> </asp:menuitem> </asp:menuitem> </items> </asp:Menu> </div> </form> </body> </html> Свойства "Text", "Value", "NavigateUrl" несут туже нагрузку, что и в TreeView, поэтому на них останавливаться не будем. Menu допускает горизонтальную и вертикальную ориентацию, которая задается свойством "Orientation" контрола. На Рис.17. показано различие расположения пунктов меню при значении свойства Orientation=Horizontal для a3.aspx и Orientation=Vertical для a4.aspx .
Рис.17. Создание и настройка элемента Menu Меню состоит из статической и динамической частей. Число статически (постоянно) отображаемых уровней на странице определяет свойство "StaticDisplayLavele". Если на любой странице для нашего меню мы поставим значение этого свойства равное 3, то меню будет полностью присутствовать на странице (все его узлы). По умолчанию это значение равно 1. Каждая часть (динамическая и статическая) имеет свой набор стилей отображения "StaticHoverStyle", "DynamicHoverStyle", "StaticMenuItemStyle", "DynamicMenuItemStyle", "StaticMenuStyle", "DynamicMenuStyle", "StaticSelectedStyle", "DynamicSelectedStyle", "StaticTemplate", "DynamicTemplate". Свойство "Selectable" установленное для пункта в значение "false" приводит к запрету выбора пункта меню, что может быть полезно для блокировки ссылки на уже выбранную страницу или определения заголовка. И последнее, если мы хотим, что бы страница по ссылке меню открывалась в новом окне, то необходимо установить свойство Target в значение "_blanc". Как и элемент TreeView, элемент Menu можно привязать к карте сайта, используя SiteMapDataSource (рис.18.).
Рис.18. Создание и настройка элемента Menu Использование событий элемента Menu ограничено (как и для элемента TreeView). Элемент может работать в двух режимах: навигации и выбора. Если он используется для навигации (не зависимо от способа его заполнения), то клик мышкой на любом узле приводит к переходу на другую страницу и события контрола не возникают (вспомним "Жизненный цикл страницы"). Обработчики событий доступны только тогда, когда элемент управления используется для отображения иерархической информации (и не содержит URL сайтов - свойства "NavigateUrl"). Клик мышкой по узлу, в этом случае, будет приводить к обратной отправке страницы на сайт. Поскольку, мы ведем речь в данном разделе о навигации, то эту возможность мы рассматривать не будем. Возможность программного взаимодействия с картой сайта (API интерфейс карты сайта), в этом случае также доступна и можно использовать класс System.Web.SiteMap аналогично, как мы это делали в параграфе 6. И последнее - элемент SiteMapPath удобен для дополнительной ориентации на сайте при его совместном использовании с элементом Menu . Параграф 8. Карты сайта и базы данныхНемного из истории этого параграфа. Пример, который мы рассмотрим ниже, взят из книги M.MacDonald and M.Szpustra "Pro ASP.NET 2.0 in C# 2005". При изучении материала сразу удалось его повторить для элемента Menu, но попытка заставить работать код с TreeView упорно давала "Stack Overflow Exception". Обратившись к "всемирному разуму", стало понятно, что эта проблема не только у меня одного. Пришлось искать ответ самому. Решение нашлось, когда были немного изменены оформление карты сайта в таблице базы данных и настройку SiteMapDataSource (вернее, потребовалось обязательное задание одного из его свойств). В силу проведенных изменений, удалось создать код, который успешно работает как с элементами SiteMapPath и Menu, так и с элементом TreeView. Если мы попытаемся просто сохранить информацию о карте сайта в базе данных и затем программно сформировать карту сайта на этапе инициализации или загрузки страницы, то карта сайта странице доступна не будет. Причина - карта сайта должна существовать до начала инициализации страницы. В силу этого нам придется использовать механизмы поставщиков данных. Все поставщики карт сайтов происходят от абстрактного класса System.Web.SiteMapProvider. Мы можем воспользоваться непосредственно этим классом или использовать класс StaticSiteMapProvider, в который уже заложена основная логика для работы с картой сайта. В начале создадим таблицу БД для хранения карты сайта в любой доступной нам базе (далее использовался SQL сервер, хотя не возбраняется использовать любой другой, определив провайдер данных для базы, на что будет обращено внимание при регистрации нашего поставщика карты сайта в файле Web.config). Реляционные базы не приспособлены к отображению иерархических данных, и, поэтому, нам понадобится как то закодировать иерархию страниц сайта. В приведенной ниже таблице (Рис.19.) для этой цели используются столбцы "nomnode" (номер узла) и "paretnomnode" (номер родительского узла).
Рис.19. Таблица БД для хранения карты сайта Создадим класс SqlMapProvider.cs (контекстное меню узла сайта/Add New Item /Class, задаем имя SqlMapProvider.cs, нажимаем OK и соглашаемся поместить его в папку App_Code). Делаем класс наследником StaticSiteMapProvider, который, в свою очередь, является наследником абстрактного класса SiteMapProvider. Когда мы поставим двоеточие и наберем StaticSiteMapProvider, в объявлении класса, то сможем заметить, что буква S станет подчеркнутой. Нажав на маркер подчеркивания и треугольничек в появившейся иконке (Рис.20.), выберем пункт меню "Implement abstract class".
Рис.20. Создание класса провайдера В результате в наш класс будут добавлены два перегружаемых метода BuildSiteMap() и GetRootNodeCore(). public class SqlMapProvider : StaticSiteMapProvider { public SqlMapProvider() { } public override SiteMapNode BuildSiteMap() { throw new Exception("The method or operation is not implemented."); } protected override SiteMapNode GetRootNodeCore() { throw new Exception("The method or operation is not implemented."); } } Уберем конструктор класса (он нам не нужен) и добавим методы Initialize, Clear и свойство "RootNode". public class SqlMapProvider : StaticSiteMapProvider { public bool initialized = false; private SiteMapNode rootNode = null; public virtual bool IsInitialized { get { return initialized; } } public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes) { } public override SiteMapNode BuildSiteMap() { //Здесь будем формировать карту сайта ................... //Метод вернет карту сайта return rootNode; } protected override SiteMapNode GetRootNodeCore() { return BuildSiteMap(); } //Это свойство будет требовать SiteMapDataSource public override SiteMapNode RootNode { get { //При запросе свойства return BuildSiteMap(); } } protected override void Clear() { lock (this) { base.Clear(); } } } Логика, заложенная в работу поставщика, заключается в том, что SiteMapDataSource со страницы сайта обращается к заданному или зарегистрированному поставщику данных и стремится получить от него корневой узел сайта со всеми вложенными дочерними узлами всех уровней и листьями (свойство "RootNode"). Это свойство вернет результат выполнения метода BuildSiteMap, в котором и должна быть сформирована карта сайта, а именно, возвращен будет объявленный в классе "private SiteMapNode rootNode". Первым, при взаимодействии провайдера с SiteMapDataSource, будет выполнен метод Initialize. Для того, чтобы SiteMapDataSource видел нашего поставщика, мы должны его зарегистрировать в файле web.config, как показано ниже: <configuration> <appSettings/> <connectionStrings/> <system.web> <compilation debug="true"/> <authentication mode="Windows"/> <siteMap defaultProvider="SqlMapProvider"> <providers> <add name="SqlMapProvider" type="SqlMapProvider" providerName="System.Data.SqlClient" <!-- Здесь подставить значение connectionString для сервера --> connectionString="Server= ;database= ;user id= ;......" stringSql="select * from mapsite order by parentnumnode"/> storedProcedure="" </providers> </siteMap> </system.web> </configuration> Данный код позволил нам зарегистрировать класс SqlMapProvider как провайдер по умолчанию ("defaultProvider"). Кроме того, здесь же определены и ряд параметров, которые могут быть востребованы далее (stringSql или storedProcedure для выборки данных, providerName для определения на каком типе сервера будет храниться таблица карты сайта, можно добавить и другие параметры). Извлечение параметров выполним на этапе инициализации: public class SqlMapProvider : StaticSiteMapProvider { //Сразу допишем все переменные, которые будут использованы //в коде класса (их назначение понятно из их имен) private string sConnectionString = string.Empty; private string sProviderName = string.Empty; private string sStoredProcedure = string.Empty; private string sSql = string.Empty; private DataTable myDatatable = null; //Целевая переменная rootNode private SiteMapNode rootNode = null; //Переменная для блокировки повторной инициализации public bool initialized = false; public virtual bool IsInitialized { get { return initialized; } } ////////// Метод Initialize ////////////////////////////// //Методу передаются параметры: name - содержит зарегистрированное //имя - "SqlMapProvider" и attributes - все параметры, которые //заданы дополнительно (providerName, connectionString, stringSql, //storedProcedure) public override void Initialize(string name, System.Collections.Specialized.NameValueCollection attributes) { if (!IsInitialized) { base.Initialize(name, attributes); sProviderName = attributes["providerName"]; sConnectionString = attributes["connectionString"]; sStoredProcedure = attributes["storedProcedure"]; if (sStoredProcedure == null) { sSql = attributes["stringSql"]; if (sSql == string.Empty || sSql.Length == 0) throw new Exception("Не найдена хранимая процедера или SQL предложение для выбора карты сайта."); } if (sProviderName == string.Empty || sProviderName.Length == 0) throw new Exception("Не задан провайдэр для работы с БД."); else if (sConnectionString == string.Empty || sConnectionString.Length == 0) throw new Exception("Не задана строка соединения с сервером."); initialized = true; } protected override void Clear() { lock (this) { base.Clear(); } } } Обратим внимание, что инициализация, как и формирование карты сайта, выполняется еще до загрузки сайта, а в режиме проектирования - при открытии решения сайта в Visual Studio, что создает дополнительные трудности при разработке. Иногда, при внесении изменений в код класса провайдера нам придется либо закрыть и вновь открыть решение сайта, или удалить и вновь добавить SiteMapDataSource на страницу сайта. Причем, если на сайте используется SiteMapDataSource на нескольких страницах, то сформированная карта сайта будет сохранена до тех пор, пока не будут удалены все SiteMapDataSource со всех страниц сайта. Далее приведена вторая половина кода провайдера, обеспечивающая непосредственно формирование карты сайта в методе BuildSiteMap. Читается вся информация из таблицы базы данных и находится узел, который отвечает требованию: "parentnumnode = 0 or parentnumnode is null" Обратим внимание, что мы намеренно не связали его с реальным адресом (Рис.19), иначе провайдер будет работать с элементами Menu и SiteMapPath и не будет поддерживать работу с TreeView (это будет показано ниже). Далее непосредственно формируется корневой узел (rootNode = new SiteMapNode....) и вызывается рекурсивная функция AddChildren, задача которой создавать узлы и добавлять к ним дочерние узлы и листья до тех пор, пока для очередного взятого узла имеются дочерние (есть связка на узел через столбец таблицы "parentnumnode"). Обойдя все дерево по всем узлам, функция сформирует все его ветви и добавит к rootNode, после чего управление будет возвращено в метод BuildSiteMap. Осталось вернуть сфоритрованную в rootNode карту свойству RootNode и передать его SiteMapDataSource. protected override SiteMapNode GetRootNodeCore() { return BuildSiteMap(); } public override SiteMapNode RootNode { get { return BuildSiteMap(); } } public override SiteMapNode BuildSiteMap() { lock (this) { if (rootNode == null) { Clear(); DbProviderFactory provider = DbProviderFactories.GetFactory(sProviderName); using (DbConnection dbconnection = provider.CreateConnection()) { dbconnection.ConnectionString = sConnectionString; DbCommand dbcommand = provider.CreateCommand(); if (sStoredProcedure == string.Empty || sStoredProcedure == "") { dbcommand.CommandText = sStoredProcedure; dbcommand.CommandType = CommandType.StoredProcedure; } else { dbcommand.CommandType = CommandType.Text; dbcommand.CommandText = sSql; } dbcommand.Connection = dbconnection; DbDataAdapter dbdataadapter = provider.CreateDataAdapter(); dbdataadapter.SelectCommand = dbcommand; DataSet dataset = new DataSet(); dbdataadapter.Fill(dataset); myDatatable = dataset.Tables[0]; } DataRow datarow = myDatatable.Select("parentnumnode = 0 or parentnumnode is null")[0]; rootNode = new SiteMapNode(this, datarow["Url"].ToString(), datarow["Url"].ToString(), datarow["Title"].ToString(), datarow["Description"].ToString()); AddChildren(rootNode, datarow["numnode"].ToString()); } } return rootNode; } private void AddChildren(SiteMapNode parentNode, string sNumNode) { DataRow[] childRows = myDatatable.Select("parentnumnode = " + sNumNode); foreach (DataRow row in childRows) { SiteMapNode childNode = new SiteMapNode(this, row["Url"].ToString(), row["Url"].ToString(), row["Title"].ToString(), row["Description"].ToString()); AddNode(childNode, parentNode); AddChildren(childNode, row["numnode"].ToString()); } } На этом разработка кода поставщика данных из таблицы базы данных закончена. Мы используем решение сайта для которого нами созданы страницы Default.aspx, Default2.aspx, a1.aspx, a2.aspx, a3.aspx, a4.aspx. Поместим последовательно на страницы Default.aspx, a1.aspx и a2.aspx контролы SiteMapPath и TreeView, а на страницы Default2.aspx, a3.aspx и a3.aspx контролы Menu. Выберем источник данных для контролов (Рис.21.) аналогично, как мы это уже неоднократно делали в данном параграфе.
Рис.21. Добавление источника данных для контролов Menu и TreeView. Для всех SiteMapDataSource, которые будут работать с TreeView, установим значение свойство "ShowStartingNode" в "false" (попробуйте запустить решение на выполнение со значением свойства равным "true", чтобы понять причину). Убедимся в функционировании элементов навигации сайта (Рис.22.).
Рис.22. Выполнение решения сайта Как и ранее отметим, что мы помещали средства навигации на каждую
страницу сайта, но, используя технологию Master Pages, можно
помещать средства навигации в шаблон и использовать их
на любой странице сайта.
Молчанов Владислав 14.11.2007г.
Еcли Вы пришли с поискового сервера - посетите мою главную страничкуНа главной странице Вы найдете программы комплекса Veles - программы для автолюбителей,
программу NumberPhoto, созданную для работы с фото, сделанными цифровым фотоаппаратом,
программу Локальный Web сайт - предназначенную для просмотра и прослушивания
файлов большинства графических и звуковых форматов в Web Browser,
программу Bricks - игрушку для детей и взрослых, программу записную книжку,
программу TellMe - говорящий Русско-Английский разговорник - программу для тех, кто собирается
погостить за бугром или повысить свои знания в английском, теоретический материал
по программированию в среде Borland C++ builder, C# (Windows приложения и ASP.Net Web сайты).
|