如何线程化视图列表自定义视图绑定,以便UI不会冻结

时间:2011-07-12 08:18:21

标签: wpf multithreading data-binding listview asynchronous

我是WPF的新手,尽管我尽可能多地阅读,但我是新手需要WPF贤哲的智慧。

我有一个带有自定义视图的视图列表,该视图显示了折线图的平面视图。用户在单独的列表框中选择一个项目,这将设置要在列表视图中使用的数据源。我的问题是,当listivew生成视图时,UI会冻结。这是布局的外观:

((对不起,我是网站的新用户,所以我无法发布图片,但是,这里有一个链接:ScreenShot))

数据源由一个包含一些描述性属性的类和一个数据点列表(x,y)组成。 tileview获取数据点列表的列表,并使用数据点列表为每个列表项生成一个折线图。

目前我有一个数据源,其中包含一个包含200个项目的列表,每个项目有大约200个数据点(x,y)。

当我在左侧列表框中选择一个项目时,我使用 SelectionChanged 事件使用新数据集将 ItemsSource 属性设置为listview。问题是UI会冻结,直到生成所有图表。

我一直在尝试不同的方法而没有成功。我试图将数据源设置为异步数据源:

DataSeries="{Binding IsAsync=True, Path=Data}"

这只能通过在呈现视图之前释放UI来帮助获得一点点小小的位置。这意味着在更改数据源之后,它会暂停UI一段时间,然后释放它,显示包含所有200个空图表的视图,并在使用填充图表稍微刷新视图之后。

我还尝试将绑定放在这样的线程中:

private void List_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    //Find the selected itemsource
    string runName = (string)ExperimentList.SelectedItem;
    ExperimentalRun selectedRun = _experimentRuns.Find(item => item.Name == runName);

    //bind datasource thru a new thread
    Thread newThread = new Thread(lstProductsNewSource);
    newThread.Start(selectedRun);
}

private void lstProductsNewSource(Object selectedRun)
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate()
    {
        lstProducts.ItemsSource = ((ExperimentalRun)selectedRun).RunGraphData;
    });
} 

lstProducts是具有图表平铺视图的列表视图的名称。 RunGraphData属性指向数据点列表的列表。

我希望这会释放UI,但这不起作用。在这种情况下,在数据绑定之前没有耗时的过程,数据在内存中很容易获得,耗时的是创建视图和它们的数据绑定,而不是渲染。

作为附加细节,我想指定图表是由usercontrol生成的,以下是tile视图的xml的外观:

<local:TileView x:Key="ImageView">
    <local:TileView.ItemTemplate>
        <DataTemplate>
            <StackPanel Width="150" VerticalAlignment="Top">
                <local:graph DataSeries="{Binding IsAsync=True, Path=Data}"/>
                <TextBlock TextWrapping="Wrap" HorizontalAlignment="Center"  Text="{Binding >IsAsync=True, Path=ContainerName}" ></TextBlock>
            </StackPanel>
        </DataTemplate>
    </local:TileView.ItemTemplate>
</local:TileView>

图形用户控件是一种典型的图表控件,它接收数据集并将其绑定。

我寻求有关如何在listview绑定时保持我的UI响应的建议。你能救我吗?

编辑:

我忘了提到我尝试在listview上使用虚拟化而没有任何明显的区别:

<ListView Name="lstProducts" View="{StaticResource GridView}" >VirtualizingStackPanel.IsVirtualizing="True" >
</ListView>

我原本期望它会加载当前窗口值的项目,当我向下滚动它会开始加载新项目,但即使我使用 IsVirtualizing 属性设置为true它似乎一次加载,相同的延迟,即使我向下滚动没有额外的延迟给我的印象,所有项目已经存在。

编辑2: 我在本网站找到了另一个问题,答案提供了一种解决方案:link

根据后面提到的线程中提供的建议,我修改了我之前的线程尝试,试图将整个数据绑定事件封装到一个后台线程中,通过利用ObservableCollection自动将一个数据一次抽取到一个图形中。更新属性。基本上我创建一个空的数据源并分配它,然后我通过每次添加一个点时创建一个具有后台优先级的线程逐个添加元素到数据源。这看起来像这样:

private void ExperimentList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    //Find the selected itemsource
    string runName = (string)ExperimentList.SelectedItem;
    ExperimentalRun selectedRun = _experimentRuns.Find(item => item.Name == runName);

    //create and empty datasource to be filled one item at a time
    ExperimentalRun pumpRun = new ExperimentalRun(((ExperimentalRun)selectedRun).Name);
    lstProducts.ItemsSource = pumpRun.RunGraphData;

    //fill the emtpy source using a new thread per item added
    foreach (var item in selectedRun.RunGraphData)
    {
        PumpExperimentalRunData pump = new PumpExperimentalRunData(pumpRun, item);
        Thread newThread = new Thread(PumpAdd);
        newThread.Start(pump);
    }
}

private void PumpAdd(object PumpExperimentalRunData)
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate()
    {
            ((PumpExperimentalRunData)PumpExperimentalRunData).PumpRun.RunGraphData.Add(((PumpExperimentalRunData)PumpExperimentalRunData).Item);
    });
}

这种方法似乎有效,但我担心每次更改数据源时都会创建太多线程(200)...到目前为止我所读到的内容建议只使用几个线程,这不符合条件我认为只有少数几个。我想以不同的方式做到这一点,但我没有想法。

任何人都可以看到我的方式错误或指出更好的方法吗?

1 个答案:

答案 0 :(得分:0)

您必须使用VirtualizingStackPanel

<ListView.ItemsPanel>
  <ItemsPanelTemplate>   
      <VirtualizingStackPanel/>                 
  </ItemsPanelTemplate>  
</ListView.ItemsPanel>