Лабораторная работа №3

Цель:

1.  Получить представление о расширяемом языке разметке XML.

2.  Научиться читать и писать xml документ.

3.  Научиться работать с компонентом TreeView.

Содержание:

XML (англ. eXtensible Markup Language — расширяемый язык разметки; произносится [экс-эм-э́л]) — рекомендованный Консорциумом Всемирной паутины язык разметки, фактически представляющий собой свод общих синтаксических правил. XML — текстовый формат, предназначенный для хранения структурированных данных (взамен существующих файлов баз данных), для обмена информацией между программами, а также для создания на его основе более специализированных языков разметки (например, XHTML), иногда называемых словарями. XML является упрощённым подмножеством языка SGML.

Целью создания XML было обеспечение совместимости при передаче структурированных данных между разными системами обработки информации, особенно при передаче таких данных через Интернет. Словари, основанные на XML (например, RDF, RSS, MathML, XHTML, SVG), сами по себе формально описаны, что позволяет программно изменять и проверять документы на основе этих словарей, не зная их семантики, то есть не зная смыслового значения элементов.

Пример размеченного XML-документа:

<?xml version="1.0" encoding="UTF-8"?>

<recipe name="хлеб" preptime="5" cooktime="180">

<title>Простой хлеб</title>

<ingredient amount="3" unit="стакан">Мука</ingredient>

<ingredient amount="0.25" unit="грамм">Дрожжи</ingredient>

<ingredient amount="1.5" unit="стакан">Тёплая вода</ingredient>

<ingredient amount="1" unit="чайная ложка">Соль</ingredient>

<Instructions>

<step>Смешать все ингредиенты и тщательно замесить.</step>

<step>Закрыть тканью и оставить на один час в тёплом помещении.</step>

<step>Замесить ещё раз, положить на противень и поставить в духовку.</step>

</Instructions>

</recipe>

Остальная часть этого XML-документа состоит из вложенных элементов, некоторые из которых имеют атрибуты и содержимое. Элемент обычно состоит из открывающего и закрывающего тегов, обрамляющих текст и другие элементы. Открывающий тег состоит из имени элемента в угловых скобках, например, «<step>»; закрывающий тег состоит из того же имени в угловых скобках, но перед именем ещё добавляется косая черта, например, «</step>». Содержимым элемента (англ. content) называется всё, что расположено между открывающим и закрывающим тегами, включая текст и другие (вложенные) элементы.

Для обозначения элемента без содержания, называемого пустым элементом, необходимо применять особую форму записи, состоящую из одного тега, в котором после имени элемента ставится косая черта. Если в DTD элемент не объявлен пустым, но в документе он не имеет содержания, для него допускается применять такую форму записи. Например:

<foo></foo>

<foo />

<foo/>

Открываем проект Windows Forms Application. Открываем дизайнер и рисуем следующий дизайн:


Для этого на пустую форму накладываем компонент MenuStrip, а затем SplitContainer. Создаем два пункта меню — Load и Write. Растягиваем сплит-контейнер на всю панель с помощью пункта Dock in parent container. На первую панель накладываем компонент TreeView и тоже растягиваем его на всю панель. На правую панель накладываем компонент FlowPanelLayout и растягиваем его на всю панель. А в FlowLayoutPanel четыре кнопки. И напоследок перетягиваем два компонента OpenFileDialog и SaveFileDialog.

Далее дважды щелкнув по каждой кнопке, пункту меню и объектам openFileDialog и saveFileDialog создаем обработчики событий для каждого пункта.

Для кнопки Add Brother код обработки событий следующий:

private void add_brother_button_Click(object sender, EventArgs e)

{

if (treeView1.SelectedNode == null || treeView1.SelectedNode. Parent == null)

{

treeView1.Nodes. Add("Tree Node");

}

else

{

treeView1.SelectedNode. Parent. Nodes. Add("Tree Node");

}

if (delete_button. Enabled == false)

{

delete_button. Enabled = true;

rename_button. Enabled = true;

}

}

