Laden...

Gleichzeitig versch. DataTables als DataSources für DataGridView und DataGridViewComboBoxColumns

Erstellt von Ornithopter vor 7 Jahren Letzter Beitrag vor 7 Jahren 2.315 Views
O
Ornithopter Themenstarter:in
9 Beiträge seit 2017
vor 7 Jahren
Gleichzeitig versch. DataTables als DataSources für DataGridView und DataGridViewComboBoxColumns

Eigentlich wollte ich folgende Überschrift, aber die ist zu lang:

DataGridView mit DataTable als DataSource und DataGridViewComboBoxColumns mit separaten DataTables als DataSources für die Items

Ich recherchiere schon länger zum Thema DataGridView mit DataTable als DataSource und DataGridViewComboBoxColumns mit separaten DataTables als DataSources für die jeweiligen Items.

Es gibt eine Reihe ähnlicher Themen, die mich leider nicht zur Lösung meines Problems geführt haben.

Wünschen würde ich mir eigentlich, dass so ein Programmcode möglich wäre, aber das funktioniert nicht (hilft aber vielleicht des Problem schnell zu erfassen, ansonsten danach die Erklärung):


        public void FillDataGridView(DataTable dataGridViewDataTable, int[] indexesOfColumnsThatAreDataGridViewDataColumns, DataTable[] itemsDataTables)
        {
            dataGridView1.DataSource = dataGridViewDataTable;

            for (int i = 0; i < indexesOfColumnsThatAreDataGridViewDataColumns.Count(); i++)
            {
                dataGridView1.Columns[indexesOfColumnsThatAreDataGridViewDataColumns[i]].Type = DataGridViewDataColumn; // Typ der Spalte im DataGridView
                dataGridView1.Columns[indexesOfColumnsThatAreDataGridViewDataColumns[i]].DataSource = itemsDataTables[i];

            }
        }

Ich möchte alle Werte aus der Datenbank übernehmen und nach eventueller Änderung von Werten im DataGridView, z.B. durch Auswahl einer anderen Auswahloption im Dropdownmenü einer DataGridViewComboBoxColumn ein Update zurück an die Datenbank machen. Also wäre doch DataTable als DataSource jeweils das Mittel der Wahl, da damit sehr einfach die Daten mit der Datenbank synchronisiert werden können.

**Erst zur Laufzeit ist bekannt, welche DataTable (bzw. Datenbanktabelle) für das DataGridView zugrunde liegt. **Dementsprechend ist vorher nicht bekannt, wie viele Spalten das DataGridView hat und welcher Art diese sind. Zur Laufzeit wird ausgewählt, welche Daten verwendet werden und dann steht auch die Info zur Verfügung, ob es sich um eine DataGridViewComboBoxColumn handeln soll. Also ist es nicht möglich den Designer zu verwenden und alle Lösungen mit Designer oder mit einer fest definierten Spaltenanzahl helfen mir nicht weiter.

Problem ist die **gleichzeitige **Verwendung verschiedener DataTables als DataSources der verschiedenen Objekte.

Wie lässt sich das machen?

Also:

Für die Beschriftung der Kopfzeile im DataGridView und später auch die Zellwerte:
DataGridView.DataSource = dataGridViewDataTable;

Für sich allein genommen funktioniert das, im konkreten Fall nur für die Kopfzeile, bei anderen Beispielen, wo schon Zellwerte existieren, auch für diese.

Für die ComboBoxItems, die aufpoppen:
dataGridViewComboBoxColumn1.DataSource = itemsDataTable1;
dataGridViewComboBoxColumn2.DataSource = itemsDataTable2;

Sobald ich versuche die DataGridViewComboBoxColumns zu verwenden, fehlen in den entsprechenden Spalten die Infos aus der dataGridViewDataTable. Die ComboBoxItems werden korrekt geladen, poppen auf und sind auswählbar.
Je nach Programmierung fällt die entsprechende DataGridViewTextBoxColumn weg oder erscheint weiterhin, was ich nur anhand der Bezeichnung in der Kopfzeile des DataGridViews erkenne, da derzeit in der dateGridViewDataTable noch keine Datensätze vorhanden sind.

