WPF DataBinding Very Slow when I use Convert to format a date
I'm writing a simple app to display some data on a DataGrid. The data is just a measurement (float) and a timestamp. The timestamp a uint and is in seconds since 2000.
I sucessfully accomplished the task but did notice it takes a long time (~1 minute) to display the datagrid. There are about 20,000 data. I wouldn't think 20,000 datum consisting of a uint and a float was that mush. The next request was to display the time as formatted time instead of seconds since 2000. This I did by making the XAML look like this:
<kit:DataGridTextColumn Header="FilteredValue" Binding="{Binding Path=FilteredValue}" />
<kit:DataGridTextColumn Header="Timestamp" Binding="{Binding Path=Timestamp, Converter={StaticResource TimeConverter}}" CanUserSort="False" />
The TimeConverter method looks like:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime currentDateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
currentDateTime = currentDateTime.AddSeconds((uint)value);
return currentDateTime.ToString();
}
This also worked fine. However, it turns out that some of the raw data can be 0xFFFFFFFF. This means that there is no data or invalid data. In this case, I don't want to convert to a date. So I wrote:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((uint)value == 0xFFFFFFFF)
{
// don't bother to convert
return ((uint)value).ToString("X");
}
else
{
DateTime currentDateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
currentDateTime = currentDateTime.AddSeconds((uint)value);
return currentDateTime.ToString();
}
}
Again, it works, but it's very slow. Slower than the original and takes about 10 minutes. I was pretty amazed by this. Is it just the case that the extra code is running 23,000 times? 1. What should I be doing? Can I do something in XAML so my Converter is not called if not necessary? 2. When I have 0xFFFFFFFF for one of the measurements (FilteredValues) it gets displayed as NaN. This is probably ok, but it would be nice just to show 0xFFFFFFFF or "no data". I think it's getting set to NaN because the underlying data type is a float.
Any ideas?
Thanks, Dave
Here is the XAML. The last Datagrid is the one of interest. Note that I even set "IsVirtualizing" to True". Also note the use of ScrollViewer. I did this because otherwise I can't see all the rows on the last grid (when it finally) displays. Removing this did not speed things up.
<Window x:Class="STDatabaseReader.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:开发者_运维技巧chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" xmlns:kit="http://schemas.microsoft.com/wpf/2008/toolkit" xmlns:local="clr-namespace:STDatabaseReader"
Title="Smart Transmitter Database Reader">
<Window.Resources>
<local:BytesToStringConverter x:Key="BytesToStringConverter"></local:BytesToStringConverter>
<local:TimeConverter x:Key="TimeConverter"></local:TimeConverter>
</Window.Resources>
<Grid>
<ScrollViewer>
<StackPanel Orientation="Vertical">
<Button Name="m_btnFetchData" HorizontalAlignment="Left" Click="m_btnFetchData_Click">Fetch File</Button>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Label HorizontalAlignment="Center">Partition 1</Label>
<kit:DataGrid Name="m_gridPartion1" AutoGenerateColumns="False">
<kit:DataGrid.Columns>
<kit:DataGridTextColumn Header="Header Info" Binding="{Binding Path=HeaderInfo, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
<kit:DataGridTextColumn Header="Transmitter Id" Binding="{Binding Path=TransmitterId, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
<kit:DataGridTextColumn Header="DeviceNumber" Binding="{Binding Path=DeviceNumber}" />
<kit:DataGridTextColumn Header="HardwareVersion" Binding="{Binding Path=HardwareVersion}" />
<kit:DataGridTextColumn Header="CRC" Binding="{Binding Path=CRC}" />
</kit:DataGrid.Columns>
</kit:DataGrid>
</StackPanel>
<StackPanel Orientation="Vertical">
<Label HorizontalAlignment="Center">Partition 3</Label>
<kit:DataGrid Name="m_gridPartion3" AutoGenerateColumns="False">
<kit:DataGrid.Columns>
<kit:DataGridTextColumn Header="Header Info" Binding="{Binding Path=HeaderInfo, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
<kit:DataGridTextColumn Header="SystemTime" Binding="{Binding Path=SystemTime, Converter={StaticResource TimeConverter}}" />
</kit:DataGrid.Columns>
</kit:DataGrid>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical">
<Label HorizontalAlignment="Center">Partition 2</Label>
<kit:DataGrid Name="m_gridPartion2" AutoGenerateColumns="False">
<kit:DataGrid.Columns>
<kit:DataGridTextColumn Header="Header Info" Binding="{Binding Path=HeaderInfo, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
<kit:DataGridTextColumn Header="FirmwareRevision" Binding="{Binding Path=FirmwareRevision, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
<kit:DataGridTextColumn Header="SoftwarePartNumber" Binding="{Binding Path=SoftwarePartNumber, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
<kit:DataGridTextColumn Header="FirmwareUpgradeTime" Binding="{Binding Path=FirmwareUpgradeTime,Converter={StaticResource TimeConverter}}" />
<kit:DataGridTextColumn Header="DatabaseEraseTime" Binding="{Binding Path=DatabaseEraseTime,Converter={StaticResource TimeConverter}}" />
<kit:DataGridTextColumn Header="RangeEnzymeElectrode" Binding="{Binding Path=RangeEnzymeElectrode}" />
<kit:DataGridTextColumn Header="OffsetEnzymeElectrode" Binding="{Binding Path=OffsetEnzymeElectrode}" />
<kit:DataGridTextColumn Header="BiasValue" Binding="{Binding Path=BiasValue}" />
</kit:DataGrid.Columns>
</kit:DataGrid>
</StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<Label HorizontalAlignment="Center">Partition 4 - HeaderInfo</Label>
<kit:DataGrid Name="m_gridDataHeader" AutoGenerateColumns="False">
<kit:DataGrid.Columns>
<kit:DataGridTextColumn Header="Header Info" Binding="{Binding Path=HeaderInfo, Converter={StaticResource BytesToStringConverter}}" CanUserSort="False" />
</kit:DataGrid.Columns>
</kit:DataGrid>
</StackPanel>
<StackPanel Orientation="Vertical">
<Label HorizontalAlignment="Center">Partition 4 - Chemistry Data</Label>
<kit:DataGrid Name="m_gridData" AutoGenerateColumns="False" VirtualizingStackPanel.IsVirtualizing="True" Loaded="m_gridData_Loaded">
<kit:DataGrid.Columns>
<!--
<kit:DataGridTextColumn Header="Noise" Binding="{Binding Path=Noise, StringFormat=\{0:X8\}}" />
<kit:DataGridTextColumn Header="FilteredValue" Binding="{Binding Path=FilteredValue, StringFormat='X'}" />
<kit:DataGridTextColumn Header="Timestamp" Binding="{Binding Path=Timestamp, StringFormat=\{0:X\}}" /> -->
<kit:DataGridTextColumn Header="Noise" Binding="{Binding Path=Noise}" />
<kit:DataGridTextColumn Header="FilteredValue" Binding="{Binding Path=FilteredValue}" />
<kit:DataGridTextColumn Header="Timestamp" Binding="{Binding Path=Timestamp, Converter={StaticResource TimeConverter}}" CanUserSort="False" />
</kit:DataGrid.Columns>
</kit:DataGrid>
</StackPanel >
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
Since the column is a DataGridTextColumn
you can make it display 0xFFFFFFFF by just returning it in the converter
if ((uint)value == 0xFFFFFFFF)
{
// don't bother to convert
return "0xFFFFFFFF";
}
As for the DataGrid
being slow, it should be using a VirtualizingStackPanel
by default so if you haven't changed this then it should be pretty fast since you'll only be working with the DataGridRows
that are visible to the user at the moment. Also the code in the converter should take virtually no time.
So the most likely reason for your DataGrid
being slow is probably that you've changed the ItemsPanel
to something else than a VirtualizingStackPanel
or disabled the virtualization somehow but it's hard to tell without seeing how your DataGrid
is defined
Edit
Run the following code after your DataGrid
has finished loading, for example in the Loaded
event for the DataGrid
. If the MessageBox
displays a large number (shouldn't be above 50) then you have the source of your problem.
private void DataGrid_Loaded(object sender, RoutedEventArgs e)
{
DataGrid dataGrid = sender as DataGrid;
List<DataGridRow> generatedDataGridRows = VisualTreeHelpers.GetVisualChildCollection<DataGridRow>(dataGrid);
MessageBox.Show(generatedDataGridRows.Count.ToString());
}
public static List<T> GetVisualChildCollection<T>(object parent) where T : Visual
{
List<T> visualCollection = new List<T>();
GetVisualChildCollection(parent as DependencyObject, visualCollection);
return visualCollection;
}
private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : Visual
{
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (child is T)
{
visualCollection.Add(child as T);
}
else if (child != null)
{
GetVisualChildCollection(child, visualCollection);
}
}
}
For example, using a StackPanel
as the parent panel will be very slow since the DataGrid
can consume unlimited vertical space so all the rows will be generated
<StackPanel>
<!-- Slow DataGrid with 20000+ items in ItemsSource -->
<DataGrid ...>
</StackPanel>
but using a Grid
will be very fast because the DataGrid
will be restricted in height so Virtualization can be used
<Grid>
<!-- Fast DataGrid with 20000+ items in ItemsSource -->
<DataGrid ...>
</Grid>
精彩评论