Friday 18 May 2012

Windows Forms XML File Reader

I am working on a Windows desktop app that will allow the user to do the following:
1. Select an XML file
2. Select an Element from the XML
3. Display the child elements and the associated values
4. Select a specific value of a child element and display all occurrences of that value

All of this will displayed in a Windows Forms DataGridView Control.

So I have wanted to learn some more Linq for a while so I decided to do use Linq for querying the XML data instead of using XPath.

This post is the first post of what will likely be a few posts in this series as there are some other items I will cover. I will want to have two panels in this application, one that contain the XML structure(so the user can select which Elements they want to view) and the other panel will show the results. I might also have a 3rd panel that shows the source xml. I am going to try and do some drag and drop to allow the user drag values, we'll see how I get on with that (easy in the web world so am hoping it's not too clunky in the winforms world).

Part 1: Display Linq to XML Query Results in Windows Forms DataGridView

NOTE: All of this is using VS2010 and .Net Framwork version 4.

So the first order of business is allowing the user to select an xml file to open. So to do this I did:

1. Created a new Windows Form project inVisual Studio 2010
2. Add a Picture Box to the form and set the picture to a nice open file icon
3. Add a Open File Dialog control to the form
4. Put the following code in the button on click event


   1:  namespace XMLFileTool
   2:  {
   3:      public partial class Form1 : Form
   4:      {
   5:          public Form1()
   6:          {
   7:              InitializeComponent();
   8:          }
   9:   
  10:          private void pictureBox1_Click(object sender, EventArgs e)
  11:          {
  12:              DialogResult result = openFileDialog1.ShowDialog();
  13:              if (result == DialogResult.OK)
  14:              {
  15:                  string fileName = openFileDialog1.FileName;
  16:              }
  17:              else
  18:              {
  19:                  MessageBox.Show("It's gonna be kind of hard to read an xml file if you don't select one.");
  20:              }
  21:          }
  22:      }
  23:  }

So the next activity is to read in the file you have selected so that you can run your Linq query on the XML.

I am using the XElement Class to read in the elements from a specific node in the XML. Using this class means that I don't have to start from the Root node and work my way from there. It's a bit less code to do it this way.

I am going to go ahead and past the whole class below and then explain what it going on. You can ignore Lines 24 to 30 for now. This will be covered off in a later post.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:  using System.Data;
   5:  using System.Drawing;
   6:  using System.Linq;
   7:  using System.Text;
   8:  using System.Windows.Forms;
   9:  using System.IO;
  10:  using System.Xml;
  11:  using System.Xml.Linq;
  12:   
  13:  namespace XMLFileTool
  14:  {
  15:      public partial class Form1 : Form
  16:      {
  17:          public Form1()
  18:          {
  19:              InitializeComponent();
  20:          }
  21:   
  22:          private void pictureBox1_Click(object sender, EventArgs e)
  23:          {
  24:              List<string> xmlTagList = new List<string>();
  25:              xmlTagList.Add("OUTDOORS");
  26:              xmlTagList.Add("ACTIVITIES");
  27:              xmlTagList.Add("WALKING");
  28:              xmlTagList.Add("TOWNNAME");
  29:   
  30:              var dataTableName = xmlTagList.Last();
  31:   
  32:              DialogResult result = openFileDialog1.ShowDialog();
  33:              if (result == DialogResult.OK)
  34:              {
  35:                  string fileName = openFileDialog1.FileName;
  36:   
  37:                  tbSelectedFile.Text = openFileDialog1.FileName;
  38:                  XElement xmlDocument = XElement.Load(fileName);
  39:                  try
  40:                  {
  41:                      var xmlNodes = from nodeList in xmlDocument.Descendants(dataTableName)
  42:                                     select new
  43:                                     {
  44:                                         TownName = nodeList.Attribute("name").Value,
  45:                                         WalkName = nodeList.Element("WALKNAME").Value
  46:                                     };
  47:   
  48:                      dgSearchResults.AutoGenerateColumns = true;
  49:                      dgSearchResults.DataSource = xmlNodes.ToList();
  50:                  }
  51:                  catch (IOException ex)
  52:                  {
  53:                      MessageBox.Show(ex.Message);
  54:                  }
  55:              }
  56:              else
  57:              {
  58:                  MessageBox.Show("It's gonna be kind of hard to read an xml file if you don't select one.");
  59:              }
  60:          }
  61:      }
  62:  }

You will notice on line 37 the code to write out the file name to a text box. What I did here was added a Text Box control to the form and I use this code to display the file name and file path to the user so that they can see on screen which file they selected.

On Line 38 we use the XElement Class which has a Load() method and we pass the string fileName which is the path to the file the user has selected.

Line 41 starts the Linq query. Basically what this is saying is:

Get me a list of all of the Elements that are Children of the TOWNNAME element and set the variable xmlNodes equal to what ever is returned by the Linq query.

On Line 49 I set the DataGridView DataSource using the ToList() method of the Enumerable Class.

That's all you need to do! I have listed the XML that I used below. When you run this, select the XML file, this is what you will see on screen:




<?xml version="1.0"?>
<OUTDOORS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ACTIVITIES>
    <WALKING>
      <TOWNNAME name="Drumshanbo">
        <WALKNAME>Historical Town Walk</WALKNAME>
        <WALKLOCATION>Begins in Market Yard</WALKLOCATION>
        <DIFFICULTY>Easy</DIFFICULTY>
        <DURATION>1 hour</DURATION>
      </TOWNNAME>
      <TOWNNAME name="Ballinaglera">
        <WALKNAME>St. Hugh's Holy Well</WALKNAME>
        <WALKLOCATION>St. Hugh's Holy Well</WALKLOCATION>
        <DIFFICULTY>Medium</DIFFICULTY>
        <DURATION>2 hours</DURATION>
      </TOWNNAME>
      <TOWNNAME name="Arigna">
        <WALKNAME>Miner's Way</WALKNAME>
        <WALKLOCATION>Own train tracks</WALKLOCATION>
        <DIFFICULTY>Medium</DIFFICULTY>
        <DURATION>4 hours</DURATION>
      </TOWNNAME>
      <TOWNNAME name="Drumkeeran">
        <WALKNAME>Miner's Way</WALKNAME>
        <WALKLOCATION>Drumkeeran</WALKLOCATION>
        <DIFFICULTY>Easy</DIFFICULTY>
        <DURATION>6 hours</DURATION>
      </TOWNNAME>
    </WALKING>
    <FISHING>
        <LOUGHALLEN>
          <FISHINGTYPE>Coarse</FISHINGTYPE>
          <TYPESOFFISH>Pike</TYPESOFFISH>
        </LOUGHALLEN>
        <RIVERSHANNON>
          <FISHINGTYPE>Fly Fishing</FISHINGTYPE>
          <TYPESOFFISH>Trout</TYPESOFFISH>
        </RIVERSHANNON>
    </FISHING>      
  </ACTIVITIES>      
</OUTDOORS>  
The next part of this post I am going to cover off allowing the user to dynamically specify which Element they want to see the data for. So in the case of the XML above the user will select whether they want to see WALKING or FISHING. Another task I will be doing is implementing an Observer pattern using the Microsoft Reactive Extensions. The purpose of this will be to update the UI in real time as the app is searching the results. I will have a separate process publishing the data that is found (the publisher) and the desktop will be an observer. The Reactive Extensions will push new data found events to the desktop app. This will be interesting and fun! Hope you enjoyed this post and found it useful. If you have any questions or comments please post below.

No comments:

Post a Comment