tandard applications, of course, use a database, but if you’ve ever investigated the idea of building an application using only XML files as a data store, you know that it has been rather challenging. The .NET Framework 1.x lacked some basic XML-enabling features, which made it difficult to build applications that can handle multiple XML files. The .NET Framework 2.0 brings with it some interesting new features and controls that make this easy:
- The DataTable has been enhanced to support loading and writing XML data.
- The DataGridView control lets you easily display data from multiple tables in a DataSet.
This article builds a simple time tracking application that exploits these new features. The application stores all of its data in multiple XML files. It explores how to read and write multiple XML files into a dataset using the new DataTable XML support (which previously wasn’t possible) and how to bind XML data to the DataGridView control. It showcases how the DataGridView can be used to display and write data from an XML file and to look up data in different XML files. These latter functions required quite a bit of code in earlier versions of the framework; moreover, loading XML data into the DataTable was not possible.
TimeTracker is a simple application I built to help me keep track of the time I spend on various activities. It requires two XML files: Activities.XML and Tasks.XML. The activities file stores the daily entries, including the tasks performed, their descriptions, and start and end times. The tasks file stores the categories for various kinds of tasks, for instance, e-mail, browsing, coding etc. Figure 1 shows the structure of these two XML files and their relationship to each other.
|Author’s Note: Throughout the remainder of this article, .NET refers to .NET Framework 2.0.
Normally you would be tempted to create the two XML files required for the application by hand. But there are two reasons why you shouldn’t. First it’s dirty and murky messing with XML, especially when you are dealing with the schema definitions and the relationships between the XML files. The second reason is you really don’t need to do it manually as the DataSet designer allows you to define it visually and write out the XML files with their schema using very simple code. Unfortunately, most people aren’t aware of this functionality, which has been updated with simplified terminology.
Building the Application
Fire up Visual Studio 2005 beta 2 and choose a new Windows Application template, name the project DeskTimeKeeperXML or DTKXML. Add a new file of type DataSet to the project and name it dsActivitiesTasks. You need to add two tables: activities and tasks (see Figure 1). Simply right-click the design surface and select Add->DataTable.
Similarly, you can add a column for each table by right-clicking the table name and selecting Add->Column. Properties for each column are set by selecting the column name and setting its properties in the Properties window.
|Author’s Note: XML is case-sensitive, hence, be careful while defining names of tables and columns.
Define the properties of the DataTable elements for each table as shown in Tables 1 and 2:
Table 1. Activites Table:
Table 2. Tasks Table:
Relationship Between the Tables
Defining the relationship between the two DataTables is a simple matter of right-clicking the activities or task table in design view and choosing Add Relation. As shown in Figure 2 you need to change the foreign key value to taskid in the activities table. That completes the data design part of the application.
Designing the Application UI
The application consists of two forms. The first allows a user to enter activity information and the second allows for entry of the various kinds of tasks.
Rename the default form in the application to frmDeskTimeKeeper. Drop a DataGridView control from the Toolbox on the form. Next you need to specify the datasource for the DataGridView control. Select the DataGridView control, on the upper right-hand side, select the SmartTag that appears and specify dsActivitiesTasks, which you have already created, as the datasource (see Figure 3). (Note: Select the dataset dsActivitiesTasks not the DataTables (activities or tasks).)
|Figure 5. Form Activities UI: In this dialog, you’ll set the text for three buttons used by the application.
Next you need to set the DataMember property for the DataGridView to the activities DataTable. As you do this you will notice that the designer now shows the columns of the activities table on the DataGridView.
Customizing the DataGridView
The DataGridView shows all the columns of the activities DataTable. Some columns, such as the auto-generated activityid column, don’t need to be displayed to the end user. Moreover, the taskid should ideally display the tasks instead of the IDs, and the user should be able to choose from the tasks DataTable. The taskid column would be better suited as a drop-down column, displaying the tasks, in order to simplify data entry.
You need to edit the properties of the columns in the DataGridView to achieve these effects. Select the SmartTag on the DataGridView control and choose Edit Columns. Set the properties as shown in Table 3:
Table 3. Setting Column Properties:
After customizing the DataGridView the next step is adding three buttons to the form. Name them btnEditAddTasks, btnSave, and btnDelete, and set their text property as shown in Figure 5. Then resize the form appropriately.
Writing Code for the Application
There are two things that you need write code for in order to complete this form: loading the form with data and enabling the user to add/edit/delete/save the data.
Loading the form with data
As of now physical XML files are not available, however you have a dataset that contains two XML DataTables. The first time the application runs you need to write code to create the XML file. The next time the application runs it needs to check for the existence of both the files and then load the file. Double-click the form and add the following code to the form load event:
If CheckFileExistence() Then LoadDataSet() Else 'Application is running for first time, hence XML files are missing 'create XML files based on DataSet Schema CreateXMLFile() End If
Next you need to define a few variables indicating the file names and their locations. Add the following definitions to the class file above the code for the form load event:
Dim myDocumentsFolder As String = My.Computer.FileSystem.SpecialDirectories.MyDocumentsDim activitiesFileName As String = "Activities.xml"Dim tasksFileName As String = "Tasks.xml"Public activitiesFile As String = String.Concat(myDocumentsFolder, activitiesFileName)Public tasksFile As String = String.Concat(myDocumentsFolder, tasksFileName)
Here I used the new My keyword to get the folder path for the MyDocuments folder, where I store the application files. I have defined variables to store the names of the activities and tasks XML files.You need to write code for the functions used in the form load event above, namely: CheckFileExistence, CreateXMLFile, and LoadDataSet. Add the following three code snippets to the form:
Public Function CheckFileExistence() As BooleanIf My.Computer.FileSystem.FileExists(activitiesFile) And _My.Computer.FileSystem.FileExists(tasksFile) Then Return True Else Return False End If End Function
Here again, I’m using the My keyword, to easily access the FileExists function to check for both the XML files. Alternatively, you could use the new snippet functionality to add standard file look-up code to your application, by right-clicking anywhere in code view.
Next you want to add some sample data to the tasks table and auto-generate the schema in the XML file:
Public Sub CreateXMLFile() 'Start adding sample data to Master Tasks Table DsActivitiesTasks.Tasks.AddTasksRow("Email") DsActivitiesTasks.Tasks.AddTasksRow("Browsing") 'End adding sample data to Master Tasks Table 'Write XML file and inline schema DsActivitiesTasks.Tasks.WriteXml(tasksFile, System.Data.XmlWriteMode.WriteSchema) DsActivitiesTasks.activities.WriteXml(activitiesFile, System.Data.XmlWriteMode.WriteSchema) End Sub
The CreateXMLFile function allows me to add some sample data to the tasks table. This enables my tasks drop-down in the form to be populated. The next two lines of code are extremely important. Here I’m using the new functionality in the DataTable to write XML, however I’m also passing an important parameter that auto-generates the schema within the XML file. The schema is important as it specifies the data type and constraints of the various columns as well as the relations between the tables.
When you finish coding the application it will be a good exercise to examine the generated schema and XML data as you will realize how the functionality in the DataTable’s WriteXML method saves you a ton of work.
The last bit of code, with regards to loading the application, is to read the data from the XML files and load it into the dataset.
Public Sub LoadDataSet() DsActivitiesTasks.tasks.ReadXml(tasksFile) DsActivitiesTasks.activities.ReadXml(activitiesFile) End Sub
.NET Framework 1.x did not allow you to load multiple XML files into the dataset at one go. However the enhanced features in the DataTable enable you to easily load both the tasks and the activities XML files that have been just created.
Saving and Deleting Data
The DataGridView automatically provides the user a facility to add new data by moving to a new row; similarly for editing the data, the user just moves to the particular row or column and edits the data. Providing functionality to save and delete data is what you need to write code for. Saving data is a simple process of accepting changes made by the user and then using the WriteXML function of the DataTable to write the XML data and the schema back to the activities file. Similarly, deleting data involves capturing the current row and deleting that row. Add the following two functions, which handle the btnSave and btnDelete click events.
Private Sub btnSave_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _Handles btnSave.Click Me.DsActivitiesTasks.activities.AcceptChanges() Me.DsActivitiesTasks.activities.WriteXml(activitiesFile, System.Data.XmlWriteMode.WriteSchema)End SubPrivate Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _Handles btnDelete.Click Me.DataGridView1.Rows.RemoveAt(Me.DataGridView1.CurrentRow.Index) btnSave_Click(sender, e)End Sub
Adding and Editing Tasks
|Figure 6. UI for Form EditAddTasks: In this dialog, you can see the layout of the DataGridView and the two buttons for the EditAddTasks form.
You still need to write code for the button, btnEditAddTasks, which will allow the user to specify different kinds of tasks in the tasks.xml file. I will get back to this in a bit, as you need to add a new form to the project to enable this. Add a new form and name it frmEditAddTasks.
Drop a DataGridView and two buttons on the new form. Name the buttons btnSave and btnDelete and layout the form UI as shown in Figure 6.
Add the following code to the load event of the form:
DataGridView1.DataSource = frmActivities.DsActivitiesTasksDataGridView1.DataMember = frmActivities.DsActivitiesTasks.tasks.TableName
It is important to note that I’m referring to the instance of the DataSet, dsActivitiesTasks, which is present in the form frmActivities. Similarly, I have specified the DataMember to be the name of the tasks table. This is to ensure that in both the forms the same DataSet instance is being used. This enables any changes made by the user to be reflected immediately.
Double-click the Save button on the form and add the following code to save the data changes made by the user.
Loading the EditAddTasks Form
|Figure 7. Completed Application: The running application lets you add new tasks to your task list, complete with the drop-down list of various tasks to select from.
The last thing remaining to do is to load the frmEditAddTasks form when the button btnEditAddTasks, on the frmActivities form is clicked. Add the following code to the click event of the btnEditAddTasks.
Dim frmtasks As New frmEditAddTasksfrmtasks.ShowDialog()
Running the Application
Hit F5 to run the application. Because the application is running for the first time, the XML files will be generated and sample data will be added to the Tasks XML file. Click the “Edit/Add Tasks” button on the form, which loads the frmEditAddTasks form. You will see the sample data. Add new task types if you require. Clicking the save button writes the data to XML. Close the frmEditAddTasks. On the frmActivities enter a new activity. The Taskid field drops down to show the list of different kind of tasks (see Figure 7).
This application definitely needs a lot more in terms of functionality, such as error checking, validation, etc. My goal, however, has been to show how easy it is to build applications handling multiple XML files as a data store.
The enhanced DataTable class is now fully XML enabled; it eases the pain of reading and writing XML files directly by enabling multiple XML files to be loaded in to a dataset. This, in combination with the DataGridView control, enables one to easily build XML-enabled applications.