0

Sample XML

<Drawer_System_1>
 <DrawerSystemID>1</DrawerSystemID>
 <DrawerSysName>Drawer_System_1</DrawerSysName>
 <DrawerSysLocation>North Wall (2nd from left)</DrawerSysLocation>
 <Drawers>
  <DrawerID>1-01</DrawerID>`enter code here`
  <Contents>Contents of Drawer 1-01</Contents>
 </Drawers>
 <Drawers>
  <DrawerID>1-02</DrawerID>
  <Contents>Contents of Drawer 1-02</Contents>
 </Drawers>
</Drawer_System_1>

My Question

How do I retrieve the values of child and parent XML nodes simultaneously?

I have populated a TreeView control with my XML file, and I want to retrieve certain values (as Strings) between nodes and subnodes as I select them in the control.

My Expected Results

If I select the Drawer_System_1 node or any subnode, I want to retrieve the values between the DrawerSystemID, DrawerSysName, and DrawerSysLocation nodes.

If I select a Drawers node or any subnode, I want to keep the previously mentioned values and also retrieve the values of the DrawerID and Contents nodes.

The value of each node should be displayed in an individual label, but if it's easier to display my requested data in a multi-line text box (or label), that's fine too.

Notes

I am using VB.NET, but if you can think of a solution in C#, that's fine too--I should be able to convert the answer into VB using an online converter.

If necessary, I can re-structure my XML file to make it easier for my program to read.

EDIT: Here's my code so far:

Imports System
Imports System.Xml
Imports System.Xml.Serialization
Imports System.IO

Public Class My_LEGO_Elements
    Private Sub My_LEGO_Elements_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim serializer As New SimpleXmlSerializer()
        Dim data As LEGOElementsData = serializer.DeSerialize(Of LEGOElementsData)(File.ReadAllText("C:\Users\Steven\Documents\Visual Studio 2012\Projects\My_LEGO_Elements\My_LEGO_Elements\Drawer_Systems_5.xml"))
        ListBox1.Items.AddRange(data.DrawerSystems.ToArray())
    End Sub

    Private Sub ListBox1_SelectedValueChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ListBox1.SelectedValueChanged
        Dim drawerSystem As DrawerSystem = CType(ListBox1.SelectedItem, DrawerSystem)
        DrawerSysIDLabel.Text = drawerSystem.Id
        DrawerSysNameLabel.Text = drawerSystem.Name
        DrawerSysLocLabel.Text = drawerSystem.Location
        'retrieve specific drawer system image from resources
        Dim pictureResource = My.Resources.ResourceManager.GetObject(String.Format("{0}", drawerSystem.Id))
        'convert pictureResource to type Image and display in DrawerSysPictureBox
        DrawerSysPictureBox.Image = CType(pictureResource, Image)
    End Sub 'My_LEGO_Elements_Load

    Private Sub ListBox2_SelectedValueChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ListBox2.SelectedValueChanged
        Dim drawer As Drawer = CType(ListBox2.SelectedItem, Drawer)
        DrawerNumberLabel.Text = drawer.Id
        DrawerContentsLabel.Text = drawer.Contents
    End Sub
End Class 'My_LEGO_Elements

Public Class LEGOElementsData
    Public Property DrawerSystems() As List(Of DrawerSystem)
        Get
            Return _drawerSystems
        End Get
        Set(ByVal value As List(Of DrawerSystem))
            _drawerSystems = value
        End Set
    End Property
    Private _drawerSystems As List(Of DrawerSystem)
End Class


Public Class DrawerSystem
    Public Property Id() As String
        Get
            Return _id
        End Get
        Set(ByVal value As String)
            _id = value
        End Set
    End Property
    Private _id As String

    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property
    Private _name As String

    Public Property Location() As String
        Get
            Return _location
        End Get
        Set(ByVal value As String)
            _location = value
        End Set
    End Property
    Private _location As String

    Public Property Drawers() As List(Of Drawer)
        Get
            Return _drawers
        End Get
        Set(ByVal value As List(Of Drawer))
            _drawers = value
        End Set
    End Property
    Private _drawers As List(Of Drawer)

    Public Overrides Function ToString() As String
        Return _name
    End Function
End Class


Public Class Drawer
    Public Property Id() As String
        Get
            Return _id
        End Get
        Set(ByVal value As String)
            _id = value
        End Set
    End Property
    Private _id As String

    Public Property Contents() As String
        Get
            Return _contents
        End Get
        Set(ByVal value As String)
            _contents = value
        End Set
    End Property
    Private _contents As String
End Class

Public Class SimpleXmlSerializer
    Public Function Serialize(ByVal objectToSerialize As Object) As String
        Dim serializer As XmlSerializer = New XmlSerializer(objectToSerialize.GetType())
        Using stream As MemoryStream = New MemoryStream()
            Dim namespaces As XmlSerializerNamespaces = New XmlSerializerNamespaces()
            namespaces.Add("", "")
            serializer.Serialize(stream, objectToSerialize, namespaces)
            Using reader As StreamReader = New StreamReader(stream)
                stream.Position = 0
                Return reader.ReadToEnd()
            End Using
        End Using
    End Function

    Public Function DeSerialize(Of T)(ByVal serializedObject As String) As T
        Dim serializer As XmlSerializer = New XmlSerializer(GetType(T))
        Using reader As StringReader = New StringReader(serializedObject)
            Return CType(serializer.Deserialize(reader), T)
        End Using
    End Function
