Использование XML и LINQ в элементах управления TreeView и ListViewИсточник: cyberguru Miroslav Kadera
В то время как все больше информации хранится в XML-формате, веб-приложениям необходимо иметь возможность в интерфейсе веб-страницы осуществлять просмотр и редактирование информации, хранимой в XML-файле. Если информация хранится в файле и имеет табличный вид, то мы можем использовать элементы управления DataGrid, GridView и Repeater. Но что делать, если XML-данные представлены более иерархической структурой? Представьте телефонную книгу компании, которая рекурсивно структурируется в филиалы и отделы. Как такую информацию, которая может иметь множество узлов и любое количество уровней, отобразить и отредактировать посредством веб-страницы? В данной статье мы создадим веб-страницу, которая будет отображать содержимое телефонной книги всей компании, где информация находится в XML-файле. Страница рекурсивно будет отображать XML-информацию из телефонной книги при помощи TreeView для того, чтобы перечислять филиалы и отделы, а также ListView - для перечисления сотрудников, принадлежащих выбранному филиалу или отделу. В частности, ListView отобразит сотрудников, принадлежащих выбранному филиалу, а также тех, кто принадлежит всем отделам данного филиала. (В следующей статье мы рассмотрим способ расширения возможностей ListView, позволив пользователю добавлять, редактировать и удалять записи в телефонной книге.) Читайте далее, чтобы узнать больше об этом!
Исследование источника XML-данныхДанная статья демонстрирует способ отображения XML-данных в веб-странице при помощи элементов управления TreeView и ListView. До того как мы приступим к созданию страницы, давайте уделим пару минут исследованиию структуры XML-данных, которые мы собираемся отображать. Следующая XML-структура представляет собой телефонную книгу сотрудников компании. Компания состоит из филиалов и отделов. Филиал может состоять из отделов, а они, в свою очередь, из других отделов. Сотрудники могут принадлежать любому филиалу или отделу. XML-данные могут выглядеть следующим образом: <?xml version="1.0" encoding="utf-8"?> Указанный выше XML код демонстрирует то, что существует северный филиал (Northern Branch) с отделом маркетинга (Marketing). Сотрудники Miroslav и Scott работают в отделе Marketing. Отдел рекламы (Advertising) и принадлежащий ему отдел маркетинга (Marketing) являются местами где работают Chris, Bruce и Sam. Jisun работает в северном филиале (Northern Branch), но он не принадлежит какому-то определенному отделу данного филиала. В отделе исполнительных директоров (Executive Team) работают Davis и Kate - они также не принадлежат ни одному из филиалов. Заполнение элемента управления TreeView XML-даннымиНашей целью является создание веб-страницы, которая перечисляет все филиалы и отделы в виде иерархии в левой части, а принадлежащих сотрудников в правой. До того, как мы сможем отобразить сотрудников нам необходимо будет создать пользовательский интерфейс для перечисления филиалов и отделов. Поскольку существует иерархическая связь между филиалами и отделами, а также потому что у компании может быть неопределенное число филиалов и отделов, элемент управления TreeView является идеальным решением для вывода данной информации. Элемент управления XmlDataSource позволяет вам с легкостью отображать XML-данные в TreeView. Начните с добавления XmlDataSource на страницу. Далее, установите его свойство DataFile таким образом, чтобы оно указывало на XML-файл источник: <asp:XmlDataSource ID="treeSource" runat="server" DataFile="~/PhoneBook.xml" /> Сам по себе XmlDataSource не отображает ничего. Он просто осуществляет запрос XML-данных. Чтобы вывести информацию нам нужно добавить элемент управления TreeView и привязать его к XmlDataSource. Перетащите элемент TreeView из Toolbox на страницу и установите его свойство DataSourceID в ID элемента XmlDataSource (treeSource). Далее, создайте TreeNodeBinding для корневого элемента PhoneBook и TreeNodeBindings для дочерних элементов Branch и Department. TreeNodeBinding указывает TreeView какой XML-элемент и как он должен отобразить. <asp:TreeView ID="tvwPhoneBook" runat="server" DataSourceID="treeSource" Запустив страницы мы можем увидеть TreeView с узлами, соответствующими XML-структуре филиалов и отделов. Обратите внимание на то, что TreeView не включает в себя элементы Employee. Это потому, что в TreeNodeBinding нет элементов Employees. Загрузка записей о сотрудниках выбранного филиала или отделаКогда TreeView правильно отобразит филиалы и отделы, нашим следующим заданием будет вывод сотрудников выбранного филиала или отдела в правой части TreeView. Мы будем использовать LinqDataSource для того, чтобы загрузить информацию о сотрудниках из XML-данных телефонной книги. Начните с добавления элемента управления LinqDataSource к вашей странице: <asp:LinqDataSource ID="listSource" runat="server" /> Когда данные будут запрошены из LinqDataSource будет вызвано событие Selecting. При выполнении данного события нам необходимо программно получить соответствующий набор записей сотрудников по выбранному филиалу или отделу, поэтому создайте обработчик для события Selecting элемента LinqDataSource. Для того, чтобы создать данный обработчик события, щелкните дважды по элементу LinqDataSource в дизайнере страницы. Это создаст обработчик события в фоновом классе вашей страницы, названный listSource_Selecting и выглядящий следующим образом : protected void listSource_Selecting(object sender, LinqDataSourceSelectEventArgs e) В пределах данного метода нам необходимо выбрать информацию о сотрудниках (из XML-файла-источника), которые соответствуют выбранному узлу в TreeView. Выбранный в TreeView узел может быть получен при помощи свойства SelectedNode элемента TreeView, которое возвращает соответствующий экземпляр TreeNode выбранному узлу. Возвращенный TreeNode включает в себя свойство DataPath, которое диктует путь к элементу, представляемому TreeNodeXPath-запроса. (XPath - это язык для осуществления запросов к узлам в пределах XML-документа.) в такой форме, что он может быть сразу же использован в качестве К примеру, TreeNode представляющий первый отдел первого филиала (отдел маркетинга (Marketing) филиала Northern Branch) имеет значение свойства DataPath равное /*[position()=1]/*[position()=1]/*[position()=1]. Первая часть, /*[position()=1], представляет собой корневой элемент. Если перевести на русский , то это означает "дайте мне первый узел (position()=1) , который начинает корень". Вторая часть возвращает первый филиал, потому что, как и в первой части, она возвращает первый узел (position()=1), но на этот раз это будет первый узел корневого элемента (поскольку это то, что мы вернули при помощи первой части выражения XPath). Наконец, последняя часть представляет собой первый отдел первого филиала. Как вы уже могли заметить, XPath-индексирование, используемое position(), начинается с 1, а не с 0. (Для получения более подробной информации посетите уроки по XPath на сайте W3 Schools.) Чтобы получить соответствующий набор XML-элементов в событии Selecting элемента LinqDataSource мы будем использовать LINQ to XML. LINQ to XML является серией классов, появившихся в .NET Framework версии 3.5, которые способствуют работе с XML-данными. Одним из основных классов в LINQ to XML является XElement, который представляет XML-элемент. XElement также включает в себя метод Load , который мы можем вызвать для того, чтобы считывать содержимое XML-документа: XElement rootElement = XElement.Load(MapPath("PhoneBook.xml")); Обратите внимание на то, что метод Load из LINQ to XML возвращает корневой элемент. Если вы раньше уже работали с XML-данными в предыдущих версиях .NET Framework , то вы наверняка использовали класс XmlDocument, чей метод LoadChildNode. Мы должны помнить об этом при осуществлении запроса с XPath потому, что нам надо вырезать первую часть (адрес XPath к корневому элементу) из строки XPath. считывает весь документ, тем самым корневой элемент находится в первом Следующий код демонстрирует все это - он начинает со считывания в запросе XPath на наличие SelectedNode элемента TreeView и затем использует метод Substring для того, чтобы убрать первые n символов из строки XPath, где n является длиной XPath-выражения корневого элемента. // Получение XPath-адреса выбранного филиала или отдела Linq to XML предоставляет XElement с тремя методами: XPathEvaluate, XPathSelectElement и XPathSelectElements. Все три метода принимают XPath-выражение в качестве входного параметра. Как можно понять по их названиям, XPathEvaluateXPathSelectElement возвращает XElement , указанный выражением XPath , переданным в метод; и XPathSelectElements , который возвращает набор элементов. Мы будем использовать второй метод, XPathSelectElement. используется для определения выражения XPath, возвращая при этом скалярное значение такое, как значение атрибуты или текстовое содержимое XML-элемента; XElement parentElement = rootElement.XPathSelectElement(xPathQuery); Все немного усложняется тем, что нам необходимо выбрать записи сотрудников при помощи рекурсии. К примеру, если у нас есть филиал, выбранный в TreeView, то нам необходимо отобразить сотрудников всех отделов данного филиала , а также сотрудников филиалов, которые не принадлежат ни одному из отделов. Класс XElement содержит метод Descendants(elementName) , который может помочь в этом деле. Поэтому мы можем выбрать всех сотрудников из parentElement, независимо от глубины иерархии, при помощи: parentElement.Descendants("Employee") (Чтобы осуществить просмотр сотрудников без рекурсии - то есть, просто получить сотрудников выбранного филиала или отдела - используйте метод Elements(elementName).) Нас интересуют только имена и номера телефонов сотрудников. Простейшим способом (и более элегантным) вывода данной информации будет использование анонимного типа данных. Наш LINQ-запрос будет выглядеть следующим образом: var query = from employeeElement in parentElement.Descendants("Employee") Все что нам остается, так это вернуть информацию в LinqDataSource из события Selecting. Это выполнимо при помощи назначения результатов запроса параметру e.Results. e.Result = query; Отображение выбранных сотрудников в элементе управления ListView <asp:ListView ID="lvwEmployees" runat="server" DataSourceID="listSource" /> Элемент управления ListView работает на основе шаблонов. Нам необходимо определить шаблон для отображения всего ListView (LayoutTemplate) и затем шаблон для обработки индивидуальных элементов (ItemTemplate). Добавьте следующее объявление шаблона к вашему элементу управления ListView. <LayoutTemplate> Указанная выше разметка шаблона отображает результаты в HTML таблице (<table>) с двумя колонками. Чтобы получить имя и номер телефона каждого сотрудника используйте функцию привязки данных Eval("propertyName"). Конечным результатом будет страница, отображающая набор сотрудников в ListView в правой части элемента управления TreeView. Нам необходимо убедиться в том, что содержимое ListView будет повторно привязано к нему каждый раз когда пользователь выберет новый филиал или отдел из TreeView. Это реализуемо при помощи вызова метода ListView.DataBind() после каждого изменения SelectedNode элемента TreeView: protected void tvwPhoneBook_SelectedNodeChanged(object sender, EventArgs e) Добавление возможности сортировки и перелистыванияЭлемент управления ListView может предоставить пользователю возможность сортировать содержимое. ListView обрабатывает всю логику сортировки - нам всего лишь нужно добавить элемент управления LinkButton (или Button, либо ImageButton) в ListView, установить его свойства CommandName и CommandArgument в Sort и указать соответствующее название поля, по которому нам необходимо осуществить сортировку. К примеру, чтобы добавить LinkButton для сортировки результатов по имени (свойству Name), используйте следующий LinkButton: <asp:LinkButton ID="lnkSortName" runat="server" Text="Name" CommandName="Sort" CommandArgument="Name" /> Другим полезным улучшением является простая реализация возможности перелистывания содержимого. ASP.NET 3.5 включает в себя элемент управления DataPager, который может быть использован вместе с элементом ListView чтобы предоставить перелистываемый интерфейс. После добавления элемента управления DataPager на страницу установите его свойство PageSize в количество записей, которые вы хотите отобразить на одной странице. Далее установите свойство PagedControlID в ID элемента управления ListView, чьи данные будет возможно перелистать. Последним шагом будет указание полей для осуществления перелистывания (либо это будут ссылки Следующая/Предыдущая (Next/Previous), либо номера страниц в качестве ссылок и т.д.) - все остальное будет обработано элементами управления DataPager и ListView! (Для получения более подробной информации читайте статью про перелистывание посредством элементов управления ListView и DataPager) Следующее изображение демонстрирует ListView после того, как он был настроен на поддержку функциональности сортировки и перелистывания. Редактирование информации
ASP.NET включает в себя множество инструментов для отображения и редактирования XML-документов. Предыдущая статья по теме отображения информации демонстрировала способ создания веб-страницы, отображающей содержимое иерархического XML-файла при помощи элементов управления TreeView, ListView, XmlDataSource, LinqDataSource и примерно 50 строк кода. В частности, страница отображала содержимое фиктивной телефонной книжки с номерами сотрудников, которая позволяла хранить неопределенное число сотрудников, вложенных в отделы и филиалы компании. Элементы управления TreeView и XmlDataSource отображали различные филиалы и отделы, в то время как ListView и LinqDataSource отображали сотрудников, принадлежащих к выбранному отделу или филиалу. В дополнение к отображению информации ListView и LinqDataSource могут быть расширены таким образом, чтобы они поддерживали редактирование, вставку и удаление информации. Данная статья исследует способ обновления ListView и кода, тем самым позволяя посетителям добавлять, редактировать и удалять записи о телефонах сотрудников. Результатом данной статьи будет веб-страница, которая отображает XML-информацию и позволяет пользователю обновлять ее из простого, легкого в использовании, интерфейса веб-страницы. Читайте далее, чтобы узнать больше об этом! Настройка ListView для поддержки редактированияЭлемент управления ListView, предоставленный в ASP.NET версии 3.5, отображает серию записей из источника данных при помощи шаблонов. В дополнение к выводу информации элемент управления ListView также может быть использован для вставки, редактирования и удаления информации. Как и элемент GridView, элемент управления ListView позволяет редактирование "по месту". То есть, пользователь может выбрать определенную запись на редактирование из отображенного списка записей, выполнить все необходимые изменения и затем обновить информацию. ListView также предоставляет функциональность вставки. Принципы редактирования записей в ListView просты. Нам необходимо создать специальный шаблон для редактируемого элемента - данный шаблон, EditItemTemplate, используется для обработки элемента, редактируемого пользователем. В дополнение к этому нам необходимо добавить элемент управления Button, LinkButton или ImageButton (обычно в ItemTemplate), у которого свойство CommandName установлено в "Edit". При нажатии на данную кнопку выполняется постбэк и ListView повторно привязывается к своему источнику данных, при этом данный элемент обрабатывается при помощи EditItemTemplate. ItemTemplate достаточно прост - просто добавьте LinkButton (Button или ImageButton) со свойством CommandName установленным в "Edit": <ItemTemplate> До того как мы создадим EditItemTemplate для ListView, существует усложняющее обстоятельство в нашей ситуации - нам необходим для начала адрес. Вспомните, ведь отображаемые в ListView записи могут быть расположены на разных "уровнях" в XML-файле (т.е. различных филиалах/отделах). К примеру, при выборе определенного филиала или отдела из TreeView, ListView отображает не только сотрудников данного филиала или отдела, но и всех сотрудников дочерних филиалов и отделов. Необходимо иметь возможность определить правильный филиал или отдел, которому принадлежит данный сотрудник, при сохранении измененных значений обратно в XML-файл. Мы можем решить данную проблему путем запоминания адреса XPath XML-элемента Employee при загрузке его в ListView. Для этого нам необходимо добавить свойство в наш анонимный тип, возвращенный запросом LINQ. Текст в красном цвете XPathAddress: демонстрирует новый анонимный тип, var query = from employeeElement in parentElement.Descendants("Employee") В дальнейшем мы установим данное значение в качестве свойства CommandArgument элемента LinkButton "Update" в EditItemTemplate элемента ListView. Как вы можете заметить в указанном выше LINQ-запросе, XPathAddress было назначено значение, возвращенное методом getXPathAddress, которое нам необходимо создать в пределах класса с фоновым кодом ASP.NET-страницы. Метод итеративно пройдет по переданной структуре employeeElement к корневому элементу, тем самым выстраивая значение XPathAddress поднимаясь по дереву: private string getXPathAddress(XElement element) Теперь мы знаем выражение XPath для каждого объекта сотрудника и мы можем создать EditItemTemplate. Как вы могли бы уже заметить, свойству CommandArgument элемента LinkButton "Update" назначается значение XPathAddress , возвращенное LinqDataSource. Нам необходимо использовать данное значение в коде для обновления соответствующей записи сотрудника. Также обратите внимание на то, что вместо отображения значений имени и телефона сотрудника в Label мы используем веб-элемент управления TextBox. <EditItemTemplate> Вам наверняка интересно, почему мы использовали значение CommandName "XUpdate" вместо "Update". Если мы установим CommandName в "Update", то ListView попробует обновить изменения, автоматически вызвав метод Update элемента управления источником данных. Мы сопоставили данные собственноручно (в обработчике события Selecting элемента управления LinqDataSource), так что наш LinqDataSource в данном случае не может служить в качестве обновляющего источника. Поэтому нам необходимо самим написать метод, чтобы сохранить изменения. Сохранение отредактированной записи в XML-файлеНам необходимо выполнить код при нажатии на кнопку "XUpdate" (LinkButton). Событие ItemCommand элемента ListView вызывается, когда нажата кнопка со свойством CommandName. Поэтому нам необходимо создать обработчик для данного события. Помните, что данный обработчик события выполнится, когда будет нажата любая кнопка команды в ListView - это включает в себя и кнопки сортировки и перелистывания данного интерфейса. Следовательно, нам обязательно нужно проверить передаваемое значение e.CommandName на предмет того, чтобы оно было равно "XUpdate" до того, как мы будем продолжать. Когда будет нажата кнопка "XUpdate" мы должны будем сохранить изменения и возвратить ListView в его предыдущее состояние. Следующий код демонстрирует данную функциональность. Обратите внимание на то, что информация сохранена методом saveChanges - мы очень скоро создадим данный метод . protected void lvwEmployees_ItemCommand(object sender, ListViewCommandEventArgs e) Нашим последним заданием по отношению к редактированию записей является создание метода saveChanges, который сохраняет изменения в XML-файле. Как это демонстрирует указанный выше код, методу saveChanges передается два параметра:
Метод saveChanges загружает XML-элемент Employees из файла источника в качестве объекта XElement. Значения объекта XElement затем будут модифицированы чтобы соответствовать пользовательскому вводу. Наконец, обновленный объект XElement сохраняется в XML-файле. protected void saveChanges(string xPath, ListViewItem lvwItem) И это все! На данном этапе пользователь может редактировать существующие записи о телефонах сотрудников, изменяя их имя, номер телефона или и то, и другое. Удаление записей номеров телефонов сотрудниковУдаление номера телефона сотрудника работает по тому же принципу. Нам необходимо запомнить XPathAddress для сотрудников и передавать его обработчику события, ответственному за удаления записей. Как и в случае с редактированием, нам необходимо добавить кнопку Delete LinkButton (Button или ImageButton) к ItemTemplate элемента ListView. Установите его свойство CommandName в "XDelete" и свойство CommandArgument в значение XPathAddress удаляемого элемента. <asp:LinkButton ID="lnkDelete" runat="server" В обработчике события ItemCommand элемента управления ListView нам необходимо обработать команду "XDelete". Как и в случае с командой "XUpdate", мы вызовем вспомогательный метод (deleteItem), передавая ему выражение XPath и затем возвращая ListView в исходное состояние. case "XDelete": Метод deleteItem загружает объект XElement, основанный на предоставленном выражении XPath, удаляет его и затем сохраняет содержимое XML-файла. protected void deleteItem(string xPath) Добавление новых записей номеров телефонов сотрудниковНашим последним заданием будет предоставление пользователю возможности добавления новых записей о номерах телефонов сотрудников к выбранному филиалу или отделу. Вставка элемента из ListView является задачей схожей с редактированием записи - оба интерфейса определяются посредством шаблонов. Чтобы указать интерфейс для вставки, используйте InsertItemTemplate. Данный шаблон может быть отображен в качестве первого или последнего элемента в ListView. InsertItemTemplate (продемонстрирован ниже) выглядит так же для EditItemTemplate в том отношении, что он содержит TextBox для имени и номера телефона сотрудника. Вместо кнопки Update у нас будет кнопка Save (чье свойство CommandName установлено "XSave"). <InsertItemTemplate> Как и в случае с командами "XUpdate"и "XDelete", нам необходимо обработать команду "XSave" в обработчике события ItemCommand ListView. После сохранения элемента (посредством вызова saveNewItem), нам необходимо повторно привязать информацию к ListView, поэтому только что добавленный в список элемент будет отображен. case "XSave": Метод saveNewItem более сложен, чем методы saveChanges или deleteItem. Часть препятствий исходит из того факта, что каждый элемент в XML-файле имеет атрибут id с уникальным идентификатором для этого "уровня". Так что при добавлении нового элемента нам необходимо выбрать соответствующее значениеid. Метод saveNewItem выполняет следующие действия:
Следующий код демонстрирует это : protected void saveNewItem(ListViewItem lvwItem) И все! Небольшим усилием и парочкой строк кода мы создали работоспособное приложение ASP.NET для редактирования структурированной XML-информации. Главной идеей, на которой основано приложение, является связь между TreeView и ListView, что дало нам возможность редактировать табличные данные, хранимые в более иерархической XML-структуре. Большая часть работы выполняется за нас элементами TreeView, ListView, DataPager и LinqDataSource. Все что нам необходимо было сделать, так это написать код, который обрабатывает позицию записи в различных уровнях XML-структуры. Веселого программирования! |