Программирование для карманных компьютеров
Шрифт:
16. После проделанных манипуляций вторая созданная форма должна выглядеть так, как показано на рис. 6.14.
Рис. 6.14. Форма редактирования и добавления записи.
Работа с XML
Одной из полезных особенностей. NET CF является возможность легкой записи информации в XML-формате и последующее считывание ее из XML-файла. Из трех классов, предназначенных для работы с XML, в приложении будут использоваться классы XMLTextReader и XMLDocument. На самом деле можно было бы обойтись только классом XMLDocument, но класс XMLTextReader весьма полезен, и не хотелось бы пропускать демонстрацию его применения.
Фомат XML является форматом разметки (структурирования записи) данных. Как и HTML, XML основан на концепции тегов разметки. Но в отличие от HTML, теги XML не имеют предопределенных значений. Это значит, что, в общем случае, получатель документа может знать, как теги вложены друг в друга, какое положение один тег занимает относительно другого в документе, но ничего не может сказать относительно назначения этих тегов и информации, записанной в них. Придание значения той или иной информации, структурированной в XML-файле, ложится на плечи программы, разбирающей этот файл.
Код простейшего XML-файла показан в листинге 6.4.
Листинг 6.4<?xml version="1.0" encoding="windows-1251"?>
<Y2005>
<Date>
<Index value = "0"/>
</Date>
<Date>
<Index value = "1"/>
</Date>
<Date>
<Index>2
</Index>
</Date>
</Y2005>Первая
Любой XML-документ должен начинаться с одного тега, который называется корневым тегом. В нашем случае корневым тегом является тег Y2005.
Что нам надо хранить в нашем XML-документе? Это должна быть информация о дате, для которой мы выполняем хронометраж, о категории работы, о времени начала работы, о времени ее окончания и примечания к каждой записи, если они необходимы. Структура нашего XML-файла приведена в листинге 6.5.
Листинг 6.5Заголовочная часть:
<?xml version="1.0" encoding="windows-1251"?>
Корневой тег:
<Y2005>
Столько тегов Date, сколько дней мы будем контролировать:
<Date value = «20.08.2005» Cnt = "1">
Столько тегов Index, сколько отрезков времени мы учтем за день:
<Index Category="Work1" StartOf= «00.00» EndOf = «00.20» Note=""/>
<Index Category="Misc" StartOf= «00.20» EndOf = «00.40» Note=""/>
</Date>
…
</Y2005>В каждом теге Date есть два атрибута. Атрибут value несет в себе дату, а Cnt – количество записей за день. В каждом теге Index есть четыре атрибута. В атрибуте Category указывается категория занятия, в атрибуте StartOf – время начала, в атрибуте EndOf – время окончания, а атрибут Note предназначен для хранения примечания.
Файл с такой структурой должен быть создан при первом запуске программы, прочитан и отредактирован при каждом последующем запуске.
Упражнение 6.2 (продолжение)
17. При запуске нашего приложения необходимо проверить, существует ли файл 2005.xml, в котором записаны результаты контроля времени, и файл Category.xml, в котором содержится список категорий. Если данные файлы не существуют, то их надо создать. Если файлы все еще существуют, то их нужно загрузить. Эти операции будут проводиться в обработчике события загрузки основной формы приложения. Но перед этим в класс формы нужно добавить две переменные. В окне Solution Explorer нужно выделить файл Form1.vb и из контекстного меню выполнить для этого файла команду View Code. Затем в самое начало кода нужно добавить строки, приведенные в листинге 6.6.
Листинг 6.6Public Class Form1
Inherits System.Windows.Forms.Form
Friend WithEvents ListView1 As System.Windows.Forms.ListView
Friend WithEvents MainMenu1 As System.Windows.Forms.MainMenu
\'Добавленные переменные
Dim D As DateTime
Dim dirStr As String18. Теперь можно создать процедуру обработчика события загрузки формы, код которой приведен в листинге 6.7. Листинг 6.7
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles MyBase.Load
\'Если файл с данными отсутствует в текущем каталоге, мы его создаем, для
\'этого сначала получаем полный путь каталога, в котором расположена
\'программа, в строковую переменную dirStr
dirStr = IO.Path.GetDirectoryName_
(Reflection.Assembly.GetExecutingAssembly.GetName.CodeBase.ToString)
\'Проверяем, есть ли файл c заданным именем по полученному пути
If Not System.IO.File.Exists(dirStr + «\2005.xml») Then
\'Если файла нет – создаем его
\'Создаем экземпляр XmlDocument
Dim myxmlDoc As New Xml.XmlDocument
\'Создаем корневой тег и добавляем его к документу
Dim oROOT As Xml.XmlElement = myxmlDoc.CreateElement(«Y2005»)
myxmlDoc.AppendChild(oROOT)
\'Получаем текущую дату в переменную myDate
Dim myDate As Date = Date.Now
myDate = DateAdd(DateInterval.Day, – 1, myDate)
\'Заполняем файл тегами Data от сегодняшнего дня до конца текущего года
While Not myDate.ToShortDateString = «31.12.2005»
myDate = DateAdd(DateInterval.Day, 1, myDate)
Dim oData As Xml.XmlElement = myxmlDoc.CreateElement(«Data»)
oROOT.AppendChild(oData)
Dim oValue As Xml.XmlAttribute = _ myxmlDoc.CreateAttribute(«value»)
Dim oIndex As Xml.XmlAttribute = _ myxmlDoc.CreateAttribute(«Cnt»)
oValue.InnerText = myDate.ToShortDateString
oIndex.InnerText = «-1»
oData.SetAttributeNode(oValue)
oData.SetAttributeNode(oIndex)
End While
\'Создаем заголовочную часть XML файла
Dim myPI As Xml.XmlProcessingInstruction = _
myxmlDoc.CreateProcessingInstruction_
(«xml», "version= 1.0 encoding=\'windows-1251 ")
myxmlDoc.InsertBefore(myPI, myxmlDoc.ChildNodes(0))
\'Сохраняем созданный документ
myxmlDoc.Save(dirStr + «\2005.xml»)
End If
\'Если файл со списком категорий отсутствует в текущем каталоге, мы его
\'создаем с пятью категориями Work1, Work2, Work3, Misc, Trash
If Not System.IO.File.Exists(dirStr + «\Category.xml») Then
If Not System.IO.File.Exists(dirStr + «\Category.xml») Then
Dim myxmlDoc As New Xml.XmlDocument
Dim oROOT As Xml.XmlElement = myxmlDoc.CreateElement(«Categoty»)
myxmlDoc.AppendChild(oROOT)
Dim oWork1 As Xml.XmlElement = myxmlDoc.CreateElement(«Work1»)
oROOT.AppendChild(oWork1)
Dim oWork2 As Xml.XmlElement = myxmlDoc.CreateElement(«Work2»)
oROOT.AppendChild(oWork2)
Dim oWork3 As Xml.XmlElement = myxmlDoc.CreateElement(«Work3»)
oROOT.AppendChild(oWork3)
Dim oMisc As Xml.XmlElement = myxmlDoc.CreateElement(«Misc»)
oROOT.AppendChild(oMisc)
Dim oTrash As Xml.XmlElement = myxmlDoc.CreateElement(«Trash»)
oROOT.AppendChild(oTrash)
Dim myPI As Xml.XmlProcessingInstruction = _
myxmlDoc.CreateProcessingInstruction_
(«xml», "version= 1.0 encoding=\'windows-1251 ")
myxmlDoc.InsertBefore(myPI, myxmlDoc.ChildNodes(0))
myxmlDoc.Save(dirStr + «\Category.xml»)
End If
D = DateTime.Now
Label1.Text = System.DateTime.Today.ToShortDateString
\'Вызываем процедуру загрузки данных в ListView
Data_Load(System.DateTime.Today.ToShortDateString)
End SubЧтобы создать и сохранить новый XML-документ, мы воспользовались классом XMLDocument, который позволяет представить существующий XML-документ в виде объекта с набором полей и методов для манипуляции этими полями. Он позволяет создавать и удалять элементы XML как по имени, так и по индексу, осуществлять поиск нужного элемента, заменять или дублировать элементы, сохранять измененный документ. Единственным недостатком этого класса является то, что документ полностью загружается в создаваемый объект. Поскольку на Pocket PC и файловая система, и оперативная память программы физически являются одним и тем же устройством, то просто происходит дублирование информации на время работы с документом.
19. Процедура загрузки данных для текущего дня в компонент ListView приведена в листинге 6.8.
Листинг 6.8Private Sub Data_Load(ByVal MyDate As String)
\'Открываем файл, для это сначала получаем полный путь каталога,
\'в котором расположена программа в строковую переменную dirStr
dirStr = IO.Path.GetDirectoryName_
(Reflection.Assembly.GetExecutingAssembly.GetName.CodeBase.ToString)
\'Создаем экземпляр объекта XmlTextReader
Dim xmlrdrMy As New Xml.XmlTextReader(dirStr + «\2005.xml»)
Dim MyCount, MyIndex As Integer
Dim lvItem As ListViewItem
\'Не реагируем на пробелы и управляющие символы
xmlrdrMy.WhitespaceHandling = xmlrdrMy.WhitespaceHandling.None
\'Переходим к содержимому, пропуская заголовочную часть
xmlrdrMy.MoveToContent
\'В цикле считываем значения из файла, добавляем новую строку в ListView
\'для каждого тега Index и копируем значения атрибутов тега Index в
\'соответствующие
ListView1.Items.Clear
ListView1.Refresh
While Not xmlrdrMy.EOF
If xmlrdrMy.Name = «Data» Then
If xmlrdrMy.GetAttribute(«value») = MyDate Then
MyIndex = CInt(xmlrdrMy.GetAttribute(«Cnt»))
For MyCount = 0 To MyIndex – 1
xmlrdrMy.Read
lvItem = New ListViewItem(xmlrdrMy.GetAttribute(«Category»))
ListView1.Items.Add(lvItem)
ListView1.Items.Item(MyCount). SubItems.Add_
(xmlrdrMy.GetAttribute(«StartOf»))
ListView1.Items.Item(MyCount). SubItems.Add(xmlrdrMy.GetAttribute(«EndOf»))
ListView1.Items.Item(MyCount). SubItems.Add(xmlrdrMy.GetAttribute(«Note»))
Next
End If
End If
\'Считываем следующий элемент из файла XML
xmlrdrMy.Read
End While
\'Закрываем файл
xmlrdrMy.Close
End SubВ этом случае мы воспользовались классом XmlTextReader для считывания информации из файла. Класс XmlTextReader также позволяет разбирать содержимое XML-документа, но он не создает копию файла в памяти, а считывает его в один проход элемент за элементом. Переход к следующему элементу осуществляется вызовом метода Read.
20. Теперь нужно создать процедуру сохранения данных текущего дня в файл. Эта процедура должна запускаться при переходе от одной даты к другой, а также в момент закрытия приложения. Ее код приведен в листинге 6.9.
Листинг 6.9Private Sub Data_Save(ByVal MyDate As String)
Dim MyCount, MyIndex As Integer
\'Создаем объект XmlDocument
Dim myxml As New Xml.XmlDocument
dirStr = IO.Path.GetDirectoryName_
(Reflection.Assembly.GetExecutingAssembly.GetName.CodeBase.ToString)
\'Загружаем файл в объект XmlDocument
myxml.Load(dirStr + «\2005.xml»)
\'Перебираем все узлы уровня Data и находим сохраняемую дату
For MyCount = 0 To myxml.ChildNodes(1). ChildNodes.Count – 1
If myxml.ChildNodes(1). ChildNodes(MyCount). Attributes(0). InnerText = MyDate Then
\'Удаляем все содержимое этого узла
myxml.ChildNodes(1). ChildNodes(MyCount). RemoveAll
\'И заполняем его текущим содержимым полей ListView. При этом мы создаем
\'заново атрибуты узла Data и узлы Index со всеми атрибутами для каждой
\'строки ListView
\'Создаем атрибут value для текущего узла Data
Dim myValue As Xml.XmlAttribute = myxml.CreateAttribute(«value»)
\'Присваиваем атрибуту value значение текущей даты
myValue.InnerText = MyDate
\'Добавляем атрибут value к текущему узлу Data
myxml.ChildNodes(1). ChildNodes(MyCount). Attributes.Append(myValue)
\'Проделываем предыдущие операции с атрибутом Cnt
Dim mIndex As Xml.XmlAttribute = myxml.CreateAttribute(«Cnt»)
mIndex.InnerText = ListView1.Items.Count
myxml.ChildNodes(1). ChildNodes(MyCount). Attributes.Append(mIndex)
\'Заполняем узел Data вложенными элементами Index
For MyIndex = 0 To ListView1.Items.Count – 1
Dim oIndex As Xml.XmlElement = myxml.CreateElement(«index»)
myxml.ChildNodes(1). ChildNodes(MyCount). AppendChild(oIndex)
Dim oCategory As Xml.XmlAttribute = myxml.CreateAttribute(«Category»)
oCategory.InnerText = ListView1.Items(MyIndex). SubItems(0). Text
myxml.ChildNodes(1). ChildNodes(MyCount). ChildNodes_
(MyIndex). Attributes.Append(oCategory)
Dim oStartOf As Xml.XmlAttribute = myxml.CreateAttribute(«StartOf»)
oStartOf.InnerText = ListView1.Items(MyIndex). SubItems(1). Text
myxml.ChildNodes(1). ChildNodes(MyCount). ChildNodes_
(MyIndex). Attributes.Append(oStartOf)
Dim oEndOf As Xml.XmlAttribute = myxml.CreateAttribute(«EndOf»)
oEndOf.InnerText = ListView1.Items(MyIndex). SubItems(2). Text
myxml.ChildNodes(1). ChildNodes(MyCount). ChildNodes_
(MyIndex). Attributes.Append(oEndOf)
Dim oNote As Xml.XmlAttribute = myxml.CreateAttribute(«Note»)
oNote.InnerText = ListView1.Items(MyIndex). SubItems(3). Text
myxml.ChildNodes(1). ChildNodes(MyCount). ChildNodes_
(MyIndex). Attributes.Append(oNote)
Next
\'Если найден и заполнен требуемый узел Data, прекращаем перебор узлов
Exit For
End If
Next
\'Сохраняем документ в файл
myxml.Save(dirStr + «\2005.xml»)
End Sub21. Остальные процедуры будут не столь объемны. После того как файл был загружен и его содержимое было отображено в элементе ListView, надо написать обработчики событий для щелчков на компонентах PictureBox, которые играют роль кнопок. Поскольку при написании этих обработчиков понадобятся переменные, через которые данные будут передаваться из формы в форму, нужно создать модуль и объявить в нем эти переменные.
22. Выполнить команду Project ? Add Module. На экран будет выведено соответствующее диалоговое окно. В строке Name нужно вместо предлагаемого имени указать имя Data, после чего нужно нажать кнопку Open. В окне редактора кода будет открыт пустой модуль. В нем нужно указать код, приведенный в листинге 6.10.
Листинг 6.10Module Data
Public categorySt, startOfSt, endOfSt, noteSt As String
End Module23. Процедура сохранения информации текущего дня при закрытии приложения описана в листинге 6.11. Листинг 6.11
Private Sub Form1_Closing(ByVal sender As Object, ByVal e As
System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
Data_Save(Label1.Text)
End Sub24. Обработчик щелчка на кнопке PictureBox1, который осуществляет переход к предыдущему дню, приведен в листинге 6.12. Листинг 6.12
Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles PictureBox1.Click
Data_Save(Label1.Text)
D = DateAdd(DateInterval.Day, – 1, D)
Label1.Text = D.ToShortDateString
Data_Load(D.ToShortDateString)
End Sub25. Обработчик щелчка на кнопке PictureBox2, который осуществляет переход к следующему дню, приведен в листинге 6.13. Листинг 6.13
Private Sub PictureBox2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles PictureBox2.Click
Data_Save(Label1.Text)
D = DateAdd(DateInterval.Day, 1, D)
Label1.Text = D.ToShortDateString
Data_Load(D.ToShortDateString)
End Sub26. Обработчик щелчка на кнопке PictureBox3, который отвечает за добавление очередной записи в текущий день, показан в листинге 6.14. Листинг 6.14
Private Sub PictureBox3_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles PictureBox3.Click
Dim lvIt As ListViewItem
\'Создаем диалоговую форму редактирования
Dim MyForm As New Form2
\'Устанавливаем флаг, показывающий, что был щелчок на кнопке 3
MyForm.Send = 3
\'Если ListView не пустой, копируем значения полей текущей записи в
\'переменные так, что время конца в текущей записи станет временем начала
\'в создаваемой записи
If ListView1.Items.Count > 0 Then
categorySt = ListView1.Items.Item_
(ListView1.Items.Count – 1). SubItems(0). Text
startOfSt = ListView1.Items.Item_
(ListView1.Items.Count – 1). SubItems(2). Text
endOfSt = startOfSt
noteSt = ""
Else
categorySt = ""
startOfSt = ""
endOfSt = ""
noteSt = ""
End If
\'Выводим на экран форму редактирования записи
If MyForm.ShowDialog = DialogResult.OK Then
\'И если редактирование завершилось щелчком на кнопке OK, добавляем новую
\'запись в ListView
lvIt = New ListViewItem(categorySt)
ListView1.Items.Add(lvIt)
ListView1.Items.Item.(ListView1.Items.Count – 1). SubItems.Add(startOfSt)
ListView1.Items.Item.(ListView1.Items.Count – 1). SubItems.Add(endOfSt)
ListView1.Items.Item.(ListView1.Items.Count – 1). SubItems.Add(noteSt)
End If
End Sub27. Обработчик щелчка на кнопке PictureBox4, который отвечает за редактирование текущей записи, показан в листинге 6.15. Листинг 6.15
Private Sub PictureBox4_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles PictureBox4.Click
\'Если в ListView есть выделенная строка, тогда редактируем
If ListView1.SelectedIndices.Count > 0 Then
Dim MyForm As New Form2
\'Сообщаем форме редактирования, что был щелчок на кнопке 4
MyForm.Send = 4
\'Копируем значения полей записи в переменные
categorySt = ListView1.Items(ListView1.SelectedIndices_(0)). SubItems(0). Text
startOfSt = ListView1.Items(ListView1.SelectedIndices(0)). SubItems(1). Text
endOfSt = ListView1.Items(ListView1.SelectedIndices(0)). SubItems(2). Text
noteSt = ListView1.Items(ListView1.SelectedIndices(0)). SubItems(3). Text
\'Если редактирование завершилось щелчком на кнопке OK, копируем возвращенные
\'значения переменных в соответствующие поля ListView
If MyForm.ShowDialog = DialogResult.OK Then
ListView1.Items(ListView1.SelectedIndices(0)). SubItems(0). Text = categorySt
ListView1.Items(ListView1.SelectedIndices(0)). SubItems(1). Text = startOfSt
ListView1.Items(ListView1.SelectedIndices(0)). SubItems(2). Text = endOfSt
ListView1.Items(ListView1.SelectedIndices(0)). SubItems(3). Text = noteSt
End If
End If
End Sub