End Class

I re-structured my XML file as suggested. I got the the drawer systems information to display properly. Is there a way to display information about individual drawers in the second ListBox (or another type of control) upon selecting a drawer system? Also, for some reason my image won't display in the PictureBox.

1 Answers1

0

Sounds like an odd way to do it. I would think if you are loading the XML into a TreeView control, you would be storing all the necessary data into the TreeNode.Tag properties, or into some data structure in memory, outside of the TreeView, so that when they select a node, you wouldn't have to go back to the XML to get the data for the child elements. However, if that's what you want to do, here is one way to read that data out of the XML:

Dim xmlText As String = "<Drawer_System_1>...</Drawer_System_1>..."
Dim doc As New XmlDocument()
doc.LoadXml(xmlText)
Dim parentNode As XmlNode = doc.SelectSingleNode("//Drawer_System_1")
Dim drawerSystemID As String = parentNode.SelectSingleNode("DrawerSystemID").InnerText
Dim drawerSysName As String = parentNode.SelectSingleNode("DrawerSysName").InnerText
Dim drawerSysLocation As String = parentNode.SelectSingleNode("DrawerSysLocation").InnerText

The TreeNode.Tag property is simply an Object property which you can set to anything you want. You can set it equal to a string, a list, a data set, your own custom object, or anything else. Whatever makes sense to you, that's what its for. It allows you to store custom data about each node in the tree so that you can access it at a later time (such as when the node is selected). So for instance, if you had your own class like this:

Public Class DrawerSystem
    Public ID As String
    Public Name As String
    Public Location As String
End Class

