简体   繁体   中英

XML Datagrid Root Element - foreach loop

I want to get all of my information from the XML to the Datagrid.

Currently I have this:

class ColumnNames
{
    public string id { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Computer { get; set; }

    public ColumnNames(
        string id, 
        string Name, 
        string Surname, 
        string Computer, 
    {
        this.id = id;
        this.Name = Name;
        this.Surname = Surname;
        this.Computer = Computer; 
    }
}

private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{

    XDocument getElement = XDocument.Load("details.xml");

    foreach (var npc in getElement.Descendants("user"))
    {
        string id = npc.Attribute("id").Value;
        string Name = npc.Attribute("Name").Value;
        string Surname = npc.Attribute("Surname").Value;
        string Computer = npc.Attribute("Computer").Value;

        var items = new List<ColumnNames>();
        items.Add(new ColumnNames(id, Name, Surname, Computer));
        var grid = sender as DataGrid;
        grid.ItemsSource = items;
    }
}

This is my XML

    <info>
        <user id="1" Name="Max0" Surname="0test" Computer="0" />
        <user id="2" Name="Max1" Surname="1test" Computer="1" />
        <user id="3" Name="Max2" Surname="2test" Computer="2" />
        <user id="4" Name="Max3" Surname="3test" Computer="3" />
        <user id="5" Name="Max4" Surname="4test" Computer="4" />
        <user id="6" Name="Max5" Surname="5test" Computer="5" />
    </info>

What I am trying to do is put all of the data in the root element into a datagrid with the columns id, name, surname and computer. My problem is that at the moment it only gets the first entry and when I tried to use the following:

foreach (var npc in getElement.Root("info"))

I got an error saying

"Non-invocable member 'System.Xml.Linq.XDocument.Root' cannot be used like a method."

I'm not sure how I can make this work, It's been quite some time that I have been trying to get this fixed, some help with be nice. Thanks.


UPDATE:

This is the new code after David generously helped me. Although it is still not working.

private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{

    // getElement is a weird name for this object :)
    XDocument getElement = XDocument.Load("details.xml");

    // Create your List of ColmNames outside the foreach to use later
    var items = new List<ColumnNames>();

    foreach (var npc in getElement.Root.Elements("user"))
    {
        string id = npc.Attribute("id").Value;
        string Name = npc.Attribute("Name").Value;
        string Surname = npc.Attribute("Surname").Value;
        string Computer = npc.Attribute("Computer").Value;

        items.Add(new ColumnNames(id, Name, Surname, Computer));
    }

    // Finally, get a reference to the grid and set the ItemsSource property to use
    // your full list containing all the items
    var grid = sender as DataGrid;
    grid.ItemsSource = items;

}

You're close...

foreach (var npc in getElement.Descendants("user"))

Actually does work, and is correct, with the given XML structure . See the bottom of my answer for an important distinction, though.

The issue is that you are setting grid.ItemsSource = items; inside your foreach loop, thereby only adding one item each time the foreach loops around, and you always end up with just the last item in your datagrid.

Additionally, you need to hold the list of items outside the foreach loop as well, otherwise it only exists (only has scope ) inside the foreach .

To fix that, you need to move grid.ItemsSource = items; to be after the foreach loop, and move the list creation to before the foreach loop:

// Your code
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{    
    // getElement is a weird name for this object :)
    XDocument getElement = XDocument.Load("details.xml");

    // Create your List of ColumnNames outside the foreach to use later
    var items = new List<ColumnNames>();

    foreach (var npc in getElement.Descendants("user"))
    {
        string id = npc.Attribute("id").Value;
        string Name = npc.Attribute("Name").Value;
        string Surname = npc.Attribute("Surname").Value;
        string Computer = npc.Attribute("Computer").Value;

        // You are adding an item to the existing List<ColumnNames> now, not a new one each time
        items.Add(new ColumnNames(id, Name, Surname, Computer));
    }

    // Finally, get a reference to the grid and set the ItemsSource property to use
    // your full list containing all the items
    var grid = sender as DataGrid;
    grid.ItemsSource = items;

}

Hope this helps!


With regard to your attempt at changing the XDocument call in your foreach loop, as I mentioned, you actually had it working correctly ... but to explain why the changed version doesn't work, since I wrote it already, see the below:

Your main issue is that XDocument has a Root element, in this case, <info>

The syntax you're using, getElement.Root("info") tells the compiler you want to call a method called Root of XDocument - but that method doesn't exist. It's a property. That's what your error message is telling you.

To fix the problem,

Change your foreach to use the .Elements() method of XDocument and all will be well:

foreach (var npc in getElement.Root.Elements("user"))

Or, as you already had it

foreach (var npc in getElement.Descendants("user"))

The main difference here is .Descendants() will get any node named "user" regardless of how deeply it is nested; .Elements() will only get the next level down from the current node (direct children).

So while with your XML structure they appear to work the same, it is important to understand this distinction in the event your XML structure changes (or the scenario/project is different, etc.).


As a side note, getElement is a weird name for an XDocument object; I usually just use something like xDoc...


One More Update - Why isn't this working?

As I realized this is a WPF datagrid, and you're loading a local XML file, it dawned on me that your grid shows nothing because XDocument.Load() fails to find the file.

I checked this with a try/catch in an empty WPF app with a DataGrid.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void DataGrid_Loaded(object sender, RoutedEventArgs e)
    {
        XDocument getElement = null;// = XDocument.Load(@"c:\dev\stackoverflow\DataGridWpf\DataGridWpf\details.xml");
        try
        {
            getElement = XDocument.Load(@"details.xml");
        } catch (Exception ex) {
            var test = ex.ToString();
        }

        // Create your List of ColumnNames outside the foreach to use later
        var items = new List<ColumnNames>();

        foreach (var npc in getElement.Descendants("user"))
        {
            string id = npc.Attribute("id").Value;
            string Name = npc.Attribute("Name").Value;
            string Surname = npc.Attribute("Surname").Value;
            string Computer = npc.Attribute("Computer").Value;

            // You are adding an item to the existing List<ColumnNames> now, not a new one each time
            items.Add(new ColumnNames(id, Name, Surname, Computer));
        }

        // Finally, get a reference to the grid and set the ItemsSource property to use
        // your full list containing all the items
        var grid = sender as DataGrid;
        grid.ItemsSource = items;
    }

This code, if you step through, will throw the FileNotFound exception, which if unhandled just dies and you end up with an empty window!

In my case the full path did the trick and the grid loads up nicely:

在此处输入图片说明

Larry Wall, the author of Perl, describes laziness as the 1st great virtue of a programmer. Don't do more work than you need to (somebody will have to maintain it...and it might be you). So...

Why don't you decorate your data structure with XML serialization attributes, something like:

[XmlRoot("info")]
public class InfoList
{

  [XmlElement("user")]
  public User[] Users { get ; set ; }

  public class User
  {
    [XmlAttribute] public string id       { get; set; }
    [XmlAttribute] public string Name     { get; set; }
    [XmlAttribute] public string Surname  { get; set; }
    [XmlAttribute] public string Computer { get; set; }
  }

}

Wrap the deserialization in a method:

static User[] LoadUserDetails()
{
    XmlSerializer serializer = new XmlSerializer(typeof(InfoList)) ;
    using ( Stream s = File.Open("details.xml",FileMode.Open,FileAccess.Read,FileShare.Read) )
    {
        InfoList instance = (InfoList) serializer.Deserialize(s) ;
        return instance.Users ;
    }
}

And your method then looks something like:

private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
  DataGrid grid = (DataGrid) sender ;
  grid.ItemsSource = LoadUserDetails() ;
  return ;
}

Which is easier to test and/or maintain?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM