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!