none
XML to TreeView Starting at specific Element

    Question

  • I know that as per Deborah Kurata's blog:
    http://msmvps.com/blogs/deborahk/archive/2009/07/21/populating-a-treeview-control-from-xml.aspx

    I can populate an treeview from an XML Tree using the following code in VB:

    Dim doc As XElement = XElement.Load("testXML.xml")

    Dim stateNode As TreeNode
    Dim regionNode As TreeNode
    For Each state As XElement In doc...<State>
        stateNode = TreeView1.Nodes.Add(state.@name)
        For Each region As XElement In state...<Region>
            regionNode = stateNode.Nodes.Add(region.@name)
            For Each area As XElement In region...<Area>
                regionNode.Nodes.Add(area.@name)
            Next
        Next
    Next


    How would I adjust this if I only wanted to loop through a particular State element (say California)?  I am assuming that I have to take out the first For/Next loop so that I am not iterating through all the State Elements.  Do I have to define the starting state element as a root and then start from there?

    Thanks

    EM

    Monday, October 26, 2009 4:41 PM

Answers

  • Yes, take out the outer For Each and instead start with a single XElement respectively corresponding TreeNode. Pseudo code:
    Dim stateName As String = "California"
    Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
    If stateEl IsNot Nothing Then
      stateNode = TreeView1.Nodes.Add(state.@name)
      For Each region As XElement In stateEl...<Region>
          regionNode = stateNode.Nodes.Add(region.@name)
          For Each area As XElement In region...<Area>
              regionNode.Nodes.Add(area.@name)
          Next
      Next
    End If
    

    MVP XML My blog
    • Marked as answer by ExcelMonkey Thursday, October 29, 2009 2:47 AM
    Monday, October 26, 2009 5:52 PM
  • Hi EM -

    Martin's code is using a Where clause that works like a Where clause in a database. It finds the one "where name = statename". Notice that Martin hard-coded the stateName in the line above this line.

    So basically, the Where clause is finding the first element with the matching state name of "California".

    If you always want to get "the third one", regardless of its name, you can use the Skip(2) feature to skip the first two of them.

    Hope this helps.


    www.insteptech.com ; msmvps.com/blogs/deborahk
    We are volunteers and ask only that if we are able to help you, that you mark our reply as your answer. THANKS!
    • Marked as answer by ExcelMonkey Monday, October 26, 2009 8:29 PM
    Monday, October 26, 2009 6:08 PM
  • doc.<State> selects the 'State' child elements of the doc XElement (from the blog code sample). The Where() filters those 'State' elements to those where the 'name' attribute is equal to the variable stateName (which was set to "California" before). If you don't want to use a variable then you can of course do that, LINQ code is not different from other code so instead of a variable you can always use a constant of the same type, in this case a string literal. The FirstOrDefault() takes the first of the filtered 'State' elements or returns Nothing (in VB terms) respectively null (in C# terms) if no element with the name stateName was found.

    If you don't want to select a 'State' element by name but by index then you should be able to use e.g.
    Dim stateEl As XElement = doc.<State>(0)
    to select the first, doc.<State>(1) to select the second and so on.

    You should really read the LINQ documentation on MSDN online or in your local Visual Studio documentation.
    MVP XML My blog
    • Marked as answer by ExcelMonkey Monday, October 26, 2009 8:29 PM
    Monday, October 26, 2009 6:42 PM
  • There are two problems here:

    1) Martin's code was not exactly right for your XML.

    Here is the correct line:

            Dim stateEl As XElement = docStates...<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
    Notice the ...<State>. This means get *any* decendants where the element name is <State>. Otherwise, it was only looking for first level elements.

    2) You are using "State" as a variable, yet did not declare a variable named "State". You called it stateEl. So you need to change this line as follows:

                stateNode = TreeView1.Nodes.Add(stateEl.@name)
    This is getting the name from the results of your query.

    And to answer your question, a where clause expects to get back a SET of data (possibly more than one). FirstOrDefault will always give you the first one in the set. Even if there is only one, it will give you back a set with a count of 1.

    Hope this helps.





    www.insteptech.com ; msmvps.com/blogs/deborahk
    We are volunteers and ask only that if we are able to help you, that you mark our reply as your answer. THANKS!
    • Marked as answer by ExcelMonkey Monday, November 02, 2009 5:27 PM
    Thursday, October 29, 2009 10:40 PM
  • Martin, two last points.  In the example you provided (below), stateEI fails the IsNot Nothing test when I run it.  I also put the output of the XML below as well.  Secondly, the second line after the If stm references a "state.@name" in the brackets.  This made sense to me when the original For/NExt stmt was being used.  However now that it is gone, my VBE is telling me that "state" is a type and cannot be declared.  Have I missed something here.

    Dim doc As XElement = XElement.Load("C:\State.xml")
    Dim stateNode As TreeNode
    Dim regionNode As TreeNode
    Dim stateName As String = "California"
    Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
            If stateEl IsNot Nothing Then
                stateNode = TreeView1.Nodes.Add(State.@name)
                For Each region As XElement In stateEl...<Region>
                    regionNode = stateNode.Nodes.Add(region.@name)
                    For Each area As XElement In region...<Area>
                        regionNode.Nodes.Add(area.@name)
                    Next
                Next
            End If
    **************************************
      <?xml version="1.0" encoding="utf-8" ?>
      <States>
          <State Name="California">
              <Regions>
                  <Region Name="San Luis Obispo">
                      <Area Name="Santa Maria" />
                      <Area Name="Seaside" />
                  </Region>
                  <Region Name="Silicon Valley">
                      <Area Name="San Jose" />
                      <Area Name="Sunnyvale" />
                  </Region>
              </Regions>
          </State>
          <State Name="Wisconsin">
              <Regions>
                  <Region Name="Milwaukee">
                      <Area Name="Mukwanago" />
                      <Area Name="Germantown" />
              </Region>
                  <Region Name="Fox Valley">
                      <Area Name="Oshkosh" />
                      <Area Name="Appleton" />
                  </Region>
              </Regions>
          </State>
      </States>
    With VB the case of identifiers usually does not matter but when you code against LINQ to XML then it matters. Thus if the XML attribute name is 'Name' then in your code you need to use @Name and not @name.
    That is the only problem as far as I can see (well besides the obvious needed change the use 'stateEl'  instead of 'state'), the following correction of your code works then:

            Dim doc As XElement = XElement.Load("..\..\XMLFile1.xml")
            Dim stateNode As TreeNode
            Dim regionNode As TreeNode
            Dim stateName As String = "California"
            Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@Name = stateName).FirstOrDefault()
            If stateEl IsNot Nothing Then
                stateNode = TreeView1.Nodes.Add(stateEl.@Name)
                For Each region As XElement In stateEl...<Region>
                    regionNode = stateNode.Nodes.Add(region.@Name)
                    For Each area As XElement In region...<Area>
                        regionNode.Nodes.Add(area.@Name)
                    Next
                Next
            End If


    MVP XML My blog
    • Marked as answer by ExcelMonkey Monday, November 02, 2009 5:27 PM
    Friday, October 30, 2009 11:35 AM

