none
Data Binding im DataGrid RRS feed

  • Frage

  • Hallo :-),

    ich beschäftige mich gerade mit WPF und habe ein Problem mit dem Data Binding im DataGrid. In meiner kleinen Beispielapplikation gibt es die Klassen "Item" und "Category". Eine Category hat eine IList von Items. Ich möchte nun gerne die Eigenschaft "Name" der Items zu einem "Category"-Objekt in einem DataGrid anzeigen lassen.

    Als DataContext setze ich das "category"-Objekt und versuche dann die IList Items daran zu binden. Das Ganze resultiert jedoch immer in einer Exception wenn ich ein eigendes DataGridTextColumn in das XAML einbaue. Ich bekomme dann:

    System.InvalidOperationException wurde nicht behandelt.
      Message=Die Items-Auflistung muss vor dem Verwenden von "ItemsSource" leer sein.

     

    Lasse ich die Spalten automatisch erstellen funktioniert das gut. Weiß jemand was ich falsch mache?

     

    Grüße,

     Patrick

     

    Hier ist der Code dazu:

    public class Category {
        public IList items = new List<Item>();
        public virtual String Name { get; set; }
        public virtual int Id { get; private set; }
    
        public virtual IList Items {
          get { return items; }
          set { items = value; }
        }
    
        public void AddItem(Item i) {
          i.Category = this;
          items.Add(i);
        }
      }

     

    public class Item {
        private int id;
        private IList<String> tags = new List<String>();
    
        public virtual String Name { get; set; }
    
        public virtual IList<String> Tags {
          get { return tags; }
          set { tags = value; }
        }
    
        public Category Category { get; set; }
      }

     

    <Window x:Class="WpfApplication1.Edit"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Edit" Height="300" Width="300">
      <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
          <Label Content="Name" />
          <Label Content="{Binding Path=Name}" />
        </StackPanel>
        <StackPanel Orientation="Horizontal">
          <Label Content="Tags" />
          <DataGrid ItemsSource="{Binding Items}"
               AutoGenerateColumns="False">
            <DataGridTextColumn
       Binding="{Binding Path=Name}" Header="Name" />
    
          </DataGrid>
        </StackPanel>
      </StackPanel>
    </Window>

     

      public partial class Edit : Window
      {
        public Edit()
        {
          InitializeComponent();
        }
    
        public Edit(Category category)
        {
          InitializeComponent();
    
          this.DataContext = category;
        }
    
      }

    Donnerstag, 8. Juli 2010 09:59

Antworten

  •  

    Die WPF Datenbindung wiederum arbeitet ebensogut mit einer ObservableCollection,
    siehe Optimieren der Leistung: Datenbindung

    ObservableCollection ist zwar meist die bessere Lösung. Wenn es aber nur um eine Anzeige geht, dann geht auch eine List(Of T). Hier mal Pattasattos Beispiel:

    XAML:

    <Window x:Class="WpfApplication1.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Title="MainWindow" Height="350" Width="525"
         xmlns:my="clr-namespace:WpfApplication1">
     <Window.Resources>
      <my:Category x:Key="vm"/>
     </Window.Resources>
     <StackPanel Orientation="Vertical" DataContext="{Binding Source={StaticResource vm}}">
      <StackPanel Orientation="Horizontal">
       <Label Content="Name" />
       <Label Content="{Binding Path=Name}" />
      </StackPanel>
      <StackPanel Orientation="Horizontal">
       <Label Content="Tags" />
       <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
        <DataGrid.Columns>
         <DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" />
        </DataGrid.Columns>
       </DataGrid>
      </StackPanel>
     </StackPanel>
    </Window>
    

    Dazu der Code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace WpfApplication1
    {
     class ViewModel
     {
     }
    
     public class Category
     {
      public Category()
      {
       for (int i = 1; i < 10; i++)
       {
        Items.Add(new Item { Name = "Cat " + i.ToString() });
       }
      }
    
      public IList<Item> items = new List<Item>();
      public virtual String Name { get; set; }
      public virtual int Id { get; private set; }
    
      public virtual IList<Item> Items
      {
       get { return items; }
       set { items = value; }
      }
    
      public void AddItem(Item i)
      {
       i.Category = this;
       items.Add(i);
      }
     }
    
    
     public class Item
     {
      private int id;
      private IList<String> tags = new List<String>();
    
      public virtual String Name { get; set; }
    
      public virtual IList<String> Tags
      {
       get { return tags; }
       set { tags = value; }
      }
    
      public Category Category { get; set; }
     }
    }
    

    --
    Viele Gruesse
    Peter

    Donnerstag, 8. Juli 2010 14:02
  • Hallo Torsten,

    in solchen Fällen wäre eine Quellenangabe oder gleich der Link empfehlenswerter,
    das sieht doch sehr stark aus nach: Data Binding in DataGrid Control - Part 1

    Wobei es sich jedoch um das Windows Forms DataGrid (.NET 1.x) handelt!
    Bei dem WPF DataGrid handelt es sich um anderes Steuerelement,
    das für den Einsatz unter WPF optimiert wurde.

    Die WPF Datenbindung wiederum arbeitet ebensogut mit einer ObservableCollection,
    siehe Optimieren der Leistung: Datenbindung

    Gruß Elmar

    Donnerstag, 8. Juli 2010 12:50