Die DataGridViewComboBoxColumns sollen die DataGridViewTextBoxColumn an den entsprechenden Stellen aber einfach komplett ersetzen (aber deren Inhalt übernehmen), so dass die Daten aus der dataGridViewDataTable geladen werden und Änderungen im DataGridView auch an die dataGridViewDataTable weitergegeben werden, so dass ein Update der dataGridViewDataTable an die Datenbank ausreicht, alle Werte abzuspeichern.

Folgende Lösung führt z.B. dazu, dass die DataGridViewComboBoxColumns an der richtigen Stelle (2 und 3) eingefügt werden, aber die Werte fehlen und als normale Spalten anschließend folgen (4 und 5).

        private void FillDataGridView(int tableIndex)
        {
                DataTable dataSource = DataBaseInventaryObjects.InventaryObjectTypesDataSet.Tables[tableIndex];
                dataGridView.DataSource = dataSource;

            DataTable columnTypesDataTable = DataBaseInventaryObjects.InventaryObjectTypesColumnTypesDataSet.Tables[tableIndex];
            for (int i = 1; i < columnTypesDataTable.Columns.Count; i++)
            {

                string propertyType = columnTypesDataTable.Rows[0][i].ToString();


                if (OwnMethodsDataTable.IsEditable(propertyType, DataBase.TableType.PropertyType))
                {
                    DataTable dataTable = new DataTable(propertyType);
                    MySqlDataAdapter mySqlDataAdapter;
                    DataBaseFillDataTable.FillDataTableWithSchemaByTableDataBaseName(propertyType, ref dataTable, out mySqlDataAdapter);

                    DataGridViewComboBoxColumn comboboxColumn = new DataGridViewComboBoxColumn();
                    comboboxColumn.DisplayMember = dataTable.Columns[1].ColumnName;
                    comboboxColumn.ValueMember = dataTable.Columns[1].ColumnName;
                    comboboxColumn.DataPropertyName = dataTable.Columns[1].ColumnName;
                    comboboxColumn.DataSource = dataTable;

                    dataGridView.Columns.Insert(i, comboboxColumn);
                }
            }
        }

Folgende Lösung führt z.B. dazu, dass die DataGridViewComboBoxColumns fälschlicherweise an den Stellen 0 und 1 eingefügt werden, die Werte fehlen, die normale Spalte für die erste DataGridViewComboBoxColumn fehlt, während die normale Spalte für die zweite DataGridViewComboBoxColumn vorhanden ist.

Änderungen:1.dataGridView.DataSource wird zum Schluss gesetzt 1.Columns mit Add und nicht mit Insert hinzugefügt, da Insert zu einer Exception führen würde, da zu dem Zeitpunkt das DataGridView noch keine Spalten hat.

        private void FillDataGridView(int tableIndex)
        {
            DataTable columnTypesDataTable = DataBaseInventaryObjects.InventaryObjectTypesColumnTypesDataSet.Tables[tableIndex];
            for (int i = 1; i < columnTypesDataTable.Columns.Count; i++)
            {

                string propertyType = columnTypesDataTable.Rows[0][i].ToString();


                if (OwnMethodsDataTable.IsEditable(propertyType, DataBase.TableType.PropertyType))
                {
                    DataTable dataTable = new DataTable(propertyType);
                    MySqlDataAdapter mySqlDataAdapter;
                    DataBaseFillDataTable.FillDataTableWithSchemaByTableDataBaseName(propertyType, ref dataTable, out mySqlDataAdapter);

                    OwnMethodsDataTable.Print(dataTable);

                    DataGridViewComboBoxColumn comboboxColumn = new DataGridViewComboBoxColumn();
                    comboboxColumn.DisplayMember = dataTable.Columns[1].ColumnName;
                    comboboxColumn.ValueMember = dataTable.Columns[1].ColumnName;
                    comboboxColumn.DataPropertyName = dataTable.Columns[1].ColumnName;
                    comboboxColumn.DataSource = dataTable;

                    dataGridView.Columns.Add(comboboxColumn);
                }
            }
            DataTable dataSource = DataBaseInventaryObjects.InventaryObjectTypesDataSet.Tables[tableIndex];
            dataGridView.DataSource = dataSource;
        }