All replies

  • Yes, take out the outer For Each and instead start with a single XElement respectively corresponding TreeNode. Pseudo code:
    Dim stateName As String = "California"
    Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
    If stateEl IsNot Nothing Then
      stateNode = TreeView1.Nodes.Add(state.@name)
      For Each region As XElement In stateEl...<Region>
          regionNode = stateNode.Nodes.Add(region.@name)
          For Each area As XElement In region...<Area>
              regionNode.Nodes.Add(area.@name)
          Next
      Next
    End If
    

    MVP XML My blog
    • Marked as answer by ExcelMonkey Thursday, October 29, 2009 2:47 AM
    Monday, October 26, 2009 5:52 PM
  • Does the FirstOrDefault line start it at the first node? Is there a way to hard-code the State name if for example I only wanted the second state or the third state etc.

    Thanks

    EM
    Monday, October 26, 2009 5:55 PM
  • Hi EM -

    Martin's code is using a Where clause that works like a Where clause in a database. It finds the one "where name = statename". Notice that Martin hard-coded the stateName in the line above this line.

    So basically, the Where clause is finding the first element with the matching state name of "California".

    If you always want to get "the third one", regardless of its name, you can use the Skip(2) feature to skip the first two of them.

    Hope this helps.


    www.insteptech.com ; msmvps.com/blogs/deborahk
    We are volunteers and ask only that if we are able to help you, that you mark our reply as your answer. THANKS!
    • Marked as answer by ExcelMonkey Monday, October 26, 2009 8:29 PM
    Monday, October 26, 2009 6:08 PM
  • doc.<State> selects the 'State' child elements of the doc XElement (from the blog code sample). The Where() filters those 'State' elements to those where the 'name' attribute is equal to the variable stateName (which was set to "California" before). If you don't want to use a variable then you can of course do that, LINQ code is not different from other code so instead of a variable you can always use a constant of the same type, in this case a string literal. The FirstOrDefault() takes the first of the filtered 'State' elements or returns Nothing (in VB terms) respectively null (in C# terms) if no element with the name stateName was found.

    If you don't want to select a 'State' element by name but by index then you should be able to use e.g.
    Dim stateEl As XElement = doc.<State>(0)
    to select the first, doc.<State>(1) to select the second and so on.

    You should really read the LINQ documentation on MSDN online or in your local Visual Studio documentation.
    MVP XML My blog
    • Marked as answer by ExcelMonkey Monday, October 26, 2009 8:29 PM
    Monday, October 26, 2009 6:42 PM
  • Thanks Deborah/Martin.  I just downloaded all the LING/XML samples on MSDN.  Thanks again.

    EM
    Monday, October 26, 2009 8:30 PM
  • Martin, two last points.  In the example you provided (below), stateEI fails the IsNot Nothing test when I run it.  I also put the output of the XML below as well.  Secondly, the second line after the If stm references a "state.@name" in the brackets.  This made sense to me when the original For/NExt stmt was being used.  However now that it is gone, my VBE is telling me that "state" is a type and cannot be declared.  Have I missed something here.

    Dim doc As XElement = XElement.Load("C:\State.xml")
    Dim stateNode As TreeNode
    Dim regionNode As TreeNode
    Dim stateName As String = "California"
    Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
            If stateEl IsNot Nothing Then
                stateNode = TreeView1.Nodes.Add(State.@name)
                For Each region As XElement In stateEl...<Region>
                    regionNode = stateNode.Nodes.Add(region.@name)
                    For Each area As XElement In region...<Area>
                        regionNode.Nodes.Add(area.@name)
                    Next
                Next
            End If
    **************************************
      <?xml version="1.0" encoding="utf-8" ?>
      <States>
          <State Name="California">
              <Regions>
                  <Region Name="San Luis Obispo">
                      <Area Name="Santa Maria" />
                      <Area Name="Seaside" />
                  </Region>
                  <Region Name="Silicon Valley">
                      <Area Name="San Jose" />
                      <Area Name="Sunnyvale" />
                  </Region>
              </Regions>
          </State>
          <State Name="Wisconsin">
              <Regions>
                  <Region Name="Milwaukee">
                      <Area Name="Mukwanago" />
                      <Area Name="Germantown" />
              </Region>
                  <Region Name="Fox Valley">
                      <Area Name="Oshkosh" />
                      <Area Name="Appleton" />
                  </Region>
              </Regions>
          </State>
      </States>
    • Edited by ExcelMonkey Wednesday, October 28, 2009 3:40 AM Clarity
    Wednesday, October 28, 2009 3:34 AM
  • Still looking at this and have not figured out why the following line of code is = FALSE

     If stateEl IsNot Nothing Then

    It obviously ties back to this line:
    Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()

    If my goal was to only focus on the "California" element and all its children then as I understand it doc.<State>.Where(Function(s) s.@name = stateName) provides my starting position.  However its not clear to me why I would be using the FirstOrDefault().  I am assuming the this must be defaulting to 0 and makes stateEI = Nothing.

    Thanks

    EM

    Thursday, October 29, 2009 7:42 PM
  • There are two problems here:

    1) Martin's code was not exactly right for your XML.

    Here is the correct line:

            Dim stateEl As XElement = docStates...<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
    Notice the ...<State>. This means get *any* decendants where the element name is <State>. Otherwise, it was only looking for first level elements.

    2) You are using "State" as a variable, yet did not declare a variable named "State". You called it stateEl. So you need to change this line as follows:

                stateNode = TreeView1.Nodes.Add(stateEl.@name)
    This is getting the name from the results of your query.

    And to answer your question, a where clause expects to get back a SET of data (possibly more than one). FirstOrDefault will always give you the first one in the set. Even if there is only one, it will give you back a set with a count of 1.

    Hope this helps.





    www.insteptech.com ; msmvps.com/blogs/deborahk
    We are volunteers and ask only that if we are able to help you, that you mark our reply as your answer. THANKS!
    • Marked as answer by ExcelMonkey Monday, November 02, 2009 5:27 PM
    Thursday, October 29, 2009 10:40 PM
  • Martin, two last points.  In the example you provided (below), stateEI fails the IsNot Nothing test when I run it.  I also put the output of the XML below as well.  Secondly, the second line after the If stm references a "state.@name" in the brackets.  This made sense to me when the original For/NExt stmt was being used.  However now that it is gone, my VBE is telling me that "state" is a type and cannot be declared.  Have I missed something here.

    Dim doc As XElement = XElement.Load("C:\State.xml")
    Dim stateNode As TreeNode
    Dim regionNode As TreeNode
    Dim stateName As String = "California"
    Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@name = stateName).FirstOrDefault()
            If stateEl IsNot Nothing Then
                stateNode = TreeView1.Nodes.Add(State.@name)
                For Each region As XElement In stateEl...<Region>
                    regionNode = stateNode.Nodes.Add(region.@name)
                    For Each area As XElement In region...<Area>
                        regionNode.Nodes.Add(area.@name)
                    Next
                Next
            End If
    **************************************
      <?xml version="1.0" encoding="utf-8" ?>
      <States>
          <State Name="California">
              <Regions>
                  <Region Name="San Luis Obispo">
                      <Area Name="Santa Maria" />
                      <Area Name="Seaside" />
                  </Region>
                  <Region Name="Silicon Valley">
                      <Area Name="San Jose" />
                      <Area Name="Sunnyvale" />
                  </Region>
              </Regions>
          </State>
          <State Name="Wisconsin">
              <Regions>
                  <Region Name="Milwaukee">
                      <Area Name="Mukwanago" />
                      <Area Name="Germantown" />
              </Region>
                  <Region Name="Fox Valley">
                      <Area Name="Oshkosh" />
                      <Area Name="Appleton" />
                  </Region>
              </Regions>
          </State>
      </States>
    With VB the case of identifiers usually does not matter but when you code against LINQ to XML then it matters. Thus if the XML attribute name is 'Name' then in your code you need to use @Name and not @name.
    That is the only problem as far as I can see (well besides the obvious needed change the use 'stateEl'  instead of 'state'), the following correction of your code works then:

            Dim doc As XElement = XElement.Load("..\..\XMLFile1.xml")
            Dim stateNode As TreeNode
            Dim regionNode As TreeNode
            Dim stateName As String = "California"
            Dim stateEl As XElement = doc.<State>.Where(Function(s) s.@Name = stateName).FirstOrDefault()
            If stateEl IsNot Nothing Then
                stateNode = TreeView1.Nodes.Add(stateEl.@Name)
                For Each region As XElement In stateEl...<Region>
                    regionNode = stateNode.Nodes.Add(region.@Name)
                    For Each area As XElement In region...<Area>
                        regionNode.Nodes.Add(area.@Name)
                    Next
                Next
            End If


    MVP XML My blog
    • Marked as answer by ExcelMonkey Monday, November 02, 2009 5:27 PM
    Friday, October 30, 2009 11:35 AM
  • Thanks Margin/Deborah.  I feel a lttle overdrawn on the XML/LINQ credits (i.e. your answers to my posts)!  Thanks for your patience.  This all gelled for me over the weekend.  I am up an running! 

    I just picked up the following book on LINQ as well:

    http://www.amazon.ca/Professional-LINQ-Scott-Klein/dp/0470041811/ref=sr_1_8?ie=UTF8&s=books&qid=1257182947&sr=1-8

    Thanks again for your foucssed attention!

    EM
    Monday, November 02, 2009 5:31 PM