Alle Antworten

  • Hallo Patrick,

     

    ein DataGrid ist mir nur von der Firma XCEED bekannt und nicht im Standard der VS bei WPF drin... Gerne lerne ich hier aber was dazu... Für die Datenbindung bietet sich das ListView-Control an.

    Wenn du Kategorien zum Anzeigen hast ist doch auch ne TreeView ganz schön oder einfach nur ne ListBox...

     

    Gruss

    Torsten

     

    Donnerstag, 8. Juli 2010 10:42
  • Hallo,

    im WPF 4 ist das DataGrid nun dabei, vorher war das schon auf Codeplex verfügbar. Ribbons gibt es da im übrigen auch schon. Aber das nur am Rande :-)

    Mir geht es nicht so sehr darum das Problem durch den einsatz eines anderen Controls zu lösen, sondern um das Verstehen wie damn es einsetzt. Für die eigentliche Applikation ist es wichtig die daten in dem DataGrid verändern zu können, daher scheiden sowohl TreeView, als auch ListView aus.

    Vielen Dank für deine Antwort,

     Patrick

    Donnerstag, 8. Juli 2010 10:55
  • ok. Got it.

    Also.. habe mal nachgeschlagen. Am besten arbeitet das Datengrid, wenn du es an eine DataTable bindest. Das heisst also erst die DataTable befüllen und dann im Datagrid anzeigen.

    Vielleicht hilft dir das (zum Grundverständnis):

    Source Code

    In this application, I am adding data programmatically. I am not accessing data from any database. Perhaps you may want to access data from a data source. Method is same. In this sample, I create two DataTable objects A and B programmatically. The source code listed in Listing 1 shows CreateDataTableA and DataTableB methods, which create table A and B.

    As you can see from Listing 1, the DataTable class is used to create a table. DataColumn class is used to create columns to the table and DataRow class is used to add rows to a data table.

    Listing 1. Creating DataTable A and B.

    // Create DataTable A

    private DataTable CreateDataTableA()
    {
    DataTable aTable = new DataTable("A");
    DataColumn dtCol;
    DataRow dtRow;
    // Create ID column and add to the DataTable.
    dtCol = new DataColumn();
    dtCol.DataType= System.Type.GetType("System.Int32");
    dtCol.ColumnName = "ID";
    dtCol.AutoIncrement = true;
    dtCol.Caption = "ID";
    dtCol.ReadOnly = true;
    dtCol.Unique = true;
    // Add the column to the DataColumnCollection.
    aTable.Columns.Add(dtCol);
    // Create Name column and add to the table
    dtCol = new DataColumn();
    dtCol.DataType= System.Type.GetType("System.String");
    dtCol.ColumnName = "FName";
    dtCol.AutoIncrement = false;
    dtCol.Caption = "First Name";
    dtCol.ReadOnly = false;
    dtCol.Unique = false;
    aTable.Columns.Add(dtCol);
    // Create Last Name column and add to the table.
    dtCol = new DataColumn();
    dtCol.DataType= System.Type.GetType("System.String");
    dtCol.ColumnName = "LName";
    dtCol.AutoIncrement = false;
    dtCol.Caption = "Last Name";
    dtCol.ReadOnly = false;
    dtCol.Unique = false;
    aTable.Columns.Add(dtCol);
    // Create three rows to the table
    dtRow = aTable.NewRow();
    dtRow["ID"] = 1001;
    dtRow["FName"] = "Mahesh";
    dtRow["LName"] = "Chand" ;
    aTable.Rows.Add(dtRow);
    dtRow = aTable.NewRow();
    dtRow["ID"] = 1002;
    dtRow["FName"] = "Melanie";
    dtRow["LName"] = "Talmadge" ;
    aTable.Rows.Add(dtRow);
    dtRow = aTable.NewRow();
    dtRow["ID"] = 1003;
    dtRow["FName"] = "Vinay";
    dtRow["LName"] = "Bansal" ;
    aTable.Rows.Add(dtRow);
    return aTable;
    }
    // Create DataTable B
    private DataTable CreateDataTableB()
    {
    DataTable bTable = new DataTable("B");
    DataColumn dtCol;
    DataRow dtRow;
    // Create ID column and add to the DataTable.
    dtCol = new DataColumn();
    dtCol.DataType= System.Type.GetType("System.Int32");
    dtCol.ColumnName = "CustomerID";
    dtCol.AutoIncrement = true;
    dtCol.Caption = "CustomerID";
    dtCol.ReadOnly = true;
    dtCol.Unique = true;
    // Add the column to the DataColumnCollection.
    bTable.Columns.Add(dtCol);
    // Create Name column and add to the table
    dtCol = new DataColumn();
    dtCol.DataType= System.Type.GetType("System.String");
    dtCol.ColumnName = "Name";
    dtCol.AutoIncrement = false;
    dtCol.Caption = "Name";
    dtCol.ReadOnly = false;
    dtCol.Unique = false;
    bTable.Columns.Add(dtCol);
    // Create Last Name column and add to the table.
    dtCol = new DataColumn();
    dtCol.DataType= System.Type.GetType("System.String");
    dtCol.ColumnName = "Address";
    dtCol.AutoIncrement = false;
    dtCol.Caption = "Address";
    dtCol.ReadOnly = false;
    dtCol.Unique = false;
    bTable.Columns.Add(dtCol);
    // Create three rows to the table
    dtRow = bTable.NewRow();
    dtRow["CustomerID"] = 11;
    dtRow["Name"] = "Mr. Peter Ferrera";
    dtRow["Address"] = "9th Street, Bank Road, NOIDA" ;
    bTable.Rows.Add(dtRow);
    dtRow = bTable.NewRow();
    dtRow["CustomerID"] = 21;
    dtRow["Name"] = "Ross Tomo";
    dtRow["Address"] = "North Street, Parkway Road, SunCity" ;
    bTable.Rows.Add(dtRow);
    dtRow = bTable.NewRow();
    dtRow["CustomerID"] = 31;
    dtRow["Name"] = "Dr. Jog Rocky";
    dtRow["Address"] = "Turnpike Avenue, Philly" ;
    bTable.Rows.Add(dtRow);
    return bTable;
    }

     

    Understanding Listing 1 is important because it shows the data source for our application. We will generate DataSet, DataView and DataViewManager from DataTable objects. Ok, first let's see how to view data from a DataTable in a DataGrid.

    To view a DataTable data in a DataGrid, just set DataSource property of DataGrid as DataTable object name. As you can see from Listing 2, I call CreateDataTableA method, which returns a DataTable. After that I set DataGrid.DataSource as DataTable A. This code is written on DataTable button click event handler.

    Listing 2. Binding a DataTable to a DataGrid.

    private

    void DataTableBtn_Click(object sender, System.EventArgs e)
    {
    // Create a DataTable and Bind it to the DataGrid
    DataTable A = CreateDataTableA();
    dataGrid1.DataSource = A;
    }

     

     

    Generating a DataView from a DataTable is pretty easy. You create a DataView object using DataView class constructor and pass a DataTable as default parameter. As you can see from Listing 3, I call CreateDataTableB, which returns a DataTable object. After that I call DataView contructor and pass DataTable as default parameter and set DataView as DataGrid's DataSource.

    Listing 3. Binding a DataView to a DataGrid.

    private

    void DataViewBtn_Click(object sender, System.EventArgs e)
    {
    // Create a DataView using Table B
    DataTable table = CreateDataTableB();
    DataView view = new DataView(table);
    // Bind DataView to the DataGrid
    dataGrid1.DataSource = view;
    }

    Naja.. hoffe das hilft ein wenig zum Verständnis.

    DataTables finde ich generell besser als arrays. Am besten ist dann noch ne Collection...

     

    Gruss

    Torsten

     

    Donnerstag, 8. Juli 2010 11:08
  • Hallo Torsten,

    in solchen Fällen wäre eine Quellenangabe oder gleich der Link empfehlenswerter,
    das sieht doch sehr stark aus nach: Data Binding in DataGrid Control - Part 1

    Wobei es sich jedoch um das Windows Forms DataGrid (.NET 1.x) handelt!
    Bei dem WPF DataGrid handelt es sich um anderes Steuerelement,
    das für den Einsatz unter WPF optimiert wurde.

    Die WPF Datenbindung wiederum arbeitet ebensogut mit einer ObservableCollection,
    siehe Optimieren der Leistung: Datenbindung

    Gruß Elmar

    Donnerstag, 8. Juli 2010 12:50
  • Da fehlt im XAML die Columns-Auflistung:

    XAML:

      <StackPanel Orientation="Horizontal">
       <Label Content="Tags" />
       <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
        <DataGrid.Columns>
         <DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" />
         </DataGrid.Columns>
       </DataGrid>
      </StackPanel>
    

    --
    Viele Gruesse
    Peter

    • Als Antwort vorgeschlagen DragothiX Freitag, 27. Mai 2011 14:40
    Donnerstag, 8. Juli 2010 13:20
  •  

    Die WPF Datenbindung wiederum arbeitet ebensogut mit einer ObservableCollection,
    siehe Optimieren der Leistung: Datenbindung

    ObservableCollection ist zwar meist die bessere Lösung. Wenn es aber nur um eine Anzeige geht, dann geht auch eine List(Of T). Hier mal Pattasattos Beispiel:

    XAML:

    <Window x:Class="WpfApplication1.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Title="MainWindow" Height="350" Width="525"
         xmlns:my="clr-namespace:WpfApplication1">
     <Window.Resources>
      <my:Category x:Key="vm"/>
     </Window.Resources>
     <StackPanel Orientation="Vertical" DataContext="{Binding Source={StaticResource vm}}">
      <StackPanel Orientation="Horizontal">
       <Label Content="Name" />
       <Label Content="{Binding Path=Name}" />
      </StackPanel>
      <StackPanel Orientation="Horizontal">
       <Label Content="Tags" />
       <DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
        <DataGrid.Columns>
         <DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" />
        </DataGrid.Columns>
       </DataGrid>
      </StackPanel>
     </StackPanel>
    </Window>
    

    Dazu der Code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace WpfApplication1
    {
     class ViewModel
     {
     }
    
     public class Category
     {
      public Category()
      {
       for (int i = 1; i < 10; i++)
       {
        Items.Add(new Item { Name = "Cat " + i.ToString() });
       }
      }
    
      public IList<Item> items = new List<Item>();
      public virtual String Name { get; set; }
      public virtual int Id { get; private set; }
    
      public virtual IList<Item> Items
      {
       get { return items; }
       set { items = value; }
      }
    
      public void AddItem(Item i)
      {
       i.Category = this;
       items.Add(i);
      }
     }
    
    
     public class Item
     {
      private int id;
      private IList<String> tags = new List<String>();
    
      public virtual String Name { get; set; }
    
      public virtual IList<String> Tags
      {
       get { return tags; }
       set { tags = value; }
      }
    
      public Category Category { get; set; }
     }
    }
    

    --
    Viele Gruesse
    Peter

    Donnerstag, 8. Juli 2010 14:02
  • Hallo Peter,

    das ist mir bewußt! Es ging vor allem um den Rest des (Copy'n Paste) Beitrags.

    Und in dem WPF Grundlagen Link auch auf IEnumerable als Minimalanforderung hingewiesen.

    Gruß Elmar

    Nachtrag @Peter:
    Deine Antwort, die sich hier nicht ins Forum traut habe ich via Bridge
    in Thunderbird gelesen (seltsam das sie dort angekommen ist -
    und die Probleme damit in MVP - NewsreaderAC auch ;-)

    Ich wollte einfach nur die falsche Richtung der Diskussion hier stoppen,
    ansonsten bin ich weder beleidigt noch irgendwas anderes ;-))
    • Bearbeitet Elmar Boye Donnerstag, 8. Juli 2010 20:30 Nachtrag
    Donnerstag, 8. Juli 2010 14:46
  •  
    das ist mir bewußt! Es ging vor allem um den Rest des (Copy'n Paste) Beitrags.

    Und in dem WPF Grundlagen Link auch auf IEnumerable als Minimalanforderung hingewiesen.

    Hi Elmar,
    mir ist klar, dass Dir das bewusst ist  (<-- Smiley, was vermutlich im Forum nicht angezeigt wird). Deshalb hatte ich auch nicht Dich direkt angesprochen, sondern nur nochmals einen vollständig funktionierenden Code gepostet. Das eigentliche Problem von mrparity war das fehlende "<DataGrid.Columns>".
     
    --
    Viele Gruesse
    Peter

    Donnerstag, 8. Juli 2010 15:55
  • Vielen Dank für die Antworten, das hat mir sehr weiter geholfen. Ich hoffe, dass ich zukünftig auch schlaue Antworten zu problemen anderer Leute schreiben kann :-)

    Vielen Dank nochmal,

     Patrick

    Freitag, 9. Juli 2010 07:56