WPF数据绑定使用转换格式化日期时非常缓慢

4
我正在编写一个简单的应用程序来在DataGrid上显示一些数据。这些数据是浮点数和时间戳。时间戳是一个uint类型的,表示自2000年以来经过的秒数。
我成功地完成了任务,但发现显示datagrid需要很长时间(约1分钟)。数据大约有20,000个。我想20,000个数据由一个uint和一个float组成并不算太多。下一个请求是将时间显示为格式化的时间而不是自2000年以来的秒数。我通过让XAML看起来像这样来实现这一点:
<kit:DataGridTextColumn Header="FilteredValue" Binding="{Binding Path=FilteredValue}" />
<kit:DataGridTextColumn Header="Timestamp" Binding="{Binding Path=Timestamp, Converter={StaticResource TimeConverter}}" CanUserSort="False" />

TimeConverter方法的外观如下:

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();                  
}

这个也可以正常工作。然而,事实证明一些原始数据可能是0xFFFFFFFF。这意味着没有数据或无效数据。在这种情况下,我不想转换为日期。因此我写了以下代码:

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();
   }

}

再次运行后,虽然可以正常工作,但速度非常慢。比原来慢了大约10分钟。这让我感到惊讶。难道只是因为额外的代码运行了23000次吗? 1. 我该怎么做?我能在XAML中做些什么,以便我的转换器不会在不必要的情况下被调用吗? 2. 当其中一个测量值(FilteredValues)为0xFFFFFFFF时,它会显示为NaN。这可能没问题,但最好只显示0xFFFFFFFF或“无数据”。我认为它被设置为NaN是因为底层数据类型是浮点数。

有任何想法吗? 谢谢, Dave

以下是XAML代码。最后的Datagrid是我们关注的部分。请注意,我甚至将“IsVirtualizing”设置为True。还请注意使用ScrollViewer。我这样做是因为如果没有这个,我无法在最后一个网格上看到所有行(当最后一个网格终于显示出来时)。删除此功能并没有提高速度。

<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>


使用性能分析器能帮助你找到瓶颈吗? - Aaron Anodide
1个回答

2

由于该列是DataGridTextColumn,您可以通过在转换器中返回0xFFFFFFFF来使其显示。

if ((uint)value == 0xFFFFFFFF)
{
    // don't bother to convert
    return "0xFFFFFFFF";
}

关于 DataGrid 的速度慢的问题,它默认应该使用 VirtualizingStackPanel,所以如果您没有更改过这个设置,那么它应该非常快,因为您只会处理当前对用户可见的 DataGridRows。此外,转换器中的代码几乎不需要时间。
因此,您的 DataGrid 速度慢最可能的原因是您已将 ItemsPanel 更改为除了 VirtualizingStackPanel 之外的其他内容,或者在某种程度上禁用了虚拟化,但没有看到您如何定义 DataGrid,很难确定。
编辑:在 DataGrid 加载完成后运行以下代码,例如在 DataGridLoaded 事件中。如果 MessageBox 显示一个大数字(不应超过50),那么您就找到了问题的源头。
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);
        }
    }
}

例如,使用StackPanel作为父面板将会非常缓慢,因为DataGrid可以消耗无限的垂直空间,因此所有行都将被生成。
<StackPanel>
    <!-- Slow DataGrid with 20000+ items in ItemsSource -->
    <DataGrid ...>
</StackPanel>

但是使用Grid将非常快,因为DataGrid将被限制在高度上,所以可以使用虚拟化技术。

<Grid>
    <!-- Fast DataGrid with 20000+ items in ItemsSource -->
    <DataGrid ...>
</Grid>

谢谢Meleak,我尝试了你的一些建议。我把XAML放在了我的原始帖子中。最后一个DataGrid有20000多个项目。我试过在<DataGrid>周围加上一些<Grid>,但没有效果。它仍然很慢。我确实广泛使用StackPanels,但我猜你是指直接父级面板吗?我还尝试了你的代码来查看行数。当我首次启动应用程序时,它显示为'0',然后在获取文件时代码永远不会被执行。除了'Loaded'事件之外,还有其他事件我需要关注吗?非常感谢,Dave - Dave
@Dave:给DataGrid起一个名字,比如dataGrid(如果你还没有这样做的话),然后一旦DataGrid加载完成并能够看到数据,只需添加一个Button,并在Button的点击事件中执行该代码。请告诉我结果。 - Fredrik Hedblad
Meleak,我添加了按钮处理程序并得到了13080的计数,所以这似乎是问题所在,但我错在哪里呢?上面的XAML中命名为m_gridData的datagrid是否有任何问题?正如我所说,我的代码充满了StackPanels,但我不认为使用StackPanel(来组织内容)与DataGrids不兼容。谢谢,Dave。 - Dave
@Dave:问题出在StackPanels上。StackPanel总是会给它的子元素所要求的空间。因此,如果你在StackPanel中添加一个Grid和一个DataGrid,那么DataGrid将告诉Grid它需要多少空间,然后Grid会告诉StackPanel它需要多少空间,而StackPanel会给它全部的高度,在你的情况下是13080行。使用RowDefinition Height="Auto"Grid也会得到同样的结果,尝试使用'*'大小的Grid-RowDefinitions。从20000多行的datagrid开始,逐步回退。 - Fredrik Hedblad
Meleak,非常感谢您的帮助。已标记为答案。我没有完全理解您上次回复中提到的尝试事项,但我当然理解了Grid告诉StackPanel它需要13080行的高度这一部分。所以我将DataGrid更改为:<kit:DataGrid Height="400"。这是一个hack,因为我不理解您关于RowDefinition Height="Auto"的评论。这是否应该放在Grid定义中?而且,您说的“使用'*'大小的Grid-RowDefinitions”是什么意思?无论如何,我尝试了,但没有成功。可能是因为我没有理解。再次感谢。 - Dave
@Dave。是的,Height="400"肯定可以解决它 :) 而且,你走在了正确的轨道上。像这样为Grid指定行 <Grid><Grid.RowDefinitions><RowDefinition Height="*"/> 然后通过例如 <DataGrid Grid.Row="1".. /> 指定要将元素放入哪一行。请参见此示例:http://msdn.microsoft.com/en-us/library/system.windows.controls.grid.row.aspx。祝你好运! - Fredrik Hedblad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接