1.040 Beiträge seit 2007
vor 7 Jahren

Moin Ornithopter,

ich habe zwei gute und eine schlechte Nachricht(en) für dich:
Die erste gute Nachricht ist, dass der geschriebene Code korrekt ist - also zu den von dir geschilderten Ergebnissen passt.
Die schlechte Nachricht ist, dass du das, was du machen möchtest, einfach nicht in Codeform gebracht hast. 😉
Die zweite gute Nachricht ist, dass das, was du machen möchtest, definitiv auch technisch machtbar ist. 🙂

Vorab schon mal der Hinweis auf den [Artikel] Debugger: Wie verwende ich den von Visual Studio?.
Dadurch kann man das (korrekte) Verhalten prinzipiell nachvollziehen.

Dir macht hauptsächlich die Property DataGridView.AutoGenerateColumns (Standardwert true) das Leben schwer.
Diese Property sorgt dafür, dass beim Setzen der DataSource (Zeitpunkt!) alle * Spalten automatisch generiert werden. Die generierten Spalten sind je nach Typ stinknormale DataGridViewTextBoxColumn, DataGridViewCheckBoxColumn etc.
Weiterhin nutzt du sehr wahrscheinlich falsche Bezeichner für den DataPropertyName, wodurch einige Zellen leer bleiben.

  • alle heißt: alle fehlenden Spalten - siehe Fall 2.

Beispiele

Kurz 3 Beispiele zu automatisch generierten Spalten und manuell hinzugefügten Spalten und zu den Unterschieden, wann die DataSource gesetzt wird.

Gegeben ist eine DataTable MyFunnyDataTable mit den Spalten A (string), B (bool) und C (int).
Die DataGridView myFunnyDataGridView wurde im Designer lediglich auf das Fenster gezogen (ohne Anpassungen der Standardeinstellungen).

myFunnyDataGridView.AutoGenerateColumns = true; //Standard ist true, nur zur Veranschaulichung!
myFunnyDataGridView.DataSource = new MyFunnyDataTable()

Das Ergebnis sind 3 generierte Spalten, deren DataPropertyName jeweils auf "A", "B" bzw. "C" steht.

var myFunnyDataTable = new MyFunnyDataTable();

myFunnyDataGridView.Columns.Add(new DataGridViewTextBoxColumn
{
    HeaderText = "Meine schöne Spalte B",
    DataPropertyName = myFunnyDataTable.BColumn.ColumnName
});

myFunnyDataGridView.AutoGenerateColumns = true; //Standard ist true, nur zur Veranschaulichung!
myFunnyDataGridView.DataSource = myFunnyDataTable

Das Ergebnis sind auch in dem Fall 3 Spalten, allerdings sieht es so aus: "Meine schöne Spalte B", "A", "C". Für Spalte B hat er keine weitere Spalte generiert.
Wenn ich das in der Reference Source von Microsoft richtig überflogen habe, merkt er sich bereits vorhandene Spalten anhand des DataPropertyName und nutzt diese dann später wieder.
Das erklärt dann, warum du manchmal einige "Originalspalten" gesehen hast (wenn der DataPropertyName nicht gestimmt hat) und manchmal nicht (wenn der DataPropertyName stimmte).

var myFunnyDataTable = new MyFunnyDataTable();

myFunnyDataGridView.AutoGenerateColumns = true; //Standard ist true, nur zur Veranschaulichung!
myFunnyDataGridView.DataSource = myFunnyDataTable