Then, when you create a `TreeNode' for a drawer system, you could set it's tag like this:

Private Sub AddNode(drawerSystem As DrawerSystem)
    Dim node As TreeNode = TreeView1.Nodes.Add(drawerSystem.Name)
    node.Tag = drawerSystem
End Sub

Then, in the event when a node is clicked or selected, for instance, you could retrieve that object like this:

Dim drawerSystem As DrawerSystem = CType(e.Node.Tag, DrawerSystem)
Label1.Text = drawerSystem.ID
Label2.Text = drawerSystem.Location

I can't understand exactly why you need to show all the elements in the XML in the same hierarchy in the TreeView. It would seem like a simple list of drawer systems would suffice. Then when an item in the list was selected, you could show all the details about that drawer system in other controls. Also, rather than trying to loop through the XML, I'd recommend just deserializing the XML into a matching data structure. For instance, if you reformatted you XML like this (just to make it more conducive to deserializing):

<LegoElementsData>
  <DrawerSystems>
    <DrawerSystem>
      <Id>1</Id>
      <Name>Drawer_System_1</Name>
      <Location>North Wall (2nd from left)</Location>
      <Drawers>
        <Drawer>
          <Id>1-01</Id>
          <Contents>Contents of Drawer 1-01</Contents>
        </Drawer>
        <Drawer>
          <Id>1-02</Id>
          <Contents>Contents of Drawer 1-02</Contents>
        </Drawer>
      </Drawers>
    </DrawerSystem>
  </DrawerSystems>
</LegoElementsData>

Then, you could create a matching data structure in your code, such as this:

Public Class LegoElementsData
    Public Property DrawerSystems() As List(Of DrawerSystem)
        Get
            Return _drawerSystems
        End Get
        Set(ByVal value As List(Of DrawerSystem))
            _drawerSystems = value
        End Set
    End Property
    Private _drawerSystems As List(Of DrawerSystem)
End Class


Public Class DrawerSystem
    Public Property Id() As String
        Get
            Return _id
        End Get
        Set(ByVal value As String)
            _id = value
        End Set
    End Property
    Private _id As String

    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property
    Private _name As String

    Public Property Location() As String
        Get
            Return _location
        End Get
        Set(ByVal value As String)
            _location = value
        End Set
    End Property
    Private _location As String

    Public Property Drawers() As List(Of Drawer)
        Get
            Return _drawers
        End Get
        Set(ByVal value As List(Of Drawer))
            _drawers = value
        End Set
    End Property
    Private _drawers As List(Of Drawer)

    Public Overrides Function ToString() As String
        Return _name
    End Function
End Class


Public Class Drawer
    Public Property Id() As String
        Get
            Return _id
        End Get
        Set(ByVal value As String)
            _id = value
        End Set
    End Property
    Private _id As String

    Public Property Contents() As String
        Get
            Return _contents
        End Get
        Set(ByVal value As String)
            _contents = value
        End Set
    End Property
    Private _contents As String

    Public Overrides Function ToString() As String
        Return _id & " - " & _ contents
    End Function
End Class

Then, to make your life easier, I'd recommend making your own serializer class that simplifies the process a bit, such as:

Public Class SimpleXmlSerializer
    Public Function Serialize(ByVal objectToSerialize As Object) As String
        Dim serializer As XmlSerializer = New XmlSerializer(objectToSerialize.GetType())
        Using stream As MemoryStream = New MemoryStream()
            Dim namespaces As XmlSerializerNamespaces = New XmlSerializerNamespaces()
            namespaces.Add("", "")
            serializer.Serialize(stream, objectToSerialize, namespaces)
            Using reader As StreamReader = New StreamReader(stream)
                stream.Position = 0
                Return reader.ReadToEnd()
            End Using
        End Using
    End Function

    Public Function DeSerialize(Of T)(ByVal serializedObject As String) As T
        Dim serializer As XmlSerializer = New XmlSerializer(GetType(T))
        Using reader As StringReader = New StringReader(serializedObject)
            Return CType(serializer.Deserialize(reader), T)
        End Using
    End Function
End Class

Then, when you load the data, you could simply do something like this:

Public Class My_LEGO_Elements
    Private Sub My_LEGO_Elements_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim serializer As New SimpleXmlSerializer()
        Dim data As LegoElementsData = serializer.DeSerialize(Of LegoElementsData)(File.ReadAllText("C:\Users\Steven\Documents\Visual Studio 2012\Projects\My_LEGO_Elements\My_LEGO_Elements\Drawer_Systems_3.xml"))
        ListBox1.Items.AddRange(data.DrawerSystems.ToArray())
    End Sub

    Private Sub ListBox1_SelectedValueChanged(ByVal sender As Object, ByVal e As EventArgs) Handles ListBox1.SelectedValueChanged
        Dim drawerSystem As DrawerSystem = CType(ListBox1.SelectedItem, DrawerSystem)
        Label1.Text = drawerSystem.Id
        Label2.Text = drawerSystem.Location
        Label3.Text = drawerSystem.Name
        ListBox2.Items.Clear()
        ListBox2.Items.AddRange(drawerSystem.Drawers.ToArray())
    End Sub
End Class
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • Thanks for your answer. However, that didn't seem to work for me. (Visual Studio 2012 RC was showing errors for the SelectNode method.) How can I read from the TreeNode.Tag properties? – steven.schifter Jul 12 '12 at 19:29
  • @user1517987 I updated my answer to fix my bug and to explain what I meant by tags more clearly. – Steven Doggart Jul 12 '12 at 19:36
  • Thanks for the new info, but I'm not sure where or how to place it in my code. I just added my code to my question for reference. – steven.schifter Jul 12 '12 at 22:57
  • I re-structured my XML file as suggested. I got the the drawer systems information to display properly. Is there a way to display information about individual drawers in the second ListBox (or another type of control) upon selecting a drawer system? Also, for some reason my image won't display in the PictureBox. – steven.schifter Jul 13 '12 at 18:35
  • Of course you can. I updated my answer to show an example of one way to do it. Another thing you may want to do is consider using a DataGrid control or ListView control (set to details view) so you can show multiple columns per item in the list. In what way doesn't the image work? Does it throw an exception? If so, what's the message? – Steven Doggart Jul 13 '12 at 18:50
  • Thanks for your advice regarding how to add items to the second ListBox, but it doesn't seem to work. The following two "System.IO.FileNotFoundException" exceptions were thrown and caught: "Could not load file or assembly 'My_LEGO_Elements.XmlSerializers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified." "Could not load file or assembly 'My_LEGO_Elements.XmlSerializers' or one of its dependencies. The system cannot find the file specified." – steven.schifter Jul 14 '12 at 14:56
  • Is `My_LEGO_Elements.XmlSerializers` a class library that you created? – Steven Doggart Jul 14 '12 at 15:41
  • I don't think I created that library. Do I need to create it for my program to work properly? – steven.schifter Jul 14 '12 at 19:01
  • Check the references tab under your project properties. It sounds like you have a reference to that library but it no longer exists. If that's not the case, then I'm not sure what it wrong. – Steven Doggart Jul 15 '12 at 10:30
  • I checked the references tab, and I couldn't find `My_LEGO_Elements.XmlSerializers` anywhere. But then again, [perhaps that exception is normal...](http://stackoverflow.com/questions/294659/why-did-i-get-an-error-with-my-xmlserializer) – steven.schifter Jul 15 '12 at 16:28
  • Ok. I always set my debugger to only stop on unhandled exceptions. Maybe that's why I've never seen that. So does it work if you continue past the error? – Steven Doggart Jul 15 '12 at 19:03
  • My debugger is set that way too. I only discovered the exceptions by the insertion of a breakpoint. It seems there is no way to fix that problem, as Microsoft claims it's "[by design](https://connect.microsoft.com/VisualStudio/feedback/details/88566/bindingfailure-an-assembly-failed-to-load-while-using-xmlserialization)." – steven.schifter Jul 15 '12 at 20:37