где treeView1 – объект класса TreeView. Свойство SelectedNode – указывает на выбранный пункт дерева. Свойство объекта класса TreeNode Parent указывает на родителя данного пункта дерева. Свойство Nodes указывает на объект TreeNodeCollection, который содержит все объекты-потомки дерева или пункта дерева TreeNode. А метод Add(TreeNode) объекта TreeNodeCollection добавляет объект TreeNode в данную коллекцию. Здесь мы проверяем выбран ли сейчас какой-нибудь node и есть ли у него родитель. И если есть, то добавляем новый элемент в ту же коллекцию, что и выбранный node. Если нет, то добавляем элемент в корень. Также, разблокируем кнопку удалить и переименовать в том случае, если до этого они были заблокированы.

Для кнопки Add Child код обработки событий следующий:

НЕ нашли? Не то? Что вы ищете?

private void add_button_Click(object sender, EventArgs e)

{

if (treeView1.SelectedNode == null)

{

treeView1.Nodes. Add("Tree Node");

}

else

treeView1.SelectedNode. Nodes. Add("Tree Node");

if (delete_button. Enabled == false)

{

delete_button. Enabled = true;

rename_button. Enabled = true;

}

}

Все тоже самое, только добавляем внутрь выбранного node.

Для кнопки Rename код такой:

private void rename_button_Click(object sender, EventArgs e)

{

if(treeView1.SelectedNode != null)

treeView1.SelectedNode. BeginEdit();

}

Метод BeginEdit() переключает Node дерева в режим переименования.

Для кнопки Delete код такой:

private void delete_button_Click(object sender, EventArgs e)

{

if (treeView1.SelectedNode != null)

{

treeView1.Nodes. Remove(treeView1.SelectedNode);

}

if (treeView1.Nodes. Count == 0)

{

delete_button. Enabled = false;

rename_button. Enabled = false;

}

}

Удаляем выбранный node и запрещаем кнопки delete и rename в том случае, если дерево пустое.

Теперь пришло время к работе с XML. Добавьте в ваш проект новый xml файл. Этот файл должен содержать следующий текст:

<?xml version="1.0" encoding="utf-8" ?>

<treeroot>

<treenode value="Tree Node 1">

<treenode value="Sub Node 1"/>

<treenode value="Sub Node 2"/>

<treenode value="Sub Node 3"/>

</treenode>

<treenode value="Tree Node 2">

<treenode value="Sub Node 1"/>

<treenode value="Sub Node 2"/>

</treenode>

<treenode value="Tree Node 1">

<treenode value="Sub Node 1"/>

</treenode>

</treeroot>

Это xml с содержимым нашего дерево. Тут тэг treeroot означает корень дерева, а тэги treenode с аттрибутом value – ветви и листья (node) нашего дерева.

Для того чтобы мы могли выбрать нужный нам xml файл, пункт меню load будет вызывать диалог с выбором нужного нам файла:

private void loadToolStripMenuItem_Click(object sender, EventArgs e)

{

openFileDialog1.Filter = "XML files (*.xml)|*.xml";

openFileDialog1.ShowDialog();

}

Здесь свойство Filter – это фильтр для xml файлов в диалоге, а метод ShowDialog вызывает его.

За кнопку «Открыть» в этом диалоге отвечает уже созданный нами метод openFileDialog1_FileOk:

private void openFileDialog1_FileOk(object sender, CancelEventArgs e)

{

XmlDocument doc = new XmlDocument();

doc. Load(openFileDialog1.FileName);

treeView1.BeginUpdate();

treeView1.Nodes. Clear();

foreach (XmlNode node in doc. ChildNodes)

{

if (node. Name == "treeroot")

{

AddTreeNodes(treeView1.Nodes, node);

}

}

treeView1.EndUpdate();

if (treeView1.Nodes. Count > 0)

{

delete_button. Enabled = true;

rename_button. Enabled = true;

}

}