myFunnyDataGridView.Columns.Add(new DataGridViewTextBoxColumn
{
    HeaderText = "Meine schöne Spalte B",
    DataPropertyName = myFunnyDataTable.BColumn.ColumnName
});

Das Ergebnis sind in dem Fall 4 Spalten: "A", "B", "C", "Meine schöne Spalte B".

Lösung für dein Problem

Du solltest dir überlegen, ob du AutoGenerateColumns auf false stellst, dann hast du den kompletten Aufbau der Spalten selbst in der Hand.
Alternativ kannst du auch weiterhin nachträglich die Spalten austauschen, musst dann aber auf die korrekten DataPropertyName achten und vor allem die generierten "Originalspalten" löschen - sonst siehst du doppelt.

Im Anhang gibt es noch ein kleines Bild von einem Beispiel.
Oben links eine DataTable für boolsche Werte, Value (bool) als ValueMember; Desc (string) als DisplayMember.
Oben rechts eine DataTable für erlaubte Zahlwerte, Value (int) als ValueMember/DisplayMember.
In der Mitte meine DataTable mit den Einträgen (Name string, Flag bool, Zahl int) und automatisch generierten Spalten.
Unten die gleiche DataTable, allerdings mit manuell hinzugefügten (ComboBox)Spalten und den jeweils hinterlegten Auswahlkatalogen.

3.003 Beiträge seit 2006
vor 7 Jahren

Es gibt auch die Attribute [Browsable] und [DisplayName]. Die beiden beeinflussen die bei AutoGenerateColumn = true erzeugten Spalten.


class DataExample
{
    [Browsable(false)]
     public string A { get; set; } //spalte wird nicht erzeugt
    [DisplayName("Spalte B")]
    public DateTime B { get; set; } //spaltenheader ist "Spalte B"
    public int C { get; } //normale erzeugung
}

Macht das Leben leichter (kein händisches Hinzufügen von Spalten, wie p!lle das macht (nein, das ist deshalb nicht falsch, nur umständlich)) und erlaubt die Verwendung von AutoGenerateColumns in mehr Fällen, so dass man es wirklich nur noch in Spezialfällen ausschalten muss.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

1.040 Beiträge seit 2007
vor 7 Jahren

Geht das auch bei DataTables?

3.003 Beiträge seit 2006
vor 7 Jahren

Ach, f...adammt^^ Kann ich nicht mit Sicherheit sagen, weil ich DataTables scheue wie der Teufel das Weihwasser und mir schlicht die Erfahrung damit fehlt. Es sollte mich aber wundern, wenn es 1:1 so ginge, und nicht einen Kniff braucht. Müsste man mal probieren.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

O
Ornithopter Themenstarter:in
9 Beiträge seit 2017
vor 7 Jahren

Vielen Dank schon mal für die ausführliche Hilfe! 😃

Einen Fehler konnte ich dadurch schon beheben:


comboboxColumn.DataPropertyName = dataGridViewDataSourceDataTable.Columns[1].ColumnName;

DisplayMember und ValueMember stehen für die speziellen Eigenschaften, die durch den Typ DataGridViewComboBoxColumn hinzukommen und aus der separaten DataSource gespeist werden.

DataPropertyName gehört zu den regulären Eigenschaften einer DataGridViewColumn und wird daher wie aus der für das ganze DataGridView verwendeten DataSource gespeist, die die Werte enthält.

Da nun beim Setzen der DataGridView.DataSource bestimmte DataPropertyNames bereit vorhanden sind, werden die entsprechenden Spalten nicht mehr automatisch hinzugefügt. Dafür fehlen dann aber die Werte.

Wie lässt sich das lösen?
1.Doch zuerst die DataGridView.DataSource setzen, die Spalten doppelt generieren und dann per Schleife die Werte in die DataGridViewComboBoxColumn übertragen und anschließend die ursprüngliche Spalte löschen? Die Spalten wären dann automatisch schon an den richtigen Stellen. Die Eigenschaft DataGridView.AutoGenerateColumns hätte ich dabei allerdings auf dem Standard gelassen und nicht wie vorgeschlagen auf false gesetzt. 1.Besteht dabei die Gefahr, dass bestimmte Eigenschaften vergessen werden? Immerhin wird die DataGridViewComboBoxColumn komplett manuell gefüllt ohne DataSource für die Werte, nur für die DropDownList. 1.Oder löst man das manuell besser anders? 1.Oder gibt es auch einen automatischen Weg?

