Laden...

Binding einer DataTable an ein DataGrid langsam

Erstellt von nina-nanu vor 10 Jahren Letzter Beitrag vor 10 Jahren 6.511 Views
N
nina-nanu Themenstarter:in
10 Beiträge seit 2009
vor 10 Jahren
Binding einer DataTable an ein DataGrid langsam

Hey,

ich lade den Inhalt von unterschiedlichen Excel-Listen in eine DataTable.
Diese möchte ich anschließend an ein DataGrid binden.


<DataGrid Name="grdExcel" 
ItemsSource="{Binding DataTableWithExcelContent}" 
AutoGenerateColumns="True" />

Leider friert die Anwendung beim Binden mehrere Sekunden ein, erst dann wird die DataTable angezeigt.

Stellt man unter "Extras > Optionen > Debugging > Ausgabefenster" die Ablaufverfolgung für Datenbindung auf "Alle" sieht man, dass offensichtlich für jede Zeile mehrere Probleme auftreten:

Fehlermeldung:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=AreRowDetailsFrozen; DataItem=null; target element is 'DataGridDetailsPresenter' (Name=''); target property is 'SelectiveScrollingOrientation' (type 'SelectiveScrollingOrientation')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=HeadersVisibility; DataItem=null; target element is 'DataGridRowHeader' (Name=''); target property is 'Visibility' (type 'Visibility')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Name; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Alter; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=ValidationErrorTemplate; DataItem=null; target element is 'Control' (Name=''); target property is 'Template' (type 'ControlTemplate')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=(0); DataItem=null; target element is 'Control' (Name=''); target property is 'Visibility' (type 'Visibility')

Weiß jemand Rat?

Im Anhang befindet sich eine kleine Demo. Aufgrund der wenigen Daten fällt der Zeitaufwand nicht auf, jedoch sieht man die Binding-Probleme im Ausgabefenster von Visual Studio...

P
660 Beiträge seit 2008
vor 10 Jahren

Hallo,

ohne mir jetzt das projekt angesehen zu haben, versuch mal "AutoGenerateColumns" auf False zu
setzen und die Columns selbst zu definieren. AutoGenerateColumns braucht schon etwas an Zeit
für die Generierung.

MfG
ProGamer*Der Sinn Des Lebens Ist Es, Den Sinn Des Lebens Zu Finden! *"Wenn Unrecht zu Recht wird dann wird Widerstand zur Pflicht." *"Ignorance simplifies ANY problem." *"Stoppt die Piraterie der Musikindustrie"

N
nina-nanu Themenstarter:in
10 Beiträge seit 2009
vor 10 Jahren

Hey ProGamer,

vielen Dank für Deinen Tipp. Leider hilft es nicht, die Spalten manuell zu erstellen:


foreach (DataColumn dc in dt.Columns)
{
   DataGridTextColumn col = new DataGridTextColumn();
   col.Header = dc.ColumnName;
   col.Binding = new Binding(dc.ColumnName);                

   grdExcel.Columns.Add(col);
}

this.DataContext = this

Sobald ich den DateContext setze, treten die Fehler für jede Zeile auf. Erst dann wird das DataGrid angezeigt... 😕

Mir ist nicht klar, welchen Wert er binden will, bzw. wo ich einen fallback value setzen sollte, und welches DataItem null ist?

P
660 Beiträge seit 2008
vor 10 Jahren

Morgen,

wieso erstellst du das Binding nicht im XAML?
wieso Codebehind? O.o

was die Fehler angeht: Deine werte werden doch angezeigt oder?
Du kannst diese verändern und die änderungen werden übernommen, richtig?
Wenn ja, dann gibts keine Fehler.
Und wie gesagt, die trägheit könnte deswegen sein, weil die Spalten automatisch erzeugt werden.

ein einfaches Beispiel würde so aussehen:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyCollection}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
        <DataGridTextColumn Header="Alter" Binding="{Binding Path=Age}"/>
    </DataGrid.Columns>
</DataGrid>

MfG
ProGamer*Der Sinn Des Lebens Ist Es, Den Sinn Des Lebens Zu Finden! *"Wenn Unrecht zu Recht wird dann wird Widerstand zur Pflicht." *"Ignorance simplifies ANY problem." *"Stoppt die Piraterie der Musikindustrie"

N
nina-nanu Themenstarter:in
10 Beiträge seit 2009
vor 10 Jahren

Hey,

vielen Dank, dass Du Dir Gedanken um mein Problemchen machst! 😃
Ich kann die Spalten leider nicht in XAML erstellen, da der Inhalt meines DataSets dynamisch ist
(Es werden Excel-Tabellen geladen, die in der DataTable gespeichert und im DataGrid angezeigt werden sollen).

Das Binding funktioniert auch, nur dauert es relativ lange (je nach Anzahl der Rows mehrere Sekunden) bis das DataGrid anzeigt wird (auch wenn ich die Spalten vorher manuell erzeuge).

Stellt man in Visual Studio ein, dass alle Binding-Meldungen ausgegeben werden sollen, erscheint für jede Zeile und Zelle je folgende Nachricht im Ausgaberfenster:

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Name; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

Anschließend wird das DataGrid korrekt angezeigt. Was kann die Ursache für diese Meldung sein, und was kann man dagegen unternehmen?

Für Ratschläge wäre ich super dankbar

Die Nina

4.939 Beiträge seit 2008
vor 10 Jahren

Hallo nina-nanu,

