none
Zwei Spalten einer dreispaltigen Tabelle auf Row- und Column-Header abtragen RRS feed

  • Frage

  • Hallo zusammen,

     

    ich möchte aus einer dreispaltigen Tabelle der Form

    | Breite | Höhe | Wert |

    eine Tabellendarstellung WinForms DataGridView) folgender Form erzeugen:

    • die Werte für 'Breite' sollen als Spalten in der neuen Tabelle erscheinen
    • die Werte für 'Höhe' sollen in die Rowheader der neuen Tabelle wandern
    • die Werte bleiben die Werte und stellen den Inhalt der neuen Tabelle dar

    Hat jemand schonmal eine solche Transformation vorgenommen und kann mir einen Hinweis geben, wie das am elegantesten zu lösen ist? Alle meine bisherigen Versuche funktionieren zwar, sind aber alles andere als gut lesbar und verständlich (viel LINQ, viel Zwischenspeichern, viel Laufzeit, ...) :-(

    Gruß

    Florian

    Donnerstag, 29. Juli 2010 06:18

Antworten

  • Hallo Florian,

    einen Vorschlag wie es mit LINQ verträglich lesbar (via DataSetExtensions) aussieht:

    using System;
    using System.Data;
    using System.Linq;
    using System.Windows.Forms;
    
    namespace ElmarBoye.Samples.Forms
    {
      public partial class PivotTableForm : Form
      {
        public PivotTableForm()
        {
          InitializeComponent();
        }
    
        private DataSet CreateDataSet()
        {
          var dataSet = new DataSet("Pivot");
          var sourceTable = dataSet.Tables.Add("PivotSource");
          sourceTable.Columns.Add("Breite", typeof(int));
          sourceTable.Columns.Add("Höhe", typeof(int));
          sourceTable.Columns.Add("Wert", typeof(int));
    
          /*
          // Konstante Werte zur Kontrolle
          for (int höhe = 100; höhe <= 1000; höhe += 100)
          {
            for (int breite = 100; breite <= 200; breite += 10)
            {
              sourceTable.Rows.Add(breite, höhe, höhe * breite);
            }
          }
          */
    
          // Zufällige Werte (mit Lücken)
          var rnd = new Random();
          
          for (var rowIndex = 0; rowIndex < 1000; rowIndex++)
            sourceTable.Rows.Add(rnd.Next(100) * 10, rnd.Next(100) * 10, rnd.Next(1000));
          
          sourceTable.AcceptChanges();
    
    
          var pivotTable = dataSet.Tables.Add("PivotResult");
          pivotTable.Columns.Add("Höhe", typeof(int));
    
          // Ermitteln aller Breiten
          var breiten = (from s in sourceTable.AsEnumerable()
            let Breite = s.Field<int>("Breite")
            orderby Breite
            select Breite).Distinct();
    
          // und Anlage als Spalten
          foreach (var breite in breiten)
          {
            var column = pivotTable.Columns.Add(breite.ToString(), typeof(int));
          }
    
          var pivotRows = from s in sourceTable.AsEnumerable()
            let Höhe = s.Field<int>("Höhe")
            group s by Höhe into Höhen
            select new
            {
              Höhe = Höhen.Key,
    
              Breiten = from h in Höhen
                let Breite = h.Field<int>("Breite")
                group h by Breite into Breiten
                select new 
                {
                  Breite = Breiten.Key,
                  // Hier Summe, andere Aggregate möglich
                  Summe = Breiten.Sum(row => row.Field<int?>("Wert"))
                }
            };
    
          // Durchlaufen aller Zeilen (hier Höhe)
          foreach (var pivotRow in pivotRows)
          {
            var newRow = pivotTable.NewRow();
            newRow["Höhe"] = pivotRow.Höhe;
            
            // Durchlaufen aller Breiten
            foreach (var pivotColumn in pivotRow.Breiten)
            {
              newRow[pivotColumn.Breite.ToString()] = pivotColumn.Summe;
            }
            pivotTable.Rows.Add(newRow);
          }
          pivotTable.AcceptChanges();
    
          return dataSet;
        }
    
        private void PivotTableForm_Load(object sender, EventArgs e)
        {
          var dataSet = this.CreateDataSet();
    
          var view = new DataView(dataSet.Tables["PivotResult"], "", "[Höhe]", DataViewRowState.CurrentRows);
          this.dataGridView1.DataSource = view;
        }
      }
    }
    
    

    (Das ich es gleich in SQL erledigen würde, lassen wir mal aussen vor)

    Gruß Elmar

     

    • Als Antwort markiert Florian Simon Donnerstag, 29. Juli 2010 12:30
    Donnerstag, 29. Juli 2010 10:22
    Beantworter

Alle Antworten

  • Hallo Florian,

    klar, kann man nit Linq machen, aber ich finde ein einfaches solides foreach auch nicht schlecht.
    Also mal als Vorschlag:

    using System;
    using System.Data;
    using System.Windows.Forms;
    
    namespace WinSpaltenTransformation
    {
     public partial class Form1 : Form
     {
      public Form1()
      {
       InitializeComponent();
      }
    
      DataTable quelle = new DataTable();
      DataTable ziel = new DataTable();
      DataGridView dgvQuelle = new DataGridView();
      DataGridView dgvZiel = new DataGridView();
    
      private void Form1_Load(object sender, EventArgs e)
      {
       Width = 1000;
       ErzeugeQuellTabelle();
       FülleDemoDatenInQuelle(10);
       PositioniereDgvQuelle();
       PositioniereDgvZiel();
       dgvQuelle.DataSource = quelle;
       TransformiereQuelleInZiel();
       dgvZiel.DataSource = ziel;
      }
    
      /// <summary>
      /// Aus Spalten: | Breite | Höhe | Wert |
      /// soll eine Tabellendarstellung WinForms DataGridView folgender Form erzeugen:
      /// • die Werte für 'Breite' sollen als Spalten in der neuen Tabelle erscheinen 
      /// • die Werte für 'Höhe' sollen in die Rowheader der neuen Tabelle wandern 
      /// • die Werte bleiben die Werte und stellen den Inhalt der neuen Tabelle dar 
      /// </summary>
      private void TransformiereQuelleInZiel()
      {
       foreach (DataRow dr in quelle.Rows)
       {
        var col = ziel.Columns.Add();
        try
        {
         col.ColumnName = dr[Cols.Höhe.Ordinal].ToString();
         col.DataType = Cols.Breite.DataType;
        }
        catch (Exception exp)
        {
         MessageBox.Show(exp.Message);
         return;
        }
       }
       ziel.Rows.Clear();
       DataRow zielRow = ziel.NewRow();
       ziel.Rows.Add(zielRow);
       for (int i = 0; i < quelle.Rows.Count; i++)
       {
        DataRow dr = quelle.Rows[i];
        zielRow[i] = dr[Cols.Wert];
       }
      }
    
      int abstandDGV = 10;
    
      private void PositioniereDgvZiel()
      {
       Controls.Add(dgvZiel); dgvZiel.AllowUserToAddRows = false;
       dgvZiel.Top = dgvQuelle.Top + dgvQuelle.Height + abstandDGV;
       dgvZiel.Width = ClientRectangle.Width;
       dgvZiel.Height = ClientRectangle.Height / 2 - abstandDGV;
       dgvZiel.Anchor = AnchorStyles.Left | AnchorStyles.Bottom |
        AnchorStyles.Right;
      }
    
      private void PositioniereDgvQuelle()
      {
       Controls.Add(dgvQuelle); dgvQuelle.AllowUserToAddRows = false;
       dgvQuelle.Width = ClientRectangle.Width;
       dgvQuelle.Height = ClientRectangle.Height / 2 - abstandDGV;
       dgvQuelle.Anchor = AnchorStyles.Left | AnchorStyles.Top |
        AnchorStyles.Right | AnchorStyles.Bottom;
      }
    
      private void FülleDemoDatenInQuelle(int anzZeilen)
      {
       int breiteHöhe = 10;
       int breiteHöheZusatz = 5;
       double wert = 20.0;
       double wertZusatz = 2.0;
    
       for (int i = 0; i < anzZeilen; i++)
       {
        DataRow dr = quelle.NewRow();
        foreach (DataColumn col in quelle.Columns)
        {
         if (col.DataType == typeof(int))
         {
          dr[col.Ordinal] = breiteHöhe;
          breiteHöhe += breiteHöheZusatz;
         }
         else if (col.DataType == typeof(double))
         {
          dr[col.Ordinal] = wert;
          wert += wertZusatz;
         }
         else
         {
          MessageBox.Show("Typ '" + col.DataType + "' ist nicht unterstützt.");
          return;
         }
        }
        quelle.Rows.Add(dr);     
       }
      }
    
      class Cols
      {
       static public DataColumn Breite;
       static public DataColumn Höhe;
       static public DataColumn Wert;
      }
    
      private void ErzeugeQuellTabelle()
      {
       quelle.Columns.Clear();
       Cols.Breite = quelle.Columns.Add("Breite", typeof(int));
       Cols.Höhe = quelle.Columns.Add("Höhe", typeof(int));
       Cols.Wert = quelle.Columns.Add("Wert", typeof(double));
      }
     }
    }
    


    ciao Frank

    Donnerstag, 29. Juli 2010 08:02
  • Hallo Frank,

     

    danke für Deine Antwort, aber bei Deinem Vorschlag geht leider der Bezug zur "Breite" verloren. Ich hatte eher an so etwas gedacht:

    private DataTable data;
    
    private void buttonFill_Click(object sender, EventArgs e)
    {
     this.data = new DataTable();
     this.data.Columns.Add("col1", typeof(int));
     this.data.Columns.Add("col2", typeof(int));
     this.data.Columns.Add("col3", typeof(int));
     this.data.Rows.Add(100, 200, 6);
     this.data.Rows.Add(100, 300, 7);
     this.data.Rows.Add(200, 100, 5);
     this.data.Rows.Add(200, 200, 6);
     this.data.Rows.Add(200, 400, 8);
    
     var rowHeaders = (from DataRow row in this.data.AsEnumerable()
              orderby row.Field<int>("col1")
              select row.Field<int>("col1")).Distinct();
    
     var columnHeaders = (from DataRow row in this.data.AsEnumerable()
                orderby row.Field<int>("col2")
                select row.Field<int>("col2")).Distinct();
    
     DataTable dataSource = new DataTable("TableData");
     foreach(var col in columnHeaders)
     {
      DataColumn dc = dataSource.Columns.Add(col.ToString());
      dc.ColumnName = col.ToString();
     }
    
     foreach(var row in rowHeaders)
     {
      List<int?> array = new List<int?>();
      foreach(var col in columnHeaders)
      {
       var result = this.data.AsEnumerable().SingleOrDefault(
        dataRow => 
         (dataRow.Field<int>("col1") == row) && (dataRow.Field<int>("col2") == col));
       if(result != null)
       {
        array.Add(result.Field<int>("col3"));
       }
       else
       {
        array.Add(null);
       }
      }
    
      DataRow addRow = dataSource.NewRow();
      object[] itemArray = new object[addRow.ItemArray.Length];
      for(int i = 0; i < addRow.ItemArray.Length; i++)
      {
       if(i > (array.Count - 1))
       {
        itemArray[i] = null;
       }
       else
       {
        itemArray[i] = array[i];
       }
      }
      addRow.ItemArray = itemArray;
      dataSource.Rows.Add(addRow);
     }
     this.dataGridView.DataSource = dataSource;
     for(int i = 0; i < rowHeaders.Count(); i++)
     {
      this.dataGridView.Rows[i].HeaderCell.Value = rowHeaders.ElementAt(i).ToString();
     }
    }
    

    Dann wäre der "Breite"-Wert immer als RowHeader dargestellt. Allerdings finde ich das ganze nicht unbedingt gut lesbar. Gibt's Ideen zur vereinfachung?

     

    Gruß

    Florian

    Donnerstag, 29. Juli 2010 09:42
  • Hallo Florian,

    einen Vorschlag wie es mit LINQ verträglich lesbar (via DataSetExtensions) aussieht:

    using System;
    using System.Data;
    using System.Linq;
    using System.Windows.Forms;
    
    namespace ElmarBoye.Samples.Forms
    {
      public partial class PivotTableForm : Form
      {
        public PivotTableForm()
        {
          InitializeComponent();
        }
    
        private DataSet CreateDataSet()
        {
          var dataSet = new DataSet("Pivot");
          var sourceTable = dataSet.Tables.Add("PivotSource");
          sourceTable.Columns.Add("Breite", typeof(int));
          sourceTable.Columns.Add("Höhe", typeof(int));
          sourceTable.Columns.Add("Wert", typeof(int));
    
          /*
          // Konstante Werte zur Kontrolle
          for (int höhe = 100; höhe <= 1000; höhe += 100)
          {
            for (int breite = 100; breite <= 200; breite += 10)
            {
              sourceTable.Rows.Add(breite, höhe, höhe * breite);
            }
          }
          */
    
          // Zufällige Werte (mit Lücken)
          var rnd = new Random();
          
          for (var rowIndex = 0; rowIndex < 1000; rowIndex++)
            sourceTable.Rows.Add(rnd.Next(100) * 10, rnd.Next(100) * 10, rnd.Next(1000));
          
          sourceTable.AcceptChanges();
    
    
          var pivotTable = dataSet.Tables.Add("PivotResult");
          pivotTable.Columns.Add("Höhe", typeof(int));
    
          // Ermitteln aller Breiten
          var breiten = (from s in sourceTable.AsEnumerable()
            let Breite = s.Field<int>("Breite")
            orderby Breite
            select Breite).Distinct();
    
          // und Anlage als Spalten
          foreach (var breite in breiten)
          {
            var column = pivotTable.Columns.Add(breite.ToString(), typeof(int));
          }
    
          var pivotRows = from s in sourceTable.AsEnumerable()
            let Höhe = s.Field<int>("Höhe")
            group s by Höhe into Höhen
            select new
            {
              Höhe = Höhen.Key,
    
              Breiten = from h in Höhen
                let Breite = h.Field<int>("Breite")
                group h by Breite into Breiten
                select new 
                {
                  Breite = Breiten.Key,
                  // Hier Summe, andere Aggregate möglich
                  Summe = Breiten.Sum(row => row.Field<int?>("Wert"))
                }
            };
    
          // Durchlaufen aller Zeilen (hier Höhe)
          foreach (var pivotRow in pivotRows)
          {
            var newRow = pivotTable.NewRow();
            newRow["Höhe"] = pivotRow.Höhe;
            
            // Durchlaufen aller Breiten
            foreach (var pivotColumn in pivotRow.Breiten)
            {
              newRow[pivotColumn.Breite.ToString()] = pivotColumn.Summe;
            }
            pivotTable.Rows.Add(newRow);
          }
          pivotTable.AcceptChanges();
    
          return dataSet;
        }
    
        private void PivotTableForm_Load(object sender, EventArgs e)
        {
          var dataSet = this.CreateDataSet();
    
          var view = new DataView(dataSet.Tables["PivotResult"], "", "[Höhe]", DataViewRowState.CurrentRows);
          this.dataGridView1.DataSource = view;
        }
      }
    }
    
    

    (Das ich es gleich in SQL erledigen würde, lassen wir mal aussen vor)

    Gruß Elmar

     

    • Als Antwort markiert Florian Simon Donnerstag, 29. Juli 2010 12:30
    Donnerstag, 29. Juli 2010 10:22
    Beantworter
  • Hallo Elmar,

     

    das ist doch auf jeden Fall besser lesbar als mein Versuch.

    Die Möglichkeit SQL bietet sich mir an der Stelle nicht. Die Daten werden aus einem Text-File gelesen. Da habe ich leider keinen Einfluss mehr drauf.

    Gruß

    Florian

    Donnerstag, 29. Juli 2010 12:30
  • Hallo Florian,

        > danke für Deine Antwort, aber bei Deinem Vorschlag geht leider der Bezug zur "Breite" verloren.

    Ja, ich hatte schon vermutet, dass Du das willst, ich habe erstmal genau das codiert, was Du wolltest, nämlich die Werte.
    Wenn Du noch zusätzlich die Breite benötigst kannst Du in meinem Quellcode am Ende von TransformiereQuelleInZiel() folgendes hinzufügen:

     zielRow = ziel.NewRow(); ziel.Rows.Add(zielRow);
     for (int i = 0; i < quelle.Rows.Count; i++)
     {
      DataRow dr = quelle.Rows[i]; zielRow[i] = dr[Cols.Breite];
     }
    
    

    ich finde das alles gut überschaubar. Je nach Bedürfnis natürlich kapselbar oder in LINQ formulierbar.

    ciao Frank
    Donnerstag, 29. Juli 2010 13:08