I have a simple WPF application I'm working on, which performs a SQL query and displays the resulting data in a DataGrid.
Everything works as expected, except that performance is terrible. The length of time from clicking a button to load data, and actually seeing the data show up in the DataGrid, is on the order of 3-4 seconds. It is a good bit faster with row virtualization turned on, but I've had to turn it off since I need to be able to perform operations on cells that are no longer visible after scrolling. And even with virtualization turned on, getting the data displayed is slower than I would like.
I first assumed it was the SQL database that was being slow, but I did some tests and found that I'm reading all data from the SQL server (several hundred rows) into a DataTable in a fraction of a second. It isn't until I bind the DataTable to the DataGrid's DataContext that everything locks up for several seconds.
So why is the DataContext so slow? My computer is brand new, so I have a hard time understanding why it takes any length of time to fill out the DataGrid, considering how quickly I retrieve the data in the first place.
(I also tried binding to the DataGrid's ItemSource rather than the DataContext, but performance was the same.)
Is there an alternative method of loading data into a DataGrid that has more reasonable performance? Even an alternative to DataGrid might be worth exploring if needed.
Edit: At Vlad's suggestion, I tried another test which bypassed the SQL query. I instead filled the DataTable with 1000 rows of random generated data. No change. The data was generated and written to the DataTable in under a second. However, attaching that to the DataGrid took over 20 seconds.
Below is the DataGrid XAML I'm using. Other than the binding, it is very simple, no custom code attached to it.
<DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" Name="dataGridWellReadings" GridLinesVisibility="None" CanUserResizeRows="False" SelectionUnit="Cell" AlternatingRowBackground="#FFE0E0E0" RowBackground="#FFF0F0F0" HorizontalScrollBarVisibility="Disabled" SelectedCellsChanged="dataGridWellReadings_SelectedCellsChanged" EnableRowVirtualization="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Date" Binding="{Binding readingDate, StringFormat=yyyy-MM-dd}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Pt" Binding="{Binding readingPt, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Pc" Binding="{Binding readingPc, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Ppl" Binding="{Binding readingPpl, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="MCFD" Binding="{Binding readingMCFD, StringFormat=0.#}" Width="2*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Water Produced" Binding="{Binding readingWaterProduced, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Water Hauled" Binding="{Binding readingWaterHauled, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Temperature" Binding="{Binding readingTemperature, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Hours On (actual)" Binding="{Binding readingHoursOnActual, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Hours On (planned)" Binding="{Binding readingHoursOnPlanned, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="Clock Cycles" Binding="{Binding readingClockCycles, StringFormat=0.#}" Width="3*">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="KeyboardNavigation.IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
There are too many variables to answer this with certainty. However, here are some things for you to consider:
Is the amount of data you are feeding the grid necessary? Are you possibly giving it too much data than the user will really use? This can slow things down.
Are you rendering the columns or cells with too many templates? This makes your presentation flexible, I know, but too many templates (or controls) can slow things down.
Do you have a lot of value converters in your datagrid? Is it required that every row or every column run some slightly expensive code in order to render?
Is it possible that you have a lot of nested styles (using BasedOn) and, perhaps more important, many triggers in those styles which steal render time to apply?
Are you using a lot of user controls, esp nested controls, in your presentation that might be causing the render delay in your presentation?
Is the binding string you are using for your cells complex? Applying many StringFormat or ElementName or Ancestory lookups? These contribute to slowness in rendering.
Is it possible that a visual brush is being used to show more of the data than is immediately visible to the user? This would short circuit the virtualization logic.
Have you considered using a FallBackValue to your bindings and setting IsAsync to true? Setting this to tru will show FallBackValue until the data is ready.
Are you using many MultiBindings or PriorityBindings in your UI that might be causing your rendering to slow down as it processes more than one field or value?
Are the styles you are using complex? Especially gradient brushes, rendering these can be costly, especially if you are doing it every single row in your grid.
Have you considered using paging so that you have less data? In the end, Nairou, this is the best solution for these types of problems if you can make users accept it.
Are you looking at Memory and CPU usage? Is it possible that the hardware you are using is simply struggling to render the UI you have created here?
Are you watching the debug output to see if there are binding errors or other swallowed errors that contribute to the degrade in performance?
Did you smile at the screen and give your code a good feeling so it is willing to try harder for you? Just kidding, but there are lots of variables - huh?
Have you implemented Commands whose CanExecute handlers are called very frequently and are perhaps expensive to execute? These can be silent killers of performance.
Again, there is no 100% answer to this problem. But these might help.
One more thing to consider is that your enumeration can be an observable list - this will let you load data in parts. If you want to load the first page and in an async process append the next page and the next and so on, the user experience should be very close to loading it all at once except it will be a faster initial render. This can be complex, but it another option for you. Observable lists are nifty like that.
Something like this:
ObservableCollection<User> Users { get; set; }
void LoadUsers()
{
int _Size = 2;
int _Page = 0;
using (System.ComponentModel.BackgroundWorker _Worker
= new System.ComponentModel.BackgroundWorker())
{
_Worker.WorkerReportsProgress = true;
_Worker.DoWork += (s, arg) =>
{
List<User> _Data = null;
while (_Data == null || _Data.Any())
{
_Data = GetData(_Size, _Page++);
_Worker.ReportProgress(_Page, _Data);
}
};
_Worker.ProgressChanged += (s, e) =>
{
List<User> _Data = null;
_Data = e.UserState as List<User>;
_Data.ForEach(x => Users.Add(x));
};
_Worker.RunWorkerAsync();
}
}
List<User> GetData(int size, int page)
{
// never return null
return m_Context.Users.Take(size).Skip(page).ToList();
}
Here's what I want you to take away - binding in WPF is never instant. You will never have a complex form render and bind without SOME delay. You can control the pain here with some of the techniques above. However, you can never remove it all. However, binding in WPF is the most powerful and awesome binding tech. I have ever experienced.
Best of luck!
You might find that the slow performance is not related to the attaching itself, but to the redrawing of the DataGrid that happens when the data is displayed. The delay you mentioned seems fairly excessive.
I had a problem with the DataGrid in which it took literally seconds to refresh after a window resize, column sort, etc. and locked up the window UI while it was doing so (1000 rows, 5 columns).
It came down to an issue (bug?) with the WPF sizing calculations. I had it in a grid with the RowDefinition Height="Auto" which was causing the rendering system to try and recalculate the size of the DataGrid at runtime by measuring the size of each and every column and row, presumably by filling the whole grid (as I understand it). It is supposed to handle this intelligently somehow but in this case it was not.
A quick check to see if this is a related problem is to set the Height and Width properties of the DataGrid to a fixed size for the duration of the test, and try running again. If your performance is restored, a permanent fix may be among these options:
This might be useful to some:
I had a datagrid that was slow to bind just like the original poster.
The application queried the database and did a lot of logic in a short while and then took one or more seconds to simply bind the observable collection to the datagrid. In my case it turned out that although most of the data was ready, some of it was lazy-loaded (meaning it did not get loaded until needed - this is a common and useful part of most ORM tools like NHibernate, iBatis etc.). It wasn't needed until the binding happened. In my case it was not all the data but only one single column that was lazy-loaded. It turned out that WPF already has a very simple mechanism for handling something like this. Setting the binding for this column to the following solved the problem:
<Binding Path="SomeProperty" IsAsync="True" FallbackValue="..." />
My datagrid loaded almost instantly. One column contained just the text "..." for a few seconds, and then the correct data appeared.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With