Toggle navigation
首页
关于TCPGAME
快乐分享
联系我们
C# WPF MVVM实现百万级数据列表丝滑流畅加载显示
Lonner
时间:2021-03-10 09:10:51
阅读:3169
### 需求 在WPF中,有个列表对象存在百万级数据的量,但是又不想做分页,特别是在做数据流监控或日志输出时的功能,当然分页可以解决这样的问题,但是作为WPF丝滑开发坚守者,分页是不可能分页的,这辈子也不可能分页,只有找找资料实现丝滑加载,才能维持得了丝滑开发原则这样子。 ### 原理 原理就是WPF的虚拟化加载,虚拟化加载就是界面上只会渲染需要显示的部分数据,将其转化为界面控件显示出来,其他部分未显示的数据就不参与渲染,从而达到丝滑加载的效果。 WPF列表控件有非常多的列表显示控件,均继承自ItemsControl,因为每一行数据都使用自定义控件样式,所以使用ItemsControl来开启虚拟化实现大数据显示。 ###实现过程 直接新建一个WPF工程来说明这个列子。 先新建一个Model,命名为**Info.cs**: ``` using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MVVMBigDataList.Model { public class Info { public int Number { get; set; } public DateTime Time { get; set; } public string Message { get; set; } } } ``` 界面ViewModel,命名为**VMMainWindow.cs** ``` using MVVMBigDataList.Model; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Input; namespace MVVMBigDataList.ViewModel { public class VMMainWindow:VMBase { private ObservableCollection<Info> workDatas = new ObservableCollection<Info>(); public ObservableCollection<Info> WorkDatas { get => workDatas; set { workDatas = value; RaisePropertyChanged("WorkDatas"); } } public ICommand CmdAddMillionData { get; set; } public ICommand CmdAddOneData { get; set; } public ICommand CmdThreadAddData { get; set; } private bool ThreadRun = false; public VMMainWindow() { CmdAddMillionData = new RelayCommand<ItemsControl>(AddMillionData); CmdAddOneData = new RelayCommand<ItemsControl>(AddOneData); CmdThreadAddData = new RelayCommand<ItemsControl>(ThreadAddData); } private void AddMillionData(ItemsControl dg) { ObservableCollection<Info> temp = new ObservableCollection<Info>(WorkDatas); int currentSize = temp.Count(); for (int i = currentSize; i < currentSize + 1000000;i++) { var info = new Info(); info.Number = i; info.Time = DateTime.Now; info.Message = "Message" + i; temp.Add(info); } WorkDatas = temp; ScrollViewer s = dg.Template.FindName("ScrollViewer", dg) as ScrollViewer; s.ScrollToEnd(); } private void AddOneData(ItemsControl dg) { int currentSize = WorkDatas.Count(); var info = new Info(); info.Number = currentSize; info.Time = DateTime.Now; info.Message = "Message" + currentSize; WorkDatas.Add(info); ScrollViewer s= dg.Template.FindName("ScrollViewer" ,dg) as ScrollViewer; s.ScrollToEnd(); } private async void ThreadAddData(ItemsControl dg) { if (ThreadRun) { ThreadRun = false; return; } ThreadRun = true; while (ThreadRun) { AddOneData(dg); await Task.Delay(1); } } } } ``` 主窗体界面MainWindow.xaml: ``` <Window x:Class="MVVMBigDataList.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MVVMBigDataList" xmlns:viewmodel="clr-namespace:MVVMBigDataList.ViewModel" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.DataContext> <viewmodel:VMMainWindow></viewmodel:VMMainWindow> </Window.DataContext> <Window.Resources> <Style TargetType="ItemsControl" x:Key="Virtualizing"> <!--不需要使用按Item滚动--> <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"></Setter> <!--设置为循环,滑动显示出来的数据会创建一个新的界面控件,当隐藏掉的时候会将它创建的界面循环利用,对内存优化效果特别好--> <Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling"></Setter> <!--开启虚拟化--> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Vertical"></VirtualizingStackPanel> </ItemsPanelTemplate> </Setter.Value> </Setter> <!--显示滚动条 --> <!--注意:ScrollViewer一定要写在Template内,否正使用ScrollViewer 包含 ItemsControl 会导致ItemsControl虚拟化失效,大坑!!--> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" CanContentScroll="True"> <ItemsPresenter /> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> <DataTemplate x:Key="RowTemplate"> <Grid Height="25" > <Grid.Resources> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="White"/> <Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/> </Style> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition Width="60"/> <ColumnDefinition Width="100*"/> <ColumnDefinition Width="100*"/> <ColumnDefinition Width="120"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Number}"/> <Grid Grid.Column="0" Width="1" Background="Gray" HorizontalAlignment="Right"></Grid> <TextBlock Grid.Column="1" Text="{Binding Time,StringFormat={}{0:HH:mm:ss}}"/> <Grid Grid.Column="1" Width="1" Background="Gray" HorizontalAlignment="Right"></Grid> <TextBlock Grid.Column="2" Text="{Binding Message}"/> <Grid Grid.Column="2" Width="1" Background="Gray" HorizontalAlignment="Right"></Grid> <WrapPanel Orientation="Horizontal" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Center"> <Button Content="启动"></Button> <Button Margin="5,0,0,0" Content="配置"></Button> <Button Margin="5,0,0,0" Content="删除"></Button> </WrapPanel> <Grid Grid.Column="3" Width="1" Background="Gray" HorizontalAlignment="Right"></Grid> <Grid Height="1" Background="Gray" Grid.ColumnSpan="10" VerticalAlignment="Bottom"></Grid> </Grid> </DataTemplate> </Window.Resources> <Grid Background="#333333" > <StackPanel Height="60" VerticalAlignment="Top" Orientation="Horizontal"> <Button Content="增加100W条数据" Width="120" Command="{Binding CmdAddMillionData}" CommandParameter="{Binding ElementName=DataList}"/> <Button Content="增加1条数据" Width="120" Command="{Binding CmdAddOneData}" CommandParameter="{Binding ElementName=DataList}"/> <Button Content="线程增加数据" Width="120" Command="{Binding CmdThreadAddData}" CommandParameter="{Binding ElementName=DataList}"/> </StackPanel> <Border Background="#222222" Margin="0,60,0,0" BorderBrush="Gray" BorderThickness="1"> <ItemsControl x:Name="DataList" ItemsSource="{Binding WorkDatas}" ItemTemplate="{StaticResource RowTemplate}" Style="{StaticResource Virtualizing}" /> </Border> </Grid> </Window> ``` 运行,OK! ###扩展 不使用MVVM情况下,在ItemsControl标签内部引入如下的样式即可,注意ScrollViewer需要在Template中实现,否则虚拟化会出现失效情况,样式中代码如下: ``` <Style TargetType="ItemsControl" x:Key="Virtualizing"> <!--不需要使用按Item滚动--> <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"></Setter> <!--设置为循环,滑动显示出来的数据会创建一个新的界面控件,当隐藏掉的时候会将它创建的界面循环利用,对内存优化效果特别好--> <Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling"></Setter> <!--开启虚拟化--> <Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <VirtualizingStackPanel Orientation="Vertical"></VirtualizingStackPanel> </ItemsPanelTemplate> </Setter.Value> </Setter> <!--显示滚动条 --> <!--注意:ScrollViewer一定要写在Template内,否正使用ScrollViewer 包含 ItemsControl 会导致ItemsControl虚拟化失效,大坑!!--> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" CanContentScroll="True"> <ItemsPresenter /> </ScrollViewer> </ControlTemplate> </Setter.Value> </Setter> </Style> ``` 控件中使用虚拟化: ``` <ItemsContro Style="{StaticResource Virtualizing}"/> ``` ###Gihut源码 Github源码链接 [https://github.com/TCPGAME/MVVMBigDataList](https://github.com/TCPGAME/MVVMBigDataList "源码链接")
上一章:C# WPF MVVM 动画(Animation)的正确使用姿势
下一章:C# 程序运行时释放解决方案内的文件到指定目录