Laden...

DataGrid an List<List<T>> binden

Letzter Beitrag vor einem Jahr 11 Posts 691 Views
DataGrid an List<List<T>> binden

Hallo,

ich habe mit OpenXML eine Excedatei mit ca. 5800 Zeilen und 175 Spalten in eine List<List<T>> Struktur eingelesen, wobei T eine Zelle darstellt. T enthält mehrere Properties, aber nur eines davon soll im DataGrid dargestellt werden,

Leider schaffe ich es nicht, diese Datenstruktur an das DataGrid zu binden und Onkel Google ist nicht sehr hilfreich!

Eine List<T> bekomme ich noch wie folgt dargestellt.

Wie muss ich weiter vorgehen, damit ich eine List<List<T>> an das DataGrid binden kann?

    public class Model
    {
        public string? UnImportantValue1 { get; set; }

        public string? UnImportantValue2 { get; set; }

        public string CellValue { get; set; }
    }
    public class MainWindowViewModel
    {
        public ObservableCollection<Model> Items { get; set; }

        public ICommand ColumnGeneratingEvent { get; set; }

        public MainWindowViewModel()
        {
            ColumnGeneratingEvent = new CcActionCommand(OnAutoGeneratingColumnExecuted, null);
            Create1DModelData();
        }

        private void Create1DModelData()
        {
            ItemsModel1D = new ObservableCollection<Model>();
            for (int row = 0; row < 10; row++)
            {
                ItemsModel1D.Add(new Model() { CellValue = $"Row {row} - Col 0", UnImportantValue1 = "Blah", UnImportantValue2 = "Blubb" });
            }
        }

        private void OnAutoGeneratingColumnExecuted(object obj)
        {
            if (obj is DataGridAutoGeneratingColumnEventArgs eventArgs)
            {
                eventArgs.Column.Header = "Header";
            }
        }
    }
        <!--ItemsModel1D DataGrid-->
        <DataGrid x:Name="TestDataGrid"
                  AlternatingRowBackground="LightSlateGray"
                  HorizontalAlignment="Stretch"
                  VerticalAlignment="Stretch"
                  AutoGenerateColumns="False"
                  SelectionUnit="Cell"
                  SelectionMode="Extended"
                  CanUserAddRows="False"
                  CanUserDeleteRows="False"
                  CanUserSortColumns="False"
                  CanUserReorderColumns="False"
                  EnableColumnVirtualization="True"
                  EnableRowVirtualization="True"                  
                  ItemsSource="{Binding ItemsModel1D}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Header" Binding="{Binding CellValue}" />
            </DataGrid.Columns>
            <behav:Interaction.Triggers>
                <behav:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=TestDataGrid}" >
                    <behav:InvokeCommandAction Command="{Binding ColumnGeneratingEvent, Mode=OneWay}" PassEventArgsToCommand="True" />
                </behav:EventTrigger>
            </behav:Interaction.Triggers>
        </DataGrid>

Unter der Prämisse Deinen Code zu verstehen (Du zeigst hier Code mit Eigenschaften, die namentlich so nicht stimmen können):

Bindungen funktionieren nur mit fixen Referenzen; Du darfst also die Bindung von ObservableCollection<Model> Items nicht verändern.

Statt also eine neue Instanz zu erzeugen, leere die bestehende und füge dann die neuen Einträge ein.

Arrghh, habe im ViewModel die falsche Zeile rausgelöscht beim Bereinigen!

Statt

public ObservableCollection<Model> Items { get; set; }

muss es lauten

public ObservableCollection<Model> ItemsModel1D { get; set; }

Nach all dem was ich bis bis jetzt gelesen habe, kann man offenbar keine zweidimensionale Liste an das Datagrid binden.

Ich werde es jetzt erstmal mit einer DataTable als ItemsSource weiter versuchen.

Ja, da stimmt - direkt ist das nicht möglich, aber schau dir mal die Antworten in How to populate a WPF grid based on a 2-dimensional array (auch wenn der Ersteller etwas anderes wollte) an.

Hallo Caveman

Wie Abt schon gemeint hat, ObservableCollection nur einmal zuweisen und binden.


        public ObservableCollection<Model> Items { get; } = new ObservableCollection<Model>();

Statt das Model im View zu verwenden könntest du auch ein ViewModel verwenden. Siehe Bild.

Edit:

Das wäre dann übrigens auch die Lösung für dein Problem mit List<List<T>>

            <DataGrid.Columns>
                <DataGridTextColumn Header="Header" Binding="{Binding CellValue}" />
            </DataGrid.Columns>
            <behav:Interaction.Triggers>
                <behav:EventTrigger EventName="AutoGeneratingColumn" SourceObject="{Binding ElementName=TestDataGrid}" >
                    <behav:InvokeCommandAction Command="{Binding ColumnGeneratingEvent, Mode=OneWay}" PassEventArgsToCommand="True" />
                </behav:EventTrigger>
            </behav:Interaction.Triggers>

Entweder Autogenerate Columns, oder Columns fest definieren. Beides gleichzeitig kommt nicht so gut.

@TH69: Das habe ich auch schon durchgelesen. Weiter unten in dem Thread wird das DataGrid2D-Control erwähnt, welches auch eine DataTable im Hintergrund verwendet.

@Alf Ator: Tut mir leid, aber ich weiß überhaupt nicht, was Du mir mitteilen willst. In Deinem Bild wird eine Spalte gezeigt. Wie kommst Du auf - sagen wir mal - 50 Spalten?

Ich möchte im Prinzip eine n,m-Matrix von Model oder meinetwegen CellViewModel an die DataGrid binden.

Edit: Ich habe im Übrigen auch die Antwort von Abt nicht verstanden, obwohl ich mir das mindestens 10x durchgelesen habe.

Zitat von Caveman

Edit: Ich habe im Übrigen auch die Antwort von Abt nicht verstanden, obwohl ich mir das mindestens 10x durchgelesen habe.

   private void Create1DModelData()
    {
        ItemsModel1D = new ObservableCollection<Model>();
        for (int row = 0; row < 10; row++)
        {
            ItemsModel1D.Add(new Model() { CellValue = $"Row {row} - Col 0", UnImportantValue1 = "Blah", UnImportantValue2 = "Blubb" });
        }
    }

Du erstellt hier eine neue Instanz von  ObservableCollection<Model>(); und damit machst Du die Bindung von WPF kaputt.
Die Instanz von ObservableCollection darf nur ein einziges Mal erzeugt werden.

public ObservableCollection<Model> Items { get; } = new ObservableCollection<Model>();

Und dann muss Deine Methode aussehen wie zB

   private void Create1DModelData()
    {
        ItemsModel1D.Clear()
        for (int row = 0; row < 10; row++)
        {
            ItemsModel1D.Add(new Model() { CellValue = $"Row {row} - Col 0", UnImportantValue1 = "Blah", UnImportantValue2 = "Blubb" });
        }
    }

WPF arbeitet mit fixen Referenzen - also die Referenz, die bein Binden des Modells existiert.
Eine neue Instanz ist eine neue Referenz → kennt WPF nicht mehr, Bindung kaputt.

Okay, sorry für den unverständlichen Beitrag 😦

Der Name CellViewModel ist etwas ungünstig gewählt. Ich hatte mich an deinem Beispielcode orientiert. 
RowViewModel wäre besser. Du kannst im RowViewModel' für jede Spalte, die im DataGrid angezeigt werden soll ein Property anlegen. Für 50 Spalten also 50 Properties.

Die Lösung passt eventuell nicht zu deinem UseCase. Was sollte denn mit den angezeigten Daten gemacht werden? Sollen die nur angezeigt, oder bearbeitet werden? Ändert sich das Excel-Dokument regelmässig, oder bleibt es gleich?

Jetzt ist der Groschen gefallen, was gemeint war!

Allerdings rufe ich die Methode Create1DModelData() in meinem Beispiel nur einmal auf, nämlich im Konstruktor.

Deshalb ist das nicht zutreffend. Mir ist schon klar, dass ich das bei mehrmaligem Methodenaufruf nicht machen darf.

Hintergrund: Es handelt sich um ein Testprojekt, in dem ich meine ganzen Versuche in eigene Methoden ausgelagert habe. Im Eingangspost habe ich dann die falsche Zeile gelöscht. In dem Testprojekt werden nur Datenstrukturen erzeugt, die im DataGrid angezeigt werden.

Zitat von Caveman

Allerdings rufe ich die Methode Create1DModelData() in meinem Beispiel nur einmal auf, nämlich im Konstruktor.

Ist trotzdem quatsch. Verwende einfach

public ObservableCollection<Model> Items { get; } = new ObservableCollection<Model>();

und Du brauchst auch keinen Setter mehr, was dann eine fehlerhafte Verwendung durch Compiler Support verhindert.