1.040 Beiträge seit 2007
vor 7 Jahren

Dafür fehlen dann aber die Werte.

Ähm, nein. Ist der DataPropertyName korrekt gesetzt, werden die Werte angezeigt.

1....dann per Schleife die Werte in die DataGridViewComboBoxColumn übertragen...

Es ist nicht notwendig, Werte per Schleife woanders hin zu übertragen. Die entsprechende Column muss nur den korrekten DataPropertyName kennen...

1.Besteht dabei die Gefahr, dass bestimmte Eigenschaften vergessen werden? Immerhin wird die DataGridViewComboBoxColumn komplett manuell gefüllt ohne DataSource für die Werte, nur für die DropDownList.

...was meinst du damit? Auch eine DataGridViewComboBoxColumn muss einen DataPropertyName haben, damit die Column weiß, was sie anzeigen/"binden" soll (Werte).

Einfaches Beispiel

Gegeben sind 2 typisierte DataTables:

  • Entries (Entries_Name string, Entries_Integer int)
  • PossibleInteger (PossibleInteger_Value int, PossibleInteger_Desc string)
    Die Entries stellen die DataSource für die DataGridView da, die PossibleInteger die DataSource für eine ComboBox.

Diese beiden Tabellen werden nur im Konstruktor einer dummen Form befüllt.

var possibleIntegerTable = new DataSet1.PossibleIntegerDataTable();
possibleIntegerTable.AddPossibleIntegerRow(1, "One");
possibleIntegerTable.AddPossibleIntegerRow(3, "Three");
possibleIntegerTable.AddPossibleIntegerRow(5, "Five");
possibleIntegerTable.AddPossibleIntegerRow(19, "Nineteen");
            
var entriesTable = new DataSet1.EntriesDataTable();
entriesTable.AddEntriesRow("Eintrag 1", 19);
entriesTable.AddEntriesRow("Eintrag n", 5);

Die Entries werden einmal in einem "automatischen" DataGridView gesetzt (oberer Teil im Bild).

automaticDataGridView.DataSource = entriesTable;

Weiterhin werden in einem anderem DataGridView die Columns von Hand hinzugefügt (unterer Teil im Bild).
Spalte 2 und 3 unterscheiden sich im DisplayMember.

manualDataGridView.Columns.Add(new DataGridViewTextBoxColumn
{
    HeaderText = entriesTable.Entries_NameColumn.ColumnName,
    DataPropertyName = entriesTable.Entries_NameColumn.ColumnName
});

manualDataGridView.Columns.Add(new DataGridViewComboBoxColumn
{
    HeaderText = entriesTable.Entries_IntegerColumn.ColumnName,
    DataPropertyName = entriesTable.Entries_IntegerColumn.ColumnName,

    DisplayMember = possibleIntegerTable.PossibleInteger_ValueColumn.ColumnName,
    ValueMember = possibleIntegerTable.PossibleInteger_ValueColumn.ColumnName,
    DataSource = possibleIntegerTable
});

manualDataGridView.Columns.Add(new DataGridViewComboBoxColumn
{
    HeaderText = entriesTable.Entries_IntegerColumn.ColumnName,
    DataPropertyName = entriesTable.Entries_IntegerColumn.ColumnName,

    DisplayMember = possibleIntegerTable.PossibleInteger_DescColumn.ColumnName,
    ValueMember = possibleIntegerTable.PossibleInteger_ValueColumn.ColumnName,
    DataSource = possibleIntegerTable
});

manualDataGridView.AutoGenerateColumns = false;
manualDataGridView.DataSource = entriesTable;