Класс XmlDocument представляет собой класс, представляющий xml документ. Его метод Load, в который передается имя файла с диалога загружает данные из xml-файла во внутренние структуры объекта класса XmlDocument. Метод класса TreeView BeginUpdate() запрещает отображение изменений в дереве до вызова метода EndUpdate(). Метод Clear класса TreeNodeCollection удаляет все объекты коллекции. Свойство ChildNodes класса XmlDocument указывает на объект класса XmlNodeList, который хранит все дочерние элементы xml документы. В случае с нашим xml файлом это объявление о версии xml и кодировке и элемент treeroot. Класс XmlNode представляет узел дерева. Это более глобальное понятие в модели DOM, чем элемент и класс XmlElement. Класс XmlElement представляет узел дерева ограниченный открывающим и закрывающим тэгом. Фактически узлом являются такие типы как:


Document

XmlDocument Class

Контейнер для всех дочерних узлов.

DocumentFragment

XmlDocumentFragment Class

Фрагмент документа

Element

XmlElement Class

Элемент

Attr

XmlAttribute Class

Аттрибут

ProcessingInstruction

XmlProcessingInstruction Class

Инструкция по обработке

Comment

XmlComment Class

Комментарий

Text

XmlText Class

Текс аттрибута или элемента

CDATASection

XmlCDataSection Class

Раздел CDATA.


Свойство Name класса XmlNode определяет имя узла. Поскольку процесс прохода по дереву xml и дереву TreeView подразумевает исследование более глубоких уровней дерева, нам необходима рекурсионная функция, которая будет исследовать содержимое дочерних элементов. Это функция AddTreeNodes, которая получает в качестве входных параметров ссылку на коллекцию элементов TreeNode и узел XML, который надо исследовать. Вот содержимое этой функции:

private void AddTreeNodes(TreeNodeCollection nodeCollection, XmlNode node)

{

foreach (XmlNode n in node. ChildNodes)

{

if (n. Name == "treenode")

{

TreeNode treeNode = new TreeNode(n. Attributes["value"].Value);

nodeCollection. Add(treeNode);

if (n. ChildNodes. Count > 0)

AddTreeNodes(treeNode. Nodes, n);

}

}

}

Здесь мы проверяем, что нужный нам узел имеет имя treenode и добавляем в дерево элемент с названием, которое содержит аттрибут value выбранного XML элемента. И проверяем, если данный xml узел содержит потомков, вызываем эту функцию еще раз, передавая уже новую коллекцию объектов TreeNode и рассматриваемый нами XML-узел.

Процедура сохранения содержимого дерева TreeView в XML очень похожа:

private void writeToolStripMenuItem_Click(object sender, EventArgs e)

{

saveFileDialog1.Filter = "XML files (*.xml)|*.xml";

saveFileDialog1.ShowDialog();

}

private void saveFileDialog1_FileOk(object sender, CancelEventArgs e)

{

XmlDocument doc = new XmlDocument();

doc. AppendChild(doc. CreateXmlDeclaration("1.0", "utf-8", ""));

XmlElement element = doc. CreateElement("treeroot");

AddXmlElements(doc, treeView1.Nodes, element);

doc. AppendChild(element);

doc. Save(saveFileDialog1.FileName);

}

private void AddXmlElements(XmlDocument doc, TreeNodeCollection nodeCollection, XmlElement elem)

{

foreach (TreeNode n in nodeCollection)

{

XmlElement appendedElement = doc. CreateElement("treenode");

XmlAttribute attr = doc. CreateAttribute("value");

attr. Value = n. Text;

appendedElement. Attributes. Append(attr);

elem. AppendChild(appendedElement);

if (n. Nodes. Count > 0)

AddXmlElements(doc, n. Nodes, appendedElement);

}

}

При создании Xml файла необходимо участие объекта класса XmlDocument, поскольку объекты классов XmlElement, XmlAttribute и пр. нельзя получить традиционным способом с помощью оператора new и вызова конструктора. Метод CreateXmlDeclaration создает элемент с объявлением о версии и кодировки документа. Метод AppendChild делает элемент переданный в качестве входного параметра потомком вызвавшего метод. В случае с XmlDocument имеются ввиду корневые элементы. С помощью CreateElement создаются xml-элементы. Рекурсивная функция AddXmlElements получает в качестве входных параметров объект класса XmlDocument, ветку дерева TreeView и XmlElement, в который надо добавить элементов-потомков. По аналогии с открытием, каждый объект класса TreeNode проверяется на наличие потомков и по необходимости функция AddXmlElements вызывается вновь.