lies dir mal die beiden Beiträge zu diesem Thema bei stackoverflow.com durch:
Getting many Binding “Information” in WPF output window
WPF slow performance - many DataItem=null binding warnings

Fazit: um die Performance zu erhöhen, solltest du FallbackValue für jedes Binding angeben, z.B.


<TextBlock Text={Binding Name, FallbackValue="[empty]"}>

P.S. Die Eigenschaft FallbackValue kannst du auch per Code bei der Binding-Klasse setzen.

H
114 Beiträge seit 2007
vor 10 Jahren

Hallo nina-nanu,

noch eine Kleinigkeit...Ich hab mir eben das von dir angehängte Beispiel angeschaut und dabei folgendes festgestellt...
Du nutzt zur Darstellung des DataGrids folgenden XAML-Code:

<StackPanel>
    <Button Name="btnCreateDataTable" Content="Create DataTable with sample content" Click="btnCreateDataTable_Click" />
    <Button Name="btnSetDataContext" Content="Bind DataTable to DataGrid" Click="btnSetDataContext_Click" />

    <DataGrid Name="grdExcel" ItemsSource="{Binding DataTableWithExcelContent}" AutoGenerateColumns="True" />
        
</StackPanel>

Änderst du diesen allerdings wie folgt ab, bleibt die Darstellung gleich, aber die Performance wird deutlich besser:

<DockPanel>

    <StackPanel DockPanel.Dock="Top">
        <Button Name="btnCreateDataTable" Content="Create DataTable with sample content" Click="btnCreateDataTable_Click" />
        <Button Name="btnSetDataContext" Content="Bind DataTable to DataGrid" Click="btnSetDataContext_Click" />
    </StackPanel>

    <DataGrid Name="grdExcel" ItemsSource="{Binding DataTableWithExcelContent}" AutoGenerateColumns="True" />

</DockPanel>

ich bin nun kein XAML-Experte, aber ich vermute folgendes...
Ist das DataGrid in dem StackPanel, so werden sofort alle Elemente für die Darstellung berechnet, da das StackPanel keine maximal mögliche Höhe vorgibt und somit die Virtualisierung der darzustellenden Elemente nicht greifen kann. Dies sieht man auch daran, dass zum DataGrid keine Scrollbars angezeigt werden...
Steckt man das DataGrid aber nun in den DockPanel, so bekommt dieses nur eine bestimmte Höhe als maximal möglichen Wert. Dadurch kann nun die Virtualisierung greifen, da nur noch eine bestimmte Menge an Elementen sichtbar ist und dargestellt werden muss.
(Dies kann man by the way auch noch erreichen, in dem man dem DataGrid eine maximale Höhe innerhalb des StackPanel zuweist.)
Diese Angaben sind ohne Gewähr, da sie nur auf meinen Beobachtungen basieren, evtl. kann das jemand aus dem Forum fundierter begründen und bestätigen oder korrigieren. 😉
Aber vielleicht hilft dir diese Beobachtung ja schon weiter...

Grüße, HiGHteK

N
nina-nanu Themenstarter:in
10 Beiträge seit 2009
vor 10 Jahren

Hey HiGHteK,

viele Dank für den Tipp. Habe ich gerade ausprobiert und es stimmt:

Befindet sich das DataGrid innerhalb eines DockPanels werden nur die Zellen gerendert, welche auch angezeigt werden. Dies bringt natürlich bei langen Listen einen enormen Geschwindigkeitsvorteil:


<DockPanel LastChildFill="true">
	...
    <DataGrid Name="grdExcel" 
			ItemsSource="{Binding DataTableWithExcelContent}" 
			AutoGenerateColumns="True" 
			
			EnableColumnVirtualization="True"
			EnableRowVirtualization="True"
			VirtualizingStackPanel.IsVirtualizing="True"
			VirtualizingStackPanel.VirtualizationMode="Standard"
	/>	
</DockPanel>

Zusätzlich registriere ich vor dem Setzen des DataContexts das DataGrid.AutoGeneratingColumn-Event, um dort für jede Spalte einen FallBack-Value zu setzen:


grdExcel.AutoGeneratingColumn += new EventHandler<DataGridAutoGeneratingColumnEventArgs>(grdExcel_AutoGeneratingColumn);
this.DataContext = this;

// Wird ausgeführt, wenn eine Spalte im DataGrid erstellt wird
void grdExcelPreview_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
    if (e.Column.GetType() == typeof(DataGridTextColumn))
    {
        ((e.Column) as DataGridTextColumn).Binding.FallbackValue = String.Empty;
    }
}

Dies reduziert die Anzeige-Zeit weiter deutlich. Und die ursprünglichen vielen Binding-Fehlermeldungen im Ausgabefenster reduzieren sich auf vier pro Zeile:

Fehlermeldung:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=AreRowDetailsFrozen; DataItem=null; target element is 'DataGridDetailsPresenter' (Name=''); target property is 'SelectiveScrollingOrientation' (type 'SelectiveScrollingOrientation')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=HeadersVisibility; DataItem=null; target element is 'DataGridRowHeader' (Name=''); target property is 'Visibility' (type 'Visibility')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=ValidationErrorTemplate; DataItem=null; target element is 'Control' (Name=''); target property is 'Template' (type 'ControlTemplate')

System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=(0); DataItem=null; target element is 'Control' (Name=''); target property is 'Visibility' (type 'Visibility')

Wie kann ich für diese Werte einen Fallback-Value festlegen?