Benutzer mit den meisten Antworten
Zwei Spalten einer dreispaltigen Tabelle auf Row- und Column-Header abtragen

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
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
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
-
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
